//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=============================================================================

#include "cbase.h"
#include "tf_weapon_spellbook.h"
#include "decals.h"
#include "tf_gamerules.h"
#include "tf_pumpkin_bomb.h"

// Client specific.
#ifdef CLIENT_DLL
	#include "c_basedoor.h"
	#include "c_tf_player.h"
	#include "IEffects.h"
	#include "bone_setup.h"
	#include "c_tf_gamestats.h"
	#include "iclientmode.h"
	#include <vgui_controls/AnimationController.h>
	#include "econ_notifications.h"
	#include "gc_clientsystem.h"
	#include "tf_logic_halloween_2014.h"
	#include "tf_hud_itemeffectmeter.h"
	extern void AddSubKeyNamed( KeyValues *pKeys, const char *pszName );
// Server specific.
#else
	#include "doors.h"
	#include "tf_player.h"
	#include "tf_ammo_pack.h"
	#include "tf_gamestats.h"
	#include "ilagcompensationmanager.h"
	#include "collisionutils.h"
	#include "particle_parse.h"
	#include "tf_projectile_base.h"
	#include "tf_gamerules.h"
	#include "tf_fx.h"
	#include "takedamageinfo.h"
	#include "halloween/zombie/zombie.h"
	#include "halloween/eyeball_boss/eyeball_boss.h"
	#include "halloween/halloween_base_boss.h"
	#include "entity_healthkit.h"
	#include "eyeball_boss/teleport_vortex.h"
	#include "in_buttons.h"
	#include "halloween/merasmus/merasmus.h"
	#include "tf_weapon_grenade_pipebomb.h"
	#include "tf_obj_dispenser.h"
#endif

ConVar tf_test_spellindex( "tf_test_spellindex", "-1", FCVAR_CHEAT | FCVAR_REPLICATED, "Set to index to always get a specific spell" );
#ifdef GAME_DLL
ConVar tf_halloween_kart_rocketspell_speed( "tf_halloween_kart_rocketspell_speed", "1500", FCVAR_CHEAT );
ConVar tf_halloween_kart_rocketspell_lifetime( "tf_halloween_kart_rocketspell_lifetime", "0.5f", FCVAR_CHEAT );
ConVar tf_halloween_kart_rocketspell_force( "tf_halloween_kart_rocketspell_force", "900.0f", FCVAR_CHEAT );
#endif

extern ConVar tf_eyeball_boss_hover_height;
extern ConVar tf_halloween_kart_normal_speed;
extern ConVar tf_halloween_kart_dash_speed;
//=============================================================================
//
// Weapon Tables
//

// SpellBook --
IMPLEMENT_NETWORKCLASS_ALIASED( TFSpellBook, DT_TFWeaponSpellBook )

	BEGIN_NETWORK_TABLE( CTFSpellBook, DT_TFWeaponSpellBook )
#ifdef CLIENT_DLL
	RecvPropInt( RECVINFO( m_iSelectedSpellIndex ) ),
	RecvPropInt( RECVINFO( m_iSpellCharges ) ),
	RecvPropFloat( RECVINFO( m_flTimeNextSpell ) ),
	RecvPropBool( RECVINFO( m_bFiredAttack ) ),
#else
	SendPropInt( SENDINFO( m_iSelectedSpellIndex ) ),
	SendPropInt( SENDINFO( m_iSpellCharges ) ),
	SendPropFloat( SENDINFO( m_flTimeNextSpell ) ),
	SendPropBool( SENDINFO( m_bFiredAttack ) ),
#endif

	END_NETWORK_TABLE()

	BEGIN_PREDICTION_DATA( CTFSpellBook )
	END_PREDICTION_DATA()

	LINK_ENTITY_TO_CLASS( tf_weapon_spellbook, CTFSpellBook );
PRECACHE_WEAPON_REGISTER( tf_weapon_spellbook );
// -- SpellBook

#define SPELL_EMPTY		-1
#define SPELL_UNKNOWN	-2
#define SPELL_BOXING_GLOVE "models/props_halloween/hwn_spell_boxing_glove.mdl"

//=============================================================================
// Spell Data Structures
//=============================================================================
enum SpellType_t
{
	SPELL_ROCKET,
	SPELL_JAR,		// Explodes on Contact
	SPELL_SELF,
};

struct spell_data_t
{
	spell_data_t( 
		const char *pSpellUiName, 
		int iSpellCharges, 
		SpellType_t eSpelltype, 
		const char *pSpellEntityName,  
		bool (*pCastSpell)(CTFPlayer*), 
		const char *pszCastSound, 
		float flSpeedScale, 
		int iCastContext, 
		int iSpellContext,
		const char *pIconName,
		bool bAutoCast = false
	) {
		m_pSpellUiName = pSpellUiName;
		m_eSpellType = eSpelltype;
		m_pSpellEntityName = pSpellEntityName;
		m_iSpellCharges = iSpellCharges;
		m_pCastSpell = pCastSpell;
		m_pszCastSound = pszCastSound;
		m_flSpeedScale = flSpeedScale;
		m_iCastContext = iCastContext;
		m_iSpellContext = iSpellContext;
		m_pIconName = pIconName;
		m_bAutoCast = bAutoCast;
	}

	const char * m_pSpellUiName;
	SpellType_t m_eSpellType;
	const char *m_pSpellEntityName;
	int m_iSpellCharges;
	const char *m_pszCastSound;
	float m_flSpeedScale;
	int m_iCastContext;	// context for the spell caster
	int m_iSpellContext; // context for enemies who witness the spell

	bool (*m_pCastSpell)(CTFPlayer*);
	const char *m_pIconName;
	bool m_bAutoCast;
};

static const spell_data_t g_NormalSpellList[] =
{
	spell_data_t( "#TF_Spell_Fireball",			2,		SPELL_ROCKET,	"tf_projectile_spellfireball",			NULL,	"Halloween.spell_fireball_cast", 1.f,MP_CONCEPT_PLAYER_CAST_BOMB_HEAD_CURSE,	MP_CONCEPT_PLAYER_SPELL_BOMB_HEAD_CURSE,		"spellbook_fireball" ),
	spell_data_t( "#TF_Spell_Bats",				2,		SPELL_JAR,		"tf_projectile_spellbats",				NULL,	"Halloween.spell_bat_cast", 1.f,	MP_CONCEPT_PLAYER_CAST_MERASMUS_ZAP,		MP_CONCEPT_PLAYER_SPELL_MERASMUS_ZAP,	"spellbook_bats" ),
	spell_data_t( "#TF_Spell_OverHeal",			1,		SPELL_SELF,		NULL,	CTFSpellBook::CastSelfHeal,				"Halloween.spell_overheal", 1.f,	MP_CONCEPT_PLAYER_CAST_SELF_HEAL,			MP_CONCEPT_PLAYER_SPELL_SELF_HEAL,		"spellbook_overheal" ),
	spell_data_t( "#TF_Spell_MIRV",				1,		SPELL_JAR,		"tf_projectile_spellmirv",				NULL,	"Halloween.spell_mirv_cast", 1.f,	MP_CONCEPT_PLAYER_CAST_MIRV,				MP_CONCEPT_PLAYER_SPELL_MIRV,			"spellbook_mirv" ),																													
	spell_data_t( "#TF_Spell_BlastJump",		2,		SPELL_SELF,		NULL,	CTFSpellBook::CastRocketJump,			"Halloween.spell_blastjump", 1.f,	MP_CONCEPT_PLAYER_CAST_BLAST_JUMP,			MP_CONCEPT_PLAYER_SPELL_BLAST_JUMP,		"spellbook_blastjump"),
	spell_data_t( "#TF_Spell_Stealth",			1,		SPELL_SELF,		NULL,	CTFSpellBook::CastSelfStealth,			"Halloween.spell_stealth", 1.f,		MP_CONCEPT_PLAYER_CAST_STEALTH,				MP_CONCEPT_PLAYER_SPELL_STEALTH,		"spellbook_stealth"),
	spell_data_t( "#TF_Spell_Teleport",			2,		SPELL_JAR,		"tf_projectile_spelltransposeteleport",	NULL,	"Halloween.spell_teleport", 1.f,	MP_CONCEPT_PLAYER_CAST_TELEPORT,			MP_CONCEPT_PLAYER_SPELL_TELEPORT,		"spellbook_teleport"),
};

static const int g_NavMeshSpells = 2; // Number of spells in this list that require a navmesh, they must be at the end of this array
static const spell_data_t g_RareSpellList[] =
{
	spell_data_t( "#TF_Spell_LightningBall",	1,		SPELL_ROCKET,	"tf_projectile_lightningorb",			NULL,	"Halloween.spell_lightning_cast", 0.4f,	MP_CONCEPT_PLAYER_CAST_LIGHTNING_BALL,		MP_CONCEPT_PLAYER_SPELL_LIGHTNING_BALL,		"spellbook_lightning"),
	spell_data_t( "#TF_Spell_Athletic",			1,		SPELL_SELF,		NULL,	CTFSpellBook::CastSelfSpeedBoost,		"Halloween.spell_athletic", 1.f,	MP_CONCEPT_PLAYER_CAST_MOVEMENT_BUFF,			MP_CONCEPT_PLAYER_SPELL_MOVEMENT_BUFF,		"spellbook_athletic"),
	spell_data_t( "#TF_Spell_Meteor",			1,		SPELL_JAR,		"tf_projectile_spellmeteorshower",		NULL,	"Halloween.spell_meteor_cast", 1.f,	MP_CONCEPT_PLAYER_CAST_METEOR_SWARM,			MP_CONCEPT_PLAYER_SPELL_METEOR_SWARM,		"spellbook_meteor"),
	spell_data_t( "#TF_Spell_SpawnBoss",		1,		SPELL_JAR,		"tf_projectile_spellspawnboss",			NULL,	"Halloween.Merasmus_Spell", 1.f,	MP_CONCEPT_PLAYER_CAST_MONOCULOUS,				MP_CONCEPT_PLAYER_SPELL_MONOCULOUS,			"spellbook_boss"),
	spell_data_t( "#TF_Spell_SkeletonHorde",	1,		SPELL_JAR,		"tf_projectile_spellspawnhorde",		NULL,	"Halloween.spell_skeleton_horde_cast", 1.f,	MP_CONCEPT_PLAYER_CAST_SKELETON_HORDE,	MP_CONCEPT_PLAYER_SPELL_SKELETON_HORDE,		"spellbook_skeleton"),
};

static const spell_data_t g_KartSpellList[] =
{
	// Kart Spells
	spell_data_t( "#TF_Spell_Fireball",			1,		SPELL_ROCKET,	"tf_projectile_spellkartorb",			NULL, "Halloween.spell_fireball_cast", 1.f,		MP_CONCEPT_PLAYER_CAST_MERASMUS_ZAP,		MP_CONCEPT_PLAYER_SPELL_MERASMUS_ZAP, "../hud/Punchglove_icon" ),
	spell_data_t( "#TF_Spell_BlastJump",		1,		SPELL_SELF,		NULL,	CTFSpellBook::CastKartRocketJump,			"Halloween.spell_blastjump", 1.f,	MP_CONCEPT_PLAYER_CAST_BLAST_JUMP,			MP_CONCEPT_PLAYER_SPELL_BLAST_JUMP,		"../hud/Parachute_icon"),
	spell_data_t( "#TF_Spell_OverHeal",			1,		SPELL_SELF,		NULL,	CTFSpellBook::CastKartUber,				"Halloween.spell_overheal", 1.f,		MP_CONCEPT_PLAYER_CAST_SELF_HEAL,			MP_CONCEPT_PLAYER_SPELL_SELF_HEAL,		"spellbook_overheal" ),
	spell_data_t( "#TF_Spell_BombHead",			1,		SPELL_SELF,		NULL,	CTFSpellBook::CastKartBombHead,				"Halloween.spell_overheal", 1.f,	MP_CONCEPT_PLAYER_CAST_FIREBALL,		MP_CONCEPT_PLAYER_SPELL_FIREBALL,		"../hud/bombhead_icon" ),
};

// Do not allow all spells in doomsday
static const int g_doomsdayNormalSpellIndexList[] =
{
	0,	//Fireball
	0,	//Fireball x2
	2,	//overheal
	4,	//Jump
	5,	//Stealth
};

static const int g_doomsdayRareSpellIndexList[] =
{
	ARRAYSIZE( g_NormalSpellList ) + 0,		// Lightning
	ARRAYSIZE( g_NormalSpellList ) + 1,		// Mini
	ARRAYSIZE( g_NormalSpellList ) + 2,		// Meteor
	ARRAYSIZE( g_NormalSpellList ) + 0,		// Lightning
	ARRAYSIZE( g_NormalSpellList ) + 1,		// Mini
	ARRAYSIZE( g_NormalSpellList ) + 2,		// Meteor
	ARRAYSIZE( g_NormalSpellList ) + 3		// Boss / Monoculus.  Smaller chance
};

// Regular SpellList
// teleport and summons removed
static const int g_generalSpellIndexList[] =
{
	0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 
	ARRAYSIZE ( g_NormalSpellList ) + 0, 
	ARRAYSIZE ( g_NormalSpellList ) + 1, 
	ARRAYSIZE ( g_NormalSpellList ) + 2
};

int GetTotalSpellCount( CTFPlayer *pPlayer )
{
	int iSpellCount = ARRAYSIZE( g_NormalSpellList ) + ARRAYSIZE( g_RareSpellList );
	if ( pPlayer && pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
	{
		iSpellCount += ARRAYSIZE( g_KartSpellList );
	}
	return iSpellCount;
}

bool IsRareSpell( int iSpellIndex )
{
	if ( tf_test_spellindex.GetInt() > 0 )
	{
		iSpellIndex = tf_test_spellindex.GetInt();
	}

	return ( ( iSpellIndex >= ARRAYSIZE( g_NormalSpellList ) ) && ( iSpellIndex < ARRAYSIZE( g_NormalSpellList ) + ARRAYSIZE( g_RareSpellList ) ) );
}

const spell_data_t* GetSpellData( int iSpellIndex )
{
	if ( tf_test_spellindex.GetInt() > -1 )
	{
		iSpellIndex = tf_test_spellindex.GetInt();
	}

	if ( iSpellIndex < 0 )
		return NULL;

	const int nNormalSpellCount = ARRAYSIZE( g_NormalSpellList );
	if ( iSpellIndex < nNormalSpellCount )
		return &g_NormalSpellList[ iSpellIndex ];

	const int nRareSpellRange = nNormalSpellCount + ARRAYSIZE( g_RareSpellList );
	if ( iSpellIndex < nRareSpellRange )
		return &g_RareSpellList[ iSpellIndex - nNormalSpellCount ];

	const int nKartSpellRange = nRareSpellRange + ARRAYSIZE( g_KartSpellList );
	if ( iSpellIndex < nKartSpellRange )
		return &g_KartSpellList[ iSpellIndex - nRareSpellRange];

	return NULL;
}

int GetSpellIndexFromContext( int iContext )
{
	const int nNormalSpellCount = ARRAYSIZE( g_NormalSpellList );
	for ( int i=0; i<nNormalSpellCount; ++i )
	{
		if ( g_NormalSpellList[i].m_iSpellContext == iContext )
		{
			return i;
		}
	}

	const int nRareSpellCount = ARRAYSIZE( g_RareSpellList );
	for ( int i=0; i<nRareSpellCount; ++i )
	{
		if ( g_RareSpellList[i].m_iSpellContext == iContext )
		{
			return i + nNormalSpellCount;
		}
	}

	return -1;
}

//=============================================================================
#ifdef CLIENT_DLL
//=============================================================================
// Ui Hud
//=============================================================================
extern ConVar cl_hud_minmode;

DECLARE_HUDELEMENT_DEPTH( CHudSpellMenu, 2 );
CHudSpellMenu::CHudSpellMenu( const char *pElementName ) : CHudElement( pElementName ), BaseClass ( NULL, "HudSpellMenu" )
{
	Panel *pParent = g_pClientMode->GetViewport();
	SetParent( pParent );

	SetHiddenBits( HIDEHUD_MISCSTATUS | HIDEHUD_HEALTH | HIDEHUD_PLAYERDEAD );

	m_iNextRollTime = 0;
	m_flRollTickGap = 0.05f;
	m_bTickSoundA = false;

	m_bKillstreakMeterDrawing = false;

	m_pSpellIcon = new vgui::ImagePanel( this, "SpellIcon" );
	m_pKeyBinding = new CExLabel( this, "ActionText", "" );

	ListenForGameEvent( "inventory_updated" );
	ListenForGameEvent( "localplayer_respawn" );
	ListenForGameEvent( "localplayer_changeclass" );
	ListenForGameEvent( "post_inventory_application" );
}

//-----------------------------------------------------------------------------
void CHudSpellMenu::ApplySchemeSettings( vgui::IScheme *pScheme )
{
	BaseClass::ApplySchemeSettings( pScheme );

	KeyValues *pConditions = NULL;
	if ( m_bKillstreakMeterDrawing )
	{
		pConditions = new KeyValues( "conditions" );
		if ( pConditions )
		{
			AddSubKeyNamed( pConditions, "if_killstreak_visible" );
		}
	}

	// load control settings...
	LoadControlSettings( "resource/UI/HudSpellSelection.res", NULL, NULL, pConditions );
	SetVisible( false );
	UpdateSpellText( -1, -1 );

	if ( pConditions )
	{
		pConditions->deleteThis();
	}
}

//=============================================================================
void CHudSpellMenu::OnTick( void )
{
	bool bKillstreakMeterDrawing = false;
	CHudItemEffectMeter *pMeter = NULL;
	for ( int i = 0; i < IHudItemEffectMeterAutoList::AutoList().Count(); ++i )
	{
		pMeter = static_cast<CHudItemEffectMeter*>( IHudItemEffectMeterAutoList::AutoList()[i] );
		if ( pMeter->IsKillstreakMeter() ) // we found the killstreak meter
		{
			if ( pMeter->IsEnabled() )
			{
				bKillstreakMeterDrawing = true;
			}
			break;
		}
	}

	if ( m_bKillstreakMeterDrawing != bKillstreakMeterDrawing )
	{
		m_bKillstreakMeterDrawing = bKillstreakMeterDrawing;
		InvalidateLayout( false, true );
	}

	vgui::ivgui()->RemoveTickSignal( GetVPanel() );
}

//=============================================================================
void CHudSpellMenu::FireGameEvent( IGameEvent * event )
{
	if ( FStrEq( event->GetName(), "post_inventory_application" ) ||
		 FStrEq( event->GetName(), "localplayer_respawn" ) ||
		 FStrEq( event->GetName(), "localplayer_changeclass" ) ||
		 FStrEq( event->GetName(), "inventory_updated" ) )
	{
		vgui::ivgui()->AddTickSignal( GetVPanel(), 10 );
	}
}

//=============================================================================
bool CHudSpellMenu::ShouldDraw( void )
{
	if ( TFGameRules() && TFGameRules()->IsUsingSpells() )
	{
		if ( CTFMinigameLogic::GetMinigameLogic() && CTFMinigameLogic::GetMinigameLogic()->GetActiveMinigame() && ( TFGameRules()->State_Get() != GR_STATE_RND_RUNNING ) )
			return false;

		C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
		if ( pPlayer && pPlayer->IsAlive() && !pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) )
		{
			CTFSpellBook *pSpellBook = dynamic_cast<CTFSpellBook*>( pPlayer->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) );
			if ( pSpellBook )
			{
				UpdateSpellText( pSpellBook->m_iSelectedSpellIndex, pSpellBook->m_iSpellCharges );
				return CHudElement::ShouldDraw();
			}
		}
	}
	return false;
}
//=============================================================================
void CHudSpellMenu::UpdateSpellText( int iSpellIndex, int iChargeCount )
{
	if ( iSpellIndex == SPELL_EMPTY || ( iChargeCount <= 0 && iSpellIndex != SPELL_UNKNOWN ) )
	{
		SetDialogVariable( "counttext", "..." );
		//SetDialogVariable( "selectedspell", g_pVGuiLocalize->Find( pSpellData->m_pSpellUiName ) );
		m_pSpellIcon->SetImage( "spellbook_nospell" );
		m_flRollTickGap = 0.01f;
		m_iNextRollTime = 0;
		m_pKeyBinding->SetVisible( false );
		return;
	}

	m_pSpellIcon->SetVisible( true );

	C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
	if ( !pLocalPlayer )
		return;

	static wchar_t wLabel[256];

	if ( iSpellIndex == SPELL_UNKNOWN )
	{
		if ( m_iNextRollTime > gpGlobals->curtime )
			return;
		m_iNextRollTime = gpGlobals->curtime + m_flRollTickGap;
		m_flRollTickGap += 0.015f;
		static int s_iRandSpell = 0;
		s_iRandSpell = ( s_iRandSpell + 1 ) % GetTotalSpellCount( pLocalPlayer );
		const spell_data_t *pSpellData = GetSpellData( s_iRandSpell );
		SetDialogVariable( "counttext", "?" );
		m_pSpellIcon->SetImage( pSpellData->m_pIconName );

		pLocalPlayer->EmitSound( m_bTickSoundA ? "Halloween.spelltick_a" : "Halloween.spelltick_b" );
		m_bTickSoundA = !m_bTickSoundA;
				
		m_iPrevSelectedSpell = SPELL_UNKNOWN;
		m_pKeyBinding->SetVisible( false );
	}
	else
	{
		m_flRollTickGap = 0.01f;
		m_iNextRollTime = 0;
		const spell_data_t *pSpellData = GetSpellData( iSpellIndex );
		if ( pSpellData )
		{
			SetDialogVariable( "counttext", iChargeCount );
			m_pSpellIcon->SetImage( pSpellData->m_pIconName );
			if ( m_iPrevSelectedSpell != iSpellIndex && iSpellIndex != SPELL_EMPTY )
			{
				pLocalPlayer->EmitSound( "Halloween.spelltick_set" );
			}
			m_iPrevSelectedSpell = iSpellIndex;
			m_pKeyBinding->SetVisible( !cl_hud_minmode.GetBool() );

			// Action Key Text
			wchar_t wKeyReplaced[256];
			UTIL_ReplaceKeyBindings( g_pVGuiLocalize->Find( "#TF_Spell_Action" ), 0, wKeyReplaced, sizeof( wKeyReplaced ) );
			SetDialogVariable( "actiontext", wKeyReplaced );
		}
	}
}

//-----------------------------------------------------------------------------
// CEquipSpellbookNotification
//-----------------------------------------------------------------------------
void CEquipSpellbookNotification::Accept()
{
	m_bHasTriggered = true;
		
	CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
	if ( !pLocalInv )
	{
		MarkForDeletion();
		return;
	}

	C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
	if ( !pLocalPlayer )
	{
		MarkForDeletion();
		return;
	}

	// try to equip non-stock-spellbook first
	static CSchemaItemDefHandle pItemDef_Spellbook( "Basic Spellbook" );
	static CSchemaItemDefHandle pItemDef_Diary( "Secret Diary" );
	static CSchemaItemDefHandle pItemDef_FancySpellbook( "Halloween Spellbook" );

	Assert( pItemDef_Spellbook );
	Assert( pItemDef_Diary );
	Assert( pItemDef_FancySpellbook );

	CEconItemView *pSpellBook = NULL;

	if ( pItemDef_Spellbook && pItemDef_Diary && pItemDef_FancySpellbook )
	{
		for ( int i = 0 ; i < pLocalInv->GetItemCount() ; ++i )
		{
			CEconItemView *pItem = pLocalInv->GetItem( i );
			Assert( pItem );
			if ( pItem->GetItemDefinition() == pItemDef_Spellbook 
				|| pItem->GetItemDefinition() == pItemDef_Diary 
				|| pItem->GetItemDefinition() == pItemDef_FancySpellbook 
			) {
				pSpellBook = pItem;
				break;
			}
		}
	}

	// Default item becomes a spellbook in this mode
	itemid_t iItemId = INVALID_ITEM_ID;
	if ( pSpellBook )
	{
		iItemId = pSpellBook->GetItemID();
	}

	TFInventoryManager()->EquipItemInLoadout( pLocalPlayer->GetPlayerClass()->GetClassIndex(), LOADOUT_POSITION_ACTION, iItemId );
	
	// Tell the GC to tell server that we should respawn if we're in a respawn room
	GCSDK::CGCMsg< GCSDK::MsgGCEmpty_t > msg( k_EMsgGCRespawnPostLoadoutChange );
	GCClientSystem()->BSendMessage( msg );

	MarkForDeletion();
}

//===========================================================================================
void CEquipSpellbookNotification::UpdateTick()
{
	C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
	if ( pLocalPlayer )
	{
		CTFSpellBook *pSpellBook = dynamic_cast<CTFSpellBook*>( pLocalPlayer->Weapon_OwnsThisID( TF_WEAPON_SPELLBOOK ) );
		if ( pSpellBook )
		{
			MarkForDeletion();
		}
	}
}
#endif // CLIENT_DLL

//===========================================================================================
//
// CTFSpellBook
//
//===========================================================================================
CTFSpellBook::CTFSpellBook()
{
	m_iSelectedSpellIndex = -1;
	m_iSpellCharges = 0;
	m_flTimeNextSpell = 0;
	m_bFiredAttack = false;
#ifdef CLIENT_DLL
	m_flTimeNextErrorSound = 0;
	m_hHandEffect = NULL;
	m_hHandEffectWeapon = NULL;
#endif // CLIENT_DLL

#ifdef GAME_DLL
	m_pStoredLastWpn = NULL;
	m_iPreviouslyCastSpell = -1;
#endif // GAME_DLL
}

void CTFSpellBook::Precache()
{
	PrecacheScriptSound( "Halloween.Merasmus_Spell" );
	PrecacheScriptSound( "Weapon_SniperRailgun_Large.SingleCrit" );
	PrecacheScriptSound( "Halloween.spelltick_a" );
	PrecacheScriptSound( "Halloween.spelltick_b" );
	PrecacheScriptSound( "Halloween.spelltick_set" );

	PrecacheScriptSound( "Halloween.spell_athletic" );
	PrecacheScriptSound( "Halloween.spell_bat_cast" );
	PrecacheScriptSound( "Halloween.spell_bat_impact" );
	PrecacheScriptSound( "Halloween.spell_blastjump" );
	PrecacheScriptSound( "Halloween.spell_fireball_cast" );
	PrecacheScriptSound( "Halloween.spell_fireball_impact" );
	PrecacheScriptSound( "Halloween.spell_lightning_cast" );
	PrecacheScriptSound( "Halloween.spell_lightning_impact" );
	PrecacheScriptSound( "Halloween.spell_meteor_cast" );
	PrecacheScriptSound( "Halloween.spell_meteor_impact" );
	PrecacheScriptSound( "Halloween.spell_mirv_cast" );
	PrecacheScriptSound( "Halloween.spell_mirv_explode_primary" );
	PrecacheScriptSound( "Halloween.spell_mirv_explode_secondary" );
	PrecacheScriptSound( "Halloween.spell_skeleton_horde_cast" );
	PrecacheScriptSound( "Halloween.spell_skeleton_horde_rise" );
	PrecacheScriptSound( "Halloween.spell_spawn_boss" );
	PrecacheScriptSound( "Halloween.spell_stealth" );
	PrecacheScriptSound( "Halloween.spell_teleport" );
	PrecacheScriptSound( "Halloween.spell_overheal" );

	PrecacheParticleSystem( "merasmus_zap" );
	PrecacheParticleSystem( "spell_cast_wheel_red" );
	PrecacheParticleSystem( "spell_cast_wheel_blue" );
	PrecacheParticleSystem( "Explosion_bubbles" );
	PrecacheParticleSystem( "ExplosionCore_buildings" );
	PrecacheParticleSystem( "water_splash01" );
	PrecacheParticleSystem( "healshot_trail_blue" );
	PrecacheParticleSystem( "healshot_trail_red" );
	PrecacheParticleSystem( "xms_snowburst" );
	PrecacheParticleSystem( "bomibomicon_ring" );
	PrecacheParticleSystem( "bombinomicon_burningdebris" );
	PrecacheParticleSystem( "merasmus_tp_bits" );
	PrecacheParticleSystem( "spell_fireball_tendril_parent_red" );
	PrecacheParticleSystem( "spell_fireball_tendril_parent_blue" );
	PrecacheParticleSystem( "spell_fireball_small_blue" );
	PrecacheParticleSystem( "spell_fireball_small_red" );
	PrecacheParticleSystem( "spell_lightningball_parent_blue" );
	PrecacheParticleSystem( "spell_lightningball_parent_red" );
	PrecacheParticleSystem( "spell_lightningball_hit_blue" );
	PrecacheParticleSystem( "spell_lightningball_hit_red" );
	
	PrecacheParticleSystem( "eyeboss_tp_vortex" );
	PrecacheParticleSystem( "spell_overheal_red" );
	PrecacheParticleSystem( "spell_overheal_blue" );
	PrecacheParticleSystem( "spell_teleport_red" );
	PrecacheParticleSystem( "spell_teleport_blue" );
	PrecacheParticleSystem( "spell_batball_red" );
	PrecacheParticleSystem( "spell_batball_blue" );
	PrecacheParticleSystem( "spell_batball_throw_red" );
	PrecacheParticleSystem( "spell_batball_throw_blue" );
	PrecacheParticleSystem( "spell_batball_impact_red" );
	PrecacheParticleSystem( "spell_batball_impact_blue" );
	
	PrecacheParticleSystem( "spell_pumpkin_mirv_goop_red" );
	PrecacheParticleSystem( "spell_pumpkin_mirv_goop_blue" );
	PrecacheParticleSystem( "spell_skeleton_goop_green" );

	PrecacheParticleSystem( "spellbook_rainbow" );
	PrecacheParticleSystem( "spellbook_major_burning" );
	PrecacheParticleSystem( "spellbook_minor_burning" );
	PrecacheModel( "models/props_mvm/mvm_human_skull_collide.mdl" );
	PrecacheModel( "models/props_lakeside_event/bomb_temp_hat.mdl" );
	PrecacheModel( SPELL_BOXING_GLOVE );
	PrecacheModel( "models/props_halloween/bombonomicon.mdl" ); // bomb head spell
	PrecacheParticleSystem( "halloween_rockettrail" );
	PrecacheParticleSystem( "ExplosionCore_MidAir" );

#ifdef GAME_DLL
	CEyeballBoss::PrecacheEyeballBoss();
	CZombie::PrecacheZombie();
#endif // GAME_DLL

	BaseClass::Precache();
}

//-----------------------------------------------------------------------------
void CTFSpellBook::PrimaryAttack()
{
	// cast spell
	if ( m_flTimeNextSpell > gpGlobals->curtime )
		return;

	CTFPlayer *pPlayer = GetTFPlayerOwner();
	if ( !pPlayer )
		return;

	bool bCastSuccessful = false;
	
	bCastSuccessful = CanCastSpell( pPlayer );

	if ( bCastSuccessful ) 
	{
#ifdef GAME_DLL
		SpeakSpellConceptIfAllowed();
		
		// We need to do this before PrimaryAttack so we use the right spell index
		if ( pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
		{
			CastKartSpell();
			pPlayer->DoAnimationEvent( PLAYERANIMEVENT_CUSTOM_GESTURE, ACT_KART_ACTION_SHOOT );
		}
		else
		{
			CastSpell( pPlayer, m_iSelectedSpellIndex );
			BaseClass::PrimaryAttack();
		}
#endif
		
#ifdef GAME_DLL
		// set a default time cast time if none added
		if ( m_flTimeNextSpell < gpGlobals->curtime )
		{
			m_flTimeNextSpell = gpGlobals->curtime + 0.5f;
		}
#endif // GAME_DLL
	}
#ifdef CLIENT_DLL
	else
	{
		if ( m_flTimeNextErrorSound < gpGlobals->curtime )
		{
			m_flTimeNextErrorSound = gpGlobals->curtime + 0.5f;
			pPlayer->EmitSound( "Player.DenyWeaponSelection" );
		}
	}
#endif // CLIENT_DLL
}

//-----------------------------------------------------------------------------
void CTFSpellBook::ItemBusyFrame( void )
{
#ifdef CLIENT_DLL
	if ( m_hHandEffectWeapon && m_hHandEffect )
		return;

	CTFPlayer *pPlayer = GetTFPlayerOwner();
	if ( !pPlayer )
		return;

	if ( IsFirstPersonView() )
	{
		m_hHandEffectWeapon = pPlayer->GetViewModel();
	}
	else
	{
		m_hHandEffectWeapon = pPlayer;
	}

	if ( !m_hHandEffectWeapon )
		return;

	if ( UsingViewModel() && !g_pClientMode->ShouldDrawViewModel() )
	{
		// Prevent effects when the ViewModel is hidden with r_drawviewmodel=0
		return;
	}

	C_BaseAnimating* pBase = (C_BaseAnimating*)m_hHandEffectWeapon.Get();
	int iAttachment = pBase->C_BaseAnimating::LookupAttachment( "effect_hand_R" );

	// Start the muzzle flash, if a system hasn't already been started.
	if ( iAttachment > 0 )
	{
		const char *pszEffectName = GetHandEffect( GetAttributeContainer()->GetItem(), m_iSelectedSpellIndex >= ARRAYSIZE( g_NormalSpellList ) );
		if ( pszEffectName )
		{
			m_hHandEffect = pBase->ParticleProp()->Create( pszEffectName, PATTACH_POINT_FOLLOW, iAttachment );
		}
	}
	else
	{
		if ( m_hHandEffect )
		{
			m_hHandEffectWeapon->ParticleProp()->StopEmission( m_hHandEffect );
			m_hHandEffectWeapon = NULL;
			m_hHandEffect		= NULL;
		}
	}
#endif
}

//-----------------------------------------------------------------------------
void CTFSpellBook::ItemHolsterFrame( void )
{
#ifdef CLIENT_DLL
	if ( !m_hHandEffectWeapon )
		return;

	// Stop the muzzle flash.
	if ( m_hHandEffect )
	{
		m_hHandEffectWeapon->ParticleProp()->StopEmission( m_hHandEffect );
		m_hHandEffectWeapon = NULL;
		m_hHandEffect		= NULL;
	}
#endif

#ifdef GAME_DLL
	m_bFiredAttack = false;
#endif
}

//-----------------------------------------------------------------------------
void CTFSpellBook::ItemPostFrame( void )
{
	BaseClass::ItemPostFrame();

#ifdef CLIENT_DLL
	// attempt to attack then switch back
	if ( !m_bFiredAttack && m_iSpellCharges > 0 )
	{
		PrimaryAttack();

		if ( m_hHandEffect )
		{
			m_hHandEffectWeapon->ParticleProp()->StopEmission( m_hHandEffect );
			m_hHandEffectWeapon = NULL;
			m_hHandEffect		= NULL;
		}
	}
#endif

#ifdef GAME_DLL
	if ( tf_test_spellindex.GetInt() > -1 )
	{
		SetSelectedSpell( tf_test_spellindex.GetInt() );
	}

	// attempt to attack then switch back
	if ( !m_bFiredAttack && m_iSpellCharges > 0 )
	{
		PrimaryAttack();
		m_bFiredAttack = true;
	}
	else
	{
		if ( m_flTimeNextSpell > gpGlobals->curtime )
			return;

		CTFPlayer *pPlayer = GetTFPlayerOwner();
		if ( !pPlayer )
			return;

		if ( pPlayer->Weapon_Switch( pPlayer->GetLastWeapon() ) )
		{
			if ( m_pStoredLastWpn != NULL && pPlayer->Weapon_CanSwitchTo( m_pStoredLastWpn.Get() ) )
			{
				pPlayer->Weapon_SetLast( m_pStoredLastWpn.Get() );
				m_pStoredLastWpn = NULL;
			}
			else
			{
				pPlayer->Weapon_SetLast( NULL );
			}
			m_bFiredAttack = false;
		}
	}
#endif //GAME_DLL
}

//-----------------------------------------------------------------------------
/* static */ const char* CTFSpellBook::GetHandEffect( CEconItemView *pItem, int iTier )
{
	// if fancy spellbook //1069
	int defIndex = pItem->GetItemDefIndex();
	if ( defIndex == 1069 )
	{
		if ( iTier > 0 )
		{
			return "spellbook_major_burning";
		}
		else
		{
			return "spellbook_minor_burning";
		}
	}		
	else if ( defIndex == 5605 ) // secret diary
	{
		return "spellbook_rainbow";
	}
	else   // else Basic SpellBook
	{
		if ( iTier > 0 )
		{
			return "spellbook_major_fire";
		}
		else
		{
			return "spellbook_minor_fire";
		}
	}
}

//-----------------------------------------------------------------------------
bool CTFSpellBook::HasASpellWithCharges() 
{ 
	return tf_test_spellindex.GetInt() > -1 || m_iSpellCharges > 0 || m_iSelectedSpellIndex == SPELL_UNKNOWN;
}

//-----------------------------------------------------------------------------
bool CTFSpellBook::CanCastSpell( CTFPlayer *pPlayer )
{
	if ( !pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART) && !pPlayer->CanAttack() )
		return false;

	if ( pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_THRILLER ) )
		return false;

	if ( tf_test_spellindex.GetInt() > -1 && tf_test_spellindex.GetInt() < GetTotalSpellCount( pPlayer ) )
		return true;

	return m_iSpellCharges > 0 && m_iSelectedSpellIndex >= 0 && m_iSelectedSpellIndex < GetTotalSpellCount( pPlayer );
}

//-----------------------------------------------------------------------------
void CTFSpellBook::PaySpellCost( CTFPlayer *pPlayer )
{
	m_iSpellCharges--;
}


//-----------------------------------------------------------------------------
void CTFSpellBook::ClearSpell()
{
	m_iSpellCharges = 0;
#ifdef GAME_DLL
	// If rolling for a spell, clear that too
	m_iNextSpell = SPELL_EMPTY;
#endif // GAME_DLL
}
//-----------------------------------------------------------------------------
CBaseEntity *CTFSpellBook::FireJar( CTFPlayer *pPlayer )
{
#ifdef GAME_DLL
	if ( pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
	{
		TossJarThink();
	}
	else
	{
		SetContextThink( &CTFJar::TossJarThink, gpGlobals->curtime + 0.01f, "TOSS_JAR_THINK" );
	}
#endif
	return NULL;
}

#ifdef GAME_DLL
//-----------------------------------------------------------------------------
void CTFSpellBook::TossJarThink( void )
{
	CTFPlayer *pPlayer = GetTFPlayerOwner();
	if ( !pPlayer )
		return;

	// Self casts
	const spell_data_t *pSpellData = GetSpellData( m_iPreviouslyCastSpell );
	if ( !pSpellData )
		return;

	if ( pSpellData->m_eSpellType == SPELL_SELF )
	{
		// Self casts
		if ( TFGameRules() )
		{
			TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( pSpellData->m_iSpellContext, ( pPlayer->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED );
		}
		// Play a sound immediately for self-cast spells
		EmitSound( pSpellData->m_pszCastSound );
		pSpellData->m_pCastSpell( pPlayer );
		return;
	}

	Vector vecForward, vecRight, vecUp;
	AngleVectors( pPlayer->EyeAngles(), &vecForward, &vecRight, &vecUp );

	float fRight = 8.f;
	if ( IsViewModelFlipped() )
	{
		fRight *= -1;
	}
	Vector vecSrc = pPlayer->Weapon_ShootPosition();
	// Make spell toss position at the hand
	vecSrc = vecSrc + (vecUp * -9.0f) + (vecRight * 7.0f) + (vecForward * 3.0f);

	Vector vecVelocity = GetVelocityVector( vecForward, vecRight, vecUp ) * pSpellData->m_flSpeedScale;
	QAngle angForward = pPlayer->EyeAngles();

	// Halloween Hack
	// Eye Angles slighty higher
	if ( pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
	{
		// Add More up for Jar
		angForward = pPlayer->GetAbsAngles();
		if ( pSpellData->m_eSpellType == SPELL_JAR )
		{
			angForward.x -= 10.0f;
		}

		AngleVectors( angForward, &vecForward, &vecRight, &vecUp );
		vecVelocity = vecForward * tf_halloween_kart_rocketspell_speed.GetFloat();
	}

	trace_t trace;	
	Vector vecEye = pPlayer->EyePosition();
	CTraceFilterSimple traceFilter( this, COLLISION_GROUP_NONE );
	UTIL_TraceHull( vecEye, vecSrc, -Vector(8,8,8), Vector(8,8,8), MASK_SOLID_BRUSHONLY, &traceFilter, &trace );

	// If we started in solid, don't let them fire at all
	if ( trace.startsolid )
		return;

	// Play a sound when we actually cast the projectile
	EmitSound( pSpellData->m_pszCastSound );

	switch ( pSpellData->m_eSpellType )
	{
	case SPELL_ROCKET :
		{
			//QAngle angForward;
			//GetProjectileFireSetup( pPlayer, Vector(0,0,0), &vecSrc, &angForward, false );
			CreateSpellRocket( trace.endpos, angForward, vecVelocity, GetAngularImpulse(), pPlayer, GetTFWpnData() );
		}
		break;
	case SPELL_JAR :
		{
			CreateSpellJar( trace.endpos, angForward, vecVelocity, GetAngularImpulse(), pPlayer, GetTFWpnData() );
		}
		break;
	case SPELL_SELF :
		break;
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFSpellBook::CreateSpellRocket( const Vector &position, const QAngle &angles, const Vector &velocity, 
	const AngularImpulse &angVelocity, CBaseCombatCharacter *pOwner, const CTFWeaponInfo &weaponInfo )
{
	const spell_data_t* pSpellData = GetSpellData( m_iPreviouslyCastSpell );
	if ( !pSpellData )
	{
		return;
	}

	ASSERT( pSpellData->m_eSpellType == SPELL_ROCKET );
	CTFProjectile_Rocket *pRocket = static_cast<CTFProjectile_Rocket*>( CBaseEntity::CreateNoSpawn( pSpellData->m_pSpellEntityName, position, angles, pOwner ) );
	if ( pRocket )
	{
		pRocket->SetOwnerEntity( pOwner );
		pRocket->SetLauncher( this ); 

		Vector vForward;
		AngleVectors( angles, &vForward, NULL, NULL );
		pRocket->SetAbsVelocity( vForward * velocity.Length() );

		pRocket->SetDamage( weaponInfo.GetWeaponData(TF_WEAPON_PRIMARY_MODE).m_nDamage );
		pRocket->ChangeTeam( pOwner ? pOwner->GetTeamNumber() : TEAM_UNASSIGNED );

		IPhysicsObject *pPhysicsObject = pRocket->VPhysicsGetObject();
		if ( pPhysicsObject )
		{
			pPhysicsObject->AddVelocity( &velocity, &angVelocity );
		}

		DispatchSpawn( pRocket );
	}
}
//-----------------------------------------------------------------------------
void CTFSpellBook::CreateSpellJar( const Vector &position, const QAngle &angles, const Vector &velocity, 
	const AngularImpulse &angVelocity, CBaseCombatCharacter *pOwner, const CTFWeaponInfo &weaponInfo )
{
	const spell_data_t* pSpellData = GetSpellData( m_iPreviouslyCastSpell );
	if ( !pSpellData )
	{
		return;
	}

	ASSERT( pSpellData->m_eSpellType == SPELL_JAR );
	CTFProjectile_Jar *pGrenade = static_cast<CTFProjectile_Jar*>( CBaseEntity::CreateNoSpawn( pSpellData->m_pSpellEntityName, position, angles, pOwner ) );
	if ( pGrenade )
	{
		// Set the pipebomb mode before calling spawn, so the model & associated vphysics get setup properly.
		pGrenade->SetPipebombMode();
		DispatchSpawn( pGrenade );

		IPhysicsObject *pPhys = pGrenade->VPhysicsGetObject();
		if ( pPhys )
		{
			pPhys->SetMass( 5.0f );
		}

		pGrenade->InitGrenade( velocity, vec3_origin, pOwner, weaponInfo );
		pGrenade->m_flFullDamage = 0;
		pGrenade->ApplyLocalAngularVelocityImpulse( vec3_origin );		
	}
}

//-----------------------------------------------------------------------------
void CTFSpellBook::RollNewSpell( int iTier, bool bForceReroll /*= false*/ )
{
	// do not do anything if we already have a spell for low tier, always roll for high tier
	if ( m_iSpellCharges > 0 && iTier == 0 && !bForceReroll )
		return;

	CTFPlayer *pPlayer = GetTFPlayerOwner();
	if ( !pPlayer )
		return;

	int iNextSpell = SPELL_EMPTY;
	// Halloween 2014
	// This is dumb, make spell lists better somehow
	if ( pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
	{
		iNextSpell = RandomInt( ARRAYSIZE( g_NormalSpellList ) + ARRAYSIZE( g_RareSpellList ), GetTotalSpellCount( pPlayer ) - 1 );
	}
	else if ( iTier == 0 )
	{
		// Doomsday has special spell list
		if ( TFGameRules() && TFGameRules()->GetHalloweenScenario( ) == CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY )
		{
			iNextSpell = g_doomsdayNormalSpellIndexList[ RandomInt( 0, ARRAYSIZE( g_doomsdayNormalSpellIndexList ) - 1 ) ];
		}
		// Helltower has normal spell list
		else if ( TFGameRules() && TFGameRules()->GetHalloweenScenario( ) == CTFGameRules::HALLOWEEN_SCENARIO_HIGHTOWER )
		{
			iNextSpell = RandomInt( 0, ARRAYSIZE( g_NormalSpellList ) - 1 );
		}
		// everyone else uses special list
		else
		{
			iNextSpell = g_generalSpellIndexList[ RandomInt( 0, ARRAYSIZE( g_generalSpellIndexList ) - 1 ) ];
		}
	}
	else // rare spell should not be the else
	{	
		// Doomsday has special spell list
		if ( TFGameRules() && TFGameRules()->GetHalloweenScenario( ) == CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY )
		{
			iNextSpell = g_doomsdayRareSpellIndexList[ RandomInt( 0, ARRAYSIZE( g_doomsdayRareSpellIndexList ) - 1 ) ];
		}
		else
		{
			// g_NavMeshSpells
			// If there's no Nav mesh do not allow the upper range of spells (summons)
			int iIndexReduction = 1;
			if ( ( TheNavMesh == NULL ) || ( TheNavMesh->GetNavAreaCount() <= 0 ) )
			{
				iIndexReduction += g_NavMeshSpells;
			}
			iNextSpell = RandomInt( ARRAYSIZE( g_NormalSpellList ), GetTotalSpellCount( pPlayer ) - 1 );
		}
	}
	
	const float flRollTime = 2.f;

	m_iNextSpell = iNextSpell;
	SetSelectedSpell( SPELL_UNKNOWN );
	SetContextThink( &CTFSpellBook::RollNewSpellFinish, gpGlobals->curtime + flRollTime, "SpellRollFinish" );
}

//-----------------------------------------------------------------------------
void CTFSpellBook::RollNewSpellFinish( void )
{
	SetSelectedSpell( m_iNextSpell );

	if ( m_iNextSpell < 0 )
		return;

	// response rules
	CTFPlayer *pPlayer = GetTFPlayerOwner();
	if ( !pPlayer )
	{
		return;
	}
	int iConcept = MP_CONCEPT_NONE;
	if ( m_iNextSpell < ARRAYSIZE( g_NormalSpellList ) )
	{
		iConcept = MP_CONCEPT_PLAYER_SPELL_PICKUP_COMMON;
	}
	else
	{
		iConcept = MP_CONCEPT_PLAYER_SPELL_PICKUP_RARE;
	}

	if ( iConcept != MP_CONCEPT_NONE )
	{
		pPlayer->SpeakConceptIfAllowed( iConcept );
	}
}

//-----------------------------------------------------------------------------
void CTFSpellBook::SetSelectedSpell( int index )
{
	m_iSelectedSpellIndex = index;

	const spell_data_t *pSpellData = GetSpellData( m_iSelectedSpellIndex );
	m_iSpellCharges = pSpellData ? pSpellData->m_iSpellCharges : 0;

	if ( pSpellData && pSpellData->m_bAutoCast )
	{
		PrimaryAttack();
	}
}
//-----------------------------------------------------------------------------
void CTFSpellBook::SpeakSpellConceptIfAllowed()
{
	CTFPlayer *pPlayer = GetTFPlayerOwner();
	if ( !pPlayer || m_iSpellCharges <= 0 )
		return;

	const spell_data_t *pSpellData = GetSpellData( m_iSelectedSpellIndex );
	if ( pSpellData )
	{
		pPlayer->SpeakConceptIfAllowed( pSpellData->m_iCastContext );
	}
}

//------------------------------------------------------------------------------------------------------------------------------------
// KART FUNCTIONS
//------------------------------------------------------------------------------------------------------------------------------------
void CTFSpellBook::CastKartSpell()
{
#ifdef GAME_DLL
	// cast spell time
	if ( m_flTimeNextSpell > gpGlobals->curtime )
		return;

	CTFPlayer *pPlayer = GetTFPlayerOwner();
	if ( !pPlayer )
		return;

	if ( m_iSpellCharges <= 0 )
	{
		if ( tf_test_spellindex.GetInt() < 0 || tf_test_spellindex.GetInt() > GetTotalSpellCount( pPlayer ) )
			return;
	}
	
	// Save off what we cast for jar think
	PaySpellCost( pPlayer );

	m_iPreviouslyCastSpell = m_iSelectedSpellIndex;
	FireProjectile( pPlayer );

	m_flTimeNextSpell = gpGlobals->curtime + 0.5f;

	// Create one off spell effect in front of the player
	Vector origin = pPlayer->GetAbsOrigin();
	CPVSFilter filter( origin );

	if ( GetTeamNumber() == TF_TEAM_RED )
	{
		TE_TFParticleEffect( filter, 0.0, "spell_cast_wheel_red", origin + Vector( 0, 0, 100 ), vec3_angle, pPlayer, PATTACH_ABSORIGIN_FOLLOW );
	}
	else
	{
		TE_TFParticleEffect( filter, 0.0, "spell_cast_wheel_blue", origin + Vector( 0, 0, 100 ), vec3_angle, pPlayer, PATTACH_ABSORIGIN_FOLLOW );
	}

#endif
}

//-----------------------------------------------------------------------------
// Individual spells
//-----------------------------------------------------------------------------
bool CTFSpellBook::CastSpell( CTFPlayer *pPlayer, int iSpellIndex )
{
	if ( CanCastSpell( pPlayer ) )	
	{
		PaySpellCost( pPlayer );
		const spell_data_t *pSpellData = GetSpellData( m_iSelectedSpellIndex );
		if ( !pSpellData)
			return false;

		if ( IsRareSpell( iSpellIndex ) )
		{
			if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_HIGHTOWER ) )
			{
				pPlayer->AwardAchievement( ACHIEVEMENT_TF_HALLOWEEN_HELLTOWER_RARE_SPELL );
			}
		}

		// Save off what we cast for jar think
		m_iPreviouslyCastSpell = m_iSelectedSpellIndex;

		// Create one off spell effect in front of the player
		Vector origin = pPlayer->GetAbsOrigin();
		CPVSFilter filter( origin );

		//const spell_data_t *pSpellData = GetSpellData( m_iSelectedSpellIndex );
		if ( pSpellData && !FStrEq( pSpellData->m_pSpellUiName, "#TF_Spell_Stealth" ) )	// do NOT create for Stealth
		{
			if ( GetTeamNumber() == TF_TEAM_RED )
			{
				TE_TFParticleEffect( filter, 0.0, "spell_cast_wheel_red", origin + Vector( 0, 0, 100 ), vec3_angle, pPlayer, PATTACH_ABSORIGIN_FOLLOW );
			}
			else
			{
				TE_TFParticleEffect( filter, 0.0, "spell_cast_wheel_blue", origin + Vector( 0, 0, 100 ), vec3_angle, pPlayer, PATTACH_ABSORIGIN_FOLLOW );
			}
		}
		return true;
	}
	return false;
}

#endif

//-----------------------------------------------------------------------------
bool CTFSpellBook::CastSelfHeal( CTFPlayer *pPlayer )
{
#ifdef GAME_DLL
	Vector origin = pPlayer->GetAbsOrigin();
	CPVSFilter filter( origin );
	const char* pszEffectName = pPlayer->GetTeamNumber() == TF_TEAM_RED ? "spell_overheal_red" : "spell_overheal_blue";
	TE_TFParticleEffect( filter, 0.0, pszEffectName, origin, vec3_angle, pPlayer, PATTACH_ABSORIGIN_FOLLOW );
	
	//pPlayer->EmitSound( "BaseExplosionEffect.Sound" );

	// Collect players and cause knockback to enemies
	// Treat this trace exactly like radius damage
	CTraceFilterIgnorePlayers traceFilter( pPlayer, COLLISION_GROUP_PROJECTILE );

	// Splash pee on everyone nearby.
	CBaseEntity *pListOfEntities[32];
	int iEntities = UTIL_EntitiesInSphere( pListOfEntities, 32, origin, 250.0f, FL_CLIENT | FL_FAKECLIENT | FL_NPC );
	for ( int i = 0; i < iEntities; ++i )
	{
		CBaseCombatCharacter *pBaseTarget = NULL;
		CTFPlayer *pTarget = ToTFPlayer( pListOfEntities[i] );
		if ( !pTarget )
		{
			pBaseTarget = dynamic_cast<CBaseCombatCharacter*>( pListOfEntities[i] );
		}
		else
		{
			pBaseTarget = pTarget;
		}

		if ( !pBaseTarget || !pTarget || !pTarget->IsAlive() )
			continue;

		// Do a quick trace to see if there's any geometry in the way.
		trace_t trace;
		UTIL_TraceLine( origin, pBaseTarget->GetAbsOrigin(), ( MASK_SHOT & ~( CONTENTS_HITBOX ) ), &traceFilter, &trace );
		if ( trace.DidHitWorld() )
			continue;

		Vector vecDir = pBaseTarget->WorldSpaceCenter() - origin;
		VectorNormalize( vecDir );

		// help allies
		if ( pBaseTarget->GetTeamNumber() == pPlayer->GetTeamNumber() )
		{
			pBaseTarget->TakeHealth( 50, DMG_GENERIC );
			
			if ( pTarget )
			{
				pTarget->m_Shared.AddCond( TF_COND_INVULNERABLE_USER_BUFF, 1, pPlayer );
				pTarget->m_Shared.AddCond( TF_COND_HALLOWEEN_QUICK_HEAL, 3, pPlayer );
			}
		}
		else // knockback enemies
		{
			if ( pTarget )
			{
				pTarget->ApplyAirBlastImpulse( vecDir * 300.0f );
			}
			else
			{
				pBaseTarget->ApplyAbsVelocityImpulse( vecDir * 300.0f );
			}
		}
	}

#endif
	return true;
}
//-----------------------------------------------------------------------------
bool CTFSpellBook::CastRocketJump( CTFPlayer *pPlayer )
{
#ifdef GAME_DLL
	const float flBlastRadius = 100.f;

	// Set z to zero then add impulse
	// make this proper jumping later
	Vector vel = pPlayer->GetAbsVelocity();
	if ( vel.z < 0 )
	{
		vel.z = 0;
	}
	pPlayer->SetAbsVelocity( vel );

	Vector vForward( 0, 0, 800 );
	pPlayer->ApplyAbsVelocityImpulse( vForward );
		
	const Vector& origin = pPlayer->GetAbsOrigin();
	CPVSFilter filter( origin );
	TE_TFParticleEffect( filter, 0.0, "bombinomicon_burningdebris", origin, vec3_angle );
	TE_TFParticleEffect( filter, 0.0, "heavy_ring_of_fire", origin, vec3_angle );
	DispatchParticleEffect( "rocketjump_smoke", PATTACH_POINT_FOLLOW, pPlayer, "foot_L" );
	DispatchParticleEffect( "rocketjump_smoke", PATTACH_POINT_FOLLOW, pPlayer, "foot_R" );

	// Give a little health to compensate for fall damage
	pPlayer->TakeHealth( 25, DMG_GENERIC );

	// Collect players and cause knockback to enemies
	// Treat this trace exactly like radius damage
	CTraceFilterIgnorePlayers traceFilter( pPlayer, COLLISION_GROUP_PROJECTILE );

	// Splash pee on everyone nearby.
	CBaseEntity *pListOfEntities[32];
	int iEntities = UTIL_EntitiesInSphere( pListOfEntities, 32, origin, flBlastRadius, FL_CLIENT | FL_FAKECLIENT | FL_NPC );
	for ( int i = 0; i < iEntities; ++i )
	{
		CBaseCombatCharacter *pBaseTarget = NULL;
		CTFPlayer *pTarget = ToTFPlayer( pListOfEntities[i] );
		if ( !pTarget )
		{
			pBaseTarget = dynamic_cast<CBaseCombatCharacter*>( pListOfEntities[i] );
		}
		else
		{
			pBaseTarget = pTarget;
		}

		if ( !pBaseTarget || !pTarget || !pTarget->IsAlive() || pBaseTarget->GetTeamNumber() == pPlayer->GetTeamNumber() )
			continue;

		// Do a quick trace to see if there's any geometry in the way.
		trace_t trace;
		UTIL_TraceLine( origin, pBaseTarget->GetAbsOrigin(), ( MASK_SHOT & ~( CONTENTS_HITBOX ) ), &traceFilter, &trace );
		if ( trace.DidHitWorld() )
			continue;

		Vector vecDir = pBaseTarget->WorldSpaceCenter() - origin;
		VectorNormalize( vecDir );

		pBaseTarget->RemoveFlag( FL_ONGROUND );
		
		if ( pTarget )
		{
			pTarget->ApplyAirBlastImpulse( vecDir * 800.0f );
		}
		else
		{
			pBaseTarget->ApplyAbsVelocityImpulse( vecDir * 800.0f );
		}
	}

	CTakeDamageInfo info;
	info.SetAttacker( pPlayer );
	info.SetInflictor( pPlayer ); 
	info.SetDamage( 20.f );
	info.SetDamageCustom( TF_DMG_CUSTOM_SPELL_BLASTJUMP );
	info.SetDamagePosition( origin );
	info.SetDamageType( DMG_BLAST );

	CTFRadiusDamageInfo radiusinfo( &info, origin, flBlastRadius, pPlayer );
	TFGameRules()->RadiusDamage( radiusinfo );

#endif
	return true;
}

//-----------------------------------------------------------------------------
bool CTFSpellBook::CastSelfSpeedBoost( CTFPlayer *pPlayer )
{
#ifdef GAME_DLL
	// Give a little health
	pPlayer->TakeHealth( 100, DMG_GENERIC );

	pPlayer->m_Shared.AddCond( TF_COND_HALLOWEEN_TINY, 20, pPlayer );
	pPlayer->m_Shared.AddCond( TF_COND_HALLOWEEN_SPEED_BOOST, 20, pPlayer );
#endif
	return true;
}

//-----------------------------------------------------------------------------
bool CTFSpellBook::CastSelfStealth( CTFPlayer *pPlayer )
{
#ifdef GAME_DLL
	// Grant a small amount of health
	pPlayer->TakeHealth( 40, DMG_GENERIC );
	pPlayer->m_Shared.AddCond( TF_COND_STEALTHED_USER_BUFF, 8, pPlayer );
#endif
	return true;
}

//********************************************************************************************************************************
//-----------------------------------------------------------------------------
// Kart Self Spells
//-----------------------------------------------------------------------------
bool CTFSpellBook::CastKartRocketJump( CTFPlayer *pPlayer )
{
#ifdef GAME_DLL
	const float flBlastRadius = 250.f;

	// Set z to zero then add impulse
	// make this proper jumping later
	Vector vel = pPlayer->GetAbsVelocity();
	if ( vel.z < 0 )
	{
		vel.z = 0;
	}
	pPlayer->SetAbsVelocity( vel );

	Vector vForward( 0, 0, 1200 );
	pPlayer->ApplyAbsVelocityImpulse( vForward );

	const Vector& origin = pPlayer->GetAbsOrigin();
	CPVSFilter filter( origin );
	TE_TFParticleEffect( filter, 0.0, "bombinomicon_burningdebris", origin, vec3_angle );
	TE_TFParticleEffect( filter, 0.0, "heavy_ring_of_fire", origin, vec3_angle );
	DispatchParticleEffect( "rocketjump_smoke", PATTACH_POINT_FOLLOW, pPlayer, "foot_L" );
	DispatchParticleEffect( "rocketjump_smoke", PATTACH_POINT_FOLLOW, pPlayer, "foot_R" );

	// Give a little health to compensate for fall damage
	//pPlayer->TakeHealth( 25, DMG_GENERIC );
	pPlayer->RemoveFlag( FL_ONGROUND );
	pPlayer->m_Shared.AddCond( TF_COND_PARACHUTE_DEPLOYED );

	// Collect players and cause knockback to enemies
	// Treat this trace exactly like radius damage
	CTraceFilterIgnorePlayers traceFilter( pPlayer, COLLISION_GROUP_PROJECTILE );

	// Trace entity radius
	CBaseEntity *pListOfEntities[32];
	int iEntities = UTIL_EntitiesInSphere( pListOfEntities, 32, origin, flBlastRadius, FL_CLIENT | FL_FAKECLIENT | FL_NPC );
	for ( int i = 0; i < iEntities; ++i )
	{
		CTFPlayer *pTarget = ToTFPlayer( pListOfEntities[i] );
		
		if ( !pTarget || !pTarget->IsAlive() || pTarget->GetTeamNumber() == pPlayer->GetTeamNumber() )
			continue;

		// Do a quick trace to see if there's any geometry in the way.
		trace_t trace;
		UTIL_TraceLine( origin, pTarget->GetAbsOrigin(), ( MASK_SHOT & ~( CONTENTS_HITBOX ) ), &traceFilter, &trace );
		if ( trace.DidHitWorld() )
			continue;

		Vector vecDir = pTarget->WorldSpaceCenter() - origin;
		vecDir.NormalizeInPlace();
		vecDir.z += 0.5f;
		pTarget->AddHalloweenKartPushEvent( pPlayer, pPlayer->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ), pPlayer->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ), vecDir * tf_halloween_kart_normal_speed.GetFloat(), 30.0f );
	}
#endif
	return true;
}

bool CTFSpellBook::CastKartUber( CTFPlayer *pPlayer )
{
#ifdef GAME_DLL
	Vector origin = pPlayer->GetAbsOrigin();
	CPVSFilter filter( origin );
	const char* pszEffectName = pPlayer->GetTeamNumber() == TF_TEAM_RED ? "spell_overheal_red" : "spell_overheal_blue";
	TE_TFParticleEffect( filter, 0.0, pszEffectName, origin, vec3_angle, pPlayer, PATTACH_ABSORIGIN_FOLLOW );

	//pPlayer->EmitSound( "BaseExplosionEffect.Sound" );

	// Collect players and cause knockback to enemies
	// Treat this trace exactly like radius damage
	CTraceFilterIgnorePlayers traceFilter( pPlayer, COLLISION_GROUP_PROJECTILE );

	pPlayer->m_Shared.AddCond( TF_COND_INVULNERABLE_USER_BUFF, 7, pPlayer );
	pPlayer->AddKartDamage( -50 ); //Heal
#endif
	return true;
}


bool CTFSpellBook::CastKartBombHead( CTFPlayer *pPlayer )
{
#ifdef GAME_DLL
	pPlayer->m_Shared.AddCond( TF_COND_HALLOWEEN_BOMB_HEAD, 10, pPlayer );
#endif
	return true;
}

//************************************************************************************************************************
// Spell Projectiles
//************************************************************************************************************************
class CTFProjectile_SpellFireball : public CTFProjectile_Rocket
{
public:
	DECLARE_CLASS( CTFProjectile_SpellFireball, CTFProjectile_Rocket );
	DECLARE_NETWORKCLASS();

	virtual int			GetWeaponID( void ) const			{ return TF_WEAPON_SPELLBOOK_PROJECTILE; }
	virtual float		GetDamageRadius() const				{ return 200.0f; }
	virtual int			GetCustomDamageType() const OVERRIDE	{ return m_bIsMeteor ? TF_DMG_CUSTOM_SPELL_METEOR : TF_DMG_CUSTOM_SPELL_FIREBALL; }
	virtual bool		IsDeflectable() OVERRIDE { return false; }

	void				SetMeteor( bool bIsMeteor ) { m_bIsMeteor = bIsMeteor; }

	CTFProjectile_SpellFireball()
	{
		m_bIsMeteor = false;
#ifdef GAME_DLL
		//m_pszExplodeParticleName = "ExplosionCore_buildings";
		m_pszExplodeParticleName = "bombinomicon_burningdebris";
#endif // GAME_DLL
	}
	
#ifdef GAME_DLL
	virtual void Spawn() OVERRIDE
	{
		SetModelScale( 0.01f );
		BaseClass::Spawn();
	}
	virtual int UpdateTransmitState() OVERRIDE { return SetTransmitState( FL_EDICT_PVSCHECK ); }

	virtual void RocketTouch( CBaseEntity *pOther ) OVERRIDE
	{
		Assert( pOther );
		if ( !pOther->IsSolid() || pOther->IsSolidFlagSet( FSOLID_VOLUME_CONTENTS ) )
			return;

		if ( pOther->GetParent() == GetOwnerEntity() )
			return;

		// Handle hitting skybox (disappear).
		const trace_t *pTrace = &CBaseEntity::GetTouchTrace();

		if( pTrace->surface.flags & SURF_SKY )
		{
			UTIL_Remove( this );
			return;
		}

		// pass through ladders
		if( pTrace->surface.flags & CONTENTS_LADDER )
			return;

		Explode( pTrace );

		UTIL_Remove( this );
	}

	virtual void Explode( const trace_t *pTrace )
	{
		SetModelName( NULL_STRING );//invisible
		AddSolidFlags( FSOLID_NOT_SOLID );

		m_takedamage = DAMAGE_NO;

		// Pull out of the wall a bit.
		if ( pTrace->fraction != 1.0 )
		{
			SetAbsOrigin( pTrace->endpos + ( pTrace->plane.normal * 1.0f ) );
		}

		CTFPlayer *pThrower = ToTFPlayer( GetOwnerEntity() );
		if ( pThrower )
		{
			const Vector &vecOrigin = GetAbsOrigin();

			// Any effects from the initial explosion
			if ( InitialExplodeEffects( pThrower, pTrace ) )
			{
				// Particle
				if ( GetExplodeEffectParticle() )
				{	
					CPVSFilter filter( vecOrigin );
					TE_TFParticleEffect( filter, 0.0, GetExplodeEffectParticle(), vecOrigin, vec3_angle );
				}

				// Sounds
				if ( GetExplodeEffectSound() )
				{
					EmitSound( GetExplodeEffectSound() );
				}

				// Treat this trace exactly like radius damage
				CTraceFilterIgnorePlayers traceFilter( pThrower, COLLISION_GROUP_PROJECTILE );

				// Splash pee on everyone nearby.
				CBaseEntity *pListOfEntities[32];
				int iEntities = UTIL_EntitiesInSphere( pListOfEntities, 32, vecOrigin, GetDamageRadius(), FL_CLIENT | FL_FAKECLIENT | FL_NPC );
				for ( int i = 0; i < iEntities; ++i )
				{
					CBaseCombatCharacter *pBasePlayer = NULL;
					CTFPlayer *pPlayer = ToTFPlayer( pListOfEntities[i] );
					if ( !pPlayer )
					{
						pBasePlayer = dynamic_cast<CBaseCombatCharacter*>( pListOfEntities[i] );
					}
					else
					{
						pBasePlayer = pPlayer;
					}

					if ( !pBasePlayer || !pPlayer || !pPlayer->IsAlive() )
						continue;

					// Do a quick trace to see if there's any geometry in the way.
					trace_t trace;
					UTIL_TraceLine( vecOrigin, pPlayer->WorldSpaceCenter(), ( MASK_SHOT & ~( CONTENTS_HITBOX ) ), &traceFilter, &trace );
					//debugoverlay->AddLineOverlay( vecOrigin, pPlayer->WorldSpaceCenter(), 255, 0, 0, false, 10 );
					if ( trace.DidHitWorld() )
						continue;

					// Effects on the individual players
					ExplodeEffectOnTarget( pThrower, pPlayer, pBasePlayer );
				}

				if ( TFGameRules() )
				{
					TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_PLAYER_SPELL_FIREBALL, ( pThrower->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED );
				}

				CTakeDamageInfo info;
				info.SetAttacker( pThrower );
				info.SetInflictor( this ); 
				info.SetWeapon( GetLauncher() );
				info.SetDamage( 10.f );
				info.SetDamageCustom( GetCustomDamageType() );
				info.SetDamagePosition( vecOrigin );
				info.SetDamageType( DMG_BLAST );

				CTFRadiusDamageInfo radiusinfo( &info, vecOrigin, GetDamageRadius(), pThrower );
				TFGameRules()->RadiusDamage( radiusinfo );
			}
			else
			{
				pThrower->EmitSound( "Player.DenyWeaponSelection" );
			}
		}

		// Grenade remove
		//SetContextThink( &CBaseGrenade::SUB_Remove, gpGlobals->curtime, "RemoveThink" );

		// Remove the rocket.
		UTIL_Remove( this );
		
		SetTouch( NULL );
		AddEffects( EF_NODRAW );
		SetAbsVelocity( vec3_origin );
	}	

	virtual const char *GetProjectileModelName( void ) { return ""; } // We dont have a model by default, and that's OK
	
	virtual bool		InitialExplodeEffects( CTFPlayer *pThrower, const trace_t *pTrace ) { return true; }
	virtual void		ExplodeEffectOnTarget( CTFPlayer *pThrower, CTFPlayer *pTarget, CBaseCombatCharacter *pBaseTarget )
	{
		if ( pBaseTarget->GetTeamNumber() == GetTeamNumber() )
			return;

		if ( pTarget )
		{
			if ( pTarget->m_Shared.IsInvulnerable() )
				return;

			if ( pTarget->m_Shared.InCond( TF_COND_PHASE ) || pTarget->m_Shared.InCond( TF_COND_PASSTIME_INTERCEPTION ) )
				return;

			pTarget->m_Shared.SelfBurn( 5.0f );
		}

		const trace_t *pTrace = &CBaseEntity::GetTouchTrace();
		trace_t *pNewTrace = const_cast<trace_t*>( pTrace );

		CBaseEntity *pInflictor = GetLauncher();
		CTakeDamageInfo info;
		info.SetAttacker( pThrower );
		info.SetInflictor( this ); 
		info.SetWeapon( pInflictor );
		info.SetDamage( 100.f );
		info.SetDamageCustom( GetCustomDamageType() );
		info.SetDamagePosition( GetAbsOrigin() );
		info.SetDamageType( DMG_BURN );

		// Hurt 'em.
		Vector dir;
		AngleVectors( GetAbsAngles(), &dir );
		pBaseTarget->DispatchTraceAttack( info, dir, pNewTrace );
		ApplyMultiDamage();


		Vector vecDir = pBaseTarget->WorldSpaceCenter() - GetAbsOrigin();
		VectorNormalize( vecDir );
		vecDir.z = 0.1f;

		if ( pTarget )
		{
			pTarget->ApplyAirBlastImpulse( vecDir * 5 );
		}
	}

	virtual const char *GetExplodeEffectParticle() const	{ return m_pszExplodeParticleName; }
	void SetExplodeParticleName( const char *pszName )		{ m_pszExplodeParticleName = pszName; }
	virtual const char *GetExplodeEffectSound()	const		{ return "Halloween.spell_fireball_impact"; }
#endif

#ifdef CLIENT_DLL
	virtual const char *GetTrailParticleName( void )
	{ 
		return GetTeamNumber() == TF_TEAM_BLUE ? "spell_fireball_small_blue" : "spell_fireball_small_red";
	}
#endif

private:
	bool m_bIsMeteor;

#ifdef GAME_DLL
	const char *m_pszExplodeParticleName;
#endif // GAME_DLL
};

// Fireball
IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SpellFireball, DT_TFProjectile_SpellFireball )
	BEGIN_NETWORK_TABLE( CTFProjectile_SpellFireball, DT_TFProjectile_SpellFireball )
	END_NETWORK_TABLE()

LINK_ENTITY_TO_CLASS( tf_projectile_spellfireball, CTFProjectile_SpellFireball );
PRECACHE_WEAPON_REGISTER( tf_projectile_spellfireball);


// *************************************************************************************************************************
class CTFProjectile_SpellBats : public CTFProjectile_Jar
{
public:
	DECLARE_CLASS( CTFProjectile_SpellBats, CTFProjectile_Jar );
	DECLARE_NETWORKCLASS();

	virtual int			GetWeaponID( void ) const			{ return TF_WEAPON_SPELLBOOK_PROJECTILE; }
	virtual float		GetDamageRadius() const				{ return 250.0f; }
	virtual float		GetModelScale() const				{ return 0.01f; }
	virtual int			GetCustomDamageType() const OVERRIDE	{ return TF_DMG_CUSTOM_SPELL_BATS; }
	virtual bool		IsDeflectable() OVERRIDE { return false; }

#ifdef GAME_DLL
	virtual void Spawn( void )
	{
		SetModelScale( GetModelScale() );
		BaseClass::Spawn();
	}

	//-----------------------------------------------------------------------------
	// Lightning Ball / Base
	//-----------------------------------------------------------------------------
	virtual void Explode( trace_t *pTrace, int bitsDamageType ) OVERRIDE
	{
		SetModelName( NULL_STRING );//invisible
		AddSolidFlags( FSOLID_NOT_SOLID );

		m_takedamage = DAMAGE_NO;

		// Pull out of the wall a bit.
		if ( pTrace->fraction != 1.0 )
		{
			SetAbsOrigin( pTrace->endpos + ( pTrace->plane.normal * 1.0f ) );
		}

		CTFPlayer *pThrower = ToTFPlayer( GetThrower() );

		if ( pThrower )
		{
			const Vector& vecOrigin = GetAbsOrigin();

			// Any effects from the initial explosion
			if ( InitialExplodeEffects( pThrower, pTrace ) )
			{
				// Particle
				if ( GetExplodeEffectParticle() )
				{	
					CPVSFilter filter( vecOrigin );
					TE_TFParticleEffect( filter, 0.0, GetExplodeEffectParticle(), vecOrigin, vec3_angle );
				}

				// Sounds
				if ( GetExplodeEffectSound() )
				{
					EmitSound( GetExplodeEffectSound() );
				}

				// Treat this trace exactly like radius damage
				CTraceFilterIgnorePlayers traceFilter( pThrower, COLLISION_GROUP_PROJECTILE );

				// Splash pee on everyone nearby.
				CBaseEntity *pListOfEntities[32];
				int iEntities = UTIL_EntitiesInSphere( pListOfEntities, 32, vecOrigin, GetDamageRadius(), FL_CLIENT | FL_FAKECLIENT | FL_NPC );
				for ( int i = 0; i < iEntities; ++i )
				{
					CBaseCombatCharacter *pBasePlayer = NULL;
					CTFPlayer *pPlayer = ToTFPlayer( pListOfEntities[i] );
					if ( !pPlayer )
					{
						pBasePlayer = dynamic_cast<CBaseCombatCharacter*>( pListOfEntities[i] );
					}
					else
					{
						pBasePlayer = pPlayer;
					}

					if ( !pBasePlayer || !pBasePlayer->IsAlive() )
						continue;

					// Do a quick trace to see if there's any geometry in the way.
					trace_t trace;
					UTIL_TraceLine( GetAbsOrigin(), pBasePlayer->GetAbsOrigin(), ( MASK_SHOT & ~( CONTENTS_HITBOX ) ), &traceFilter, &trace );
					if ( trace.DidHitWorld() )
						continue;

					// Effects on the individual players
					ExplodeEffectOnTarget( pThrower, pPlayer, pBasePlayer );
				}

				if ( TFGameRules() )
				{
					TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_PLAYER_SPELL_MERASMUS_ZAP, ( pThrower->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED );
				}

				ApplyBlastDamage( pThrower, vecOrigin );
			}
			else
			{
				pThrower->EmitSound( "Player.DenyWeaponSelection" );
			}
		}

		SetContextThink( &CBaseGrenade::SUB_Remove, gpGlobals->curtime, "RemoveThink" );
		SetTouch( NULL );

		AddEffects( EF_NODRAW );
		SetAbsVelocity( vec3_origin );
	}

	virtual void ApplyBlastDamage( CTFPlayer *pThrower, Vector vecOrigin )
	{
		CTakeDamageInfo info;
		info.SetAttacker( pThrower );
		info.SetInflictor( this ); 
		info.SetWeapon( GetLauncher() );
		info.SetDamage( 10.f );
		info.SetDamageCustom( GetCustomDamageType() );
		info.SetDamagePosition( vecOrigin );
		info.SetDamageType( DMG_BLAST );

		CTFRadiusDamageInfo radiusinfo( &info, vecOrigin, GetDamageRadius(), pThrower );
		TFGameRules()->RadiusDamage( radiusinfo );
	}

	virtual bool InitialExplodeEffects( CTFPlayer *pThrower, const trace_t *pTrace )
	{
		// Added Particle
		Vector vecOrigin = GetAbsOrigin();
		// Particle
		CPVSFilter filter( vecOrigin );
		TE_TFExplosion( filter, 0.0f, vecOrigin, pTrace->plane.normal, TF_WEAPON_GRENADE_PIPEBOMB, kInvalidEHandleExplosion, -1, SPECIAL1, INVALID_STRING_INDEX );

		return true;
	}

	virtual void ExplodeEffectOnTarget( CTFPlayer *pThrower, CTFPlayer *pTarget, CBaseCombatCharacter *pBaseTarget )
	{
		if ( pBaseTarget->GetTeamNumber() == GetTeamNumber() )
			return;

		if ( pTarget )
		{
			if ( pTarget->m_Shared.IsInvulnerable() )
				return;

			if ( pTarget->m_Shared.InCond( TF_COND_PHASE ) || pTarget->m_Shared.InCond( TF_COND_PASSTIME_INTERCEPTION ) )
				return;

			// Stun the target
			pTarget->m_Shared.StunPlayer( 0.5, 0.5, TF_STUN_MOVEMENT, pThrower );
		}

		Vector vecDir = pBaseTarget->WorldSpaceCenter() - GetAbsOrigin();
		VectorNormalize( vecDir );

		if ( pTarget )
		{
			pTarget->ApplyAirBlastImpulse( vecDir * 200.0f + Vector(0, 0, 800 ) );
			const char* pszEffectName = GetTeamNumber() == TF_TEAM_RED ? "spell_batball_red" : "spell_batball_blue";
			DispatchParticleEffect( pszEffectName, PATTACH_ABSORIGIN_FOLLOW, pTarget );

			CTFSpellBook *pSpellBook = dynamic_cast< CTFSpellBook* >( pThrower->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) );
			if ( pSpellBook )
			{
				pTarget->m_Shared.MakeBleed( pThrower, pSpellBook, 3.0f );
			}
		}
		else
		{
			pBaseTarget->ApplyAbsVelocityImpulse( vecDir * 1000.0f );
		}

		const trace_t *pTrace = &CBaseEntity::GetTouchTrace();
		trace_t *pNewTrace = const_cast<trace_t*>( pTrace );

		CBaseEntity *pInflictor = GetLauncher();
		CTakeDamageInfo info;
		info.SetAttacker( pThrower );
		info.SetInflictor( this ); 
		info.SetWeapon( pInflictor );
		info.SetDamage( 40 );
		info.SetDamageCustom( GetCustomDamageType() );
		info.SetDamagePosition( GetAbsOrigin() );
		info.SetDamageType( DMG_BURN );

		// Hurt 'em.
		Vector dir;
		AngleVectors( GetAbsAngles(), &dir );
		pBaseTarget->DispatchTraceAttack( info, dir, pNewTrace );
		ApplyMultiDamage();
	}

	virtual const char *GetExplodeEffectParticle() const	{ return GetTeamNumber() == TF_TEAM_RED ? "spell_batball_impact_red" : "spell_batball_impact_blue"; }
	virtual const char *GetExplodeEffectSound()	const		{ return "Halloween.spell_bat_impact"; }
#endif

#ifdef CLIENT_DLL
	virtual const char*	GetTrailParticleName( void ) { return GetTeamNumber() == TF_TEAM_RED ? "spell_batball_throw_red" : "spell_batball_throw_blue"; }
#endif
};

IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SpellBats, DT_TFProjectile_SpellBats )
	BEGIN_NETWORK_TABLE( CTFProjectile_SpellBats, DT_TFProjectile_SpellBats )
	END_NETWORK_TABLE()

	LINK_ENTITY_TO_CLASS( tf_projectile_spellbats, CTFProjectile_SpellBats );
PRECACHE_WEAPON_REGISTER( tf_projectile_spellbats );

// *************************************************************************************************************************
class CTFProjectile_SpellSpawnZombie : public CTFProjectile_SpellBats
{
public:
	DECLARE_CLASS( CTFProjectile_SpellSpawnZombie, CTFProjectile_SpellBats );
	DECLARE_NETWORKCLASS();

	CTFProjectile_SpellSpawnZombie()
	{
#ifdef GAME_DLL
		m_skeletonType = 0;
#endif // GAME_DLL
	}

	virtual float		GetDamageRadius()	const			{ return 1.0f; }
	virtual void		SetCustomPipebombModel()			{ SetModel( "models/props_mvm/mvm_human_skull_collide.mdl" ); }
	virtual float		GetModelScale() const				{ return 1.0f; }
	virtual int			GetCustomDamageType() const OVERRIDE { return TF_DMG_CUSTOM_SPELL_SKELETON; }

#ifdef GAME_DLL

	virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) { }
	virtual void PipebombTouch( CBaseEntity *pOther ) { }

	virtual void Explode( trace_t *pTrace, int bitsDamageType ) OVERRIDE
	{
		// no owner? spawn skeletons anyways
		if ( !GetThrower() )
		{
			InitialExplodeEffects( NULL, pTrace );
			
			// Particle
			if ( GetExplodeEffectParticle() )
			{	
				CPVSFilter filter( GetAbsOrigin() );
				TE_TFParticleEffect( filter, 0.0, GetExplodeEffectParticle(), GetAbsOrigin(), vec3_angle );
			}

			// Sounds
			if ( GetExplodeEffectSound() )
			{
				EmitSound( GetExplodeEffectSound() );
			}

			SetContextThink( &CBaseGrenade::SUB_Remove, gpGlobals->curtime, "RemoveThink" );
			SetTouch( NULL );

			AddEffects( EF_NODRAW );
			SetAbsVelocity( vec3_origin );

			return;
		}

		BaseClass::Explode( pTrace, bitsDamageType );
	}

	virtual bool InitialExplodeEffects( CTFPlayer *pThrower, const trace_t *pTrace ) OVERRIDE
	{
		// Pull in a little
		Vector vSpawnPoint = ( pTrace->endpos + ( pTrace->plane.normal * 2.0f ) );
		CZombie::SpawnAtPos( vSpawnPoint, 30.0f, GetTeamNumber(), pThrower, (CZombie::SkeletonType_t)m_skeletonType );
		return true;
	}
	virtual void		ExplodeEffectOnTarget( CTFPlayer *pThrower, CTFPlayer *pTarget, CBaseCombatCharacter *pBaseTarget ) { }
	virtual const char *GetExplodeEffectParticle() const
	{
		if ( GetTeamNumber() == TF_TEAM_HALLOWEEN )
		{
			return "spell_skeleton_goop_green";
		}

		return GetTeamNumber() == TF_TEAM_RED ? "spell_pumpkin_mirv_goop_red" : "spell_pumpkin_mirv_goop_blue";
	}
	virtual const char *GetExplodeEffectSound()	const	{ return "Cleaver.ImpactFlesh"; }

	void SetSkeletonType ( int iType ) { m_skeletonType = iType; }

	int m_skeletonType;
#endif

#ifdef CLIENT_DLL
	virtual const char*			GetTrailParticleName( void ) { return "unusual_bubbles_green_fumes"; }
#endif
};

// Spawn Zombie
IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SpellSpawnZombie, DT_TFProjectile_SpellSpawnZombie )
	BEGIN_NETWORK_TABLE( CTFProjectile_SpellSpawnZombie, DT_TFProjectile_SpellSpawnZombie )
	END_NETWORK_TABLE()

	LINK_ENTITY_TO_CLASS( tf_projectile_spellspawnzombie, CTFProjectile_SpellSpawnZombie );
PRECACHE_WEAPON_REGISTER( tf_projectile_spellspawnzombie );

#ifdef GAME_DLL

CBaseEntity* CreateSpellSpawnZombie( CBaseCombatCharacter *pCaster, const Vector& vSpawnPosition, int nSkeletonType )
{
	Vector offset = RandomVector( -32, 32 );
	offset.z = 16;
	CTFProjectile_SpellSpawnZombie *pGrenade = static_cast<CTFProjectile_SpellSpawnZombie*>( CBaseEntity::CreateNoSpawn( "tf_projectile_spellspawnzombie", vSpawnPosition + offset, RandomAngle( 0, 360 ), pCaster ) );
	if ( pGrenade )
	{
		// Set the pipebomb mode before calling spawn, so the model & associated vphysics get setup properly.
		pGrenade->SetPipebombMode();
		DispatchSpawn( pGrenade );

		Vector vecImpulse = RandomVector( -1, 1 );
		VectorNormalize( vecImpulse );
		vecImpulse.z = RandomFloat( 1.0f, 1.6f );
		Vector vecVelocity = vecImpulse * RandomFloat( 250.0f, 300.0f );

		AngularImpulse angVelocity = AngularImpulse( 300, 300, 100 );
		pGrenade->InitGrenade( vecVelocity, angVelocity, pCaster, 0, 0 );
		pGrenade->ApplyLocalAngularVelocityImpulse( angVelocity );
		pGrenade->SetDetonateTimerLength( RandomFloat( 2.f, 2.5f ) );

		pGrenade->SetSkeletonType( nSkeletonType );
	}

	return pGrenade;
}

#endif

// *************************************************************************************************************************
class CTFProjectile_SpellSpawnHorde : public CTFProjectile_SpellBats
{
public:
	DECLARE_CLASS( CTFProjectile_SpellSpawnHorde, CTFProjectile_SpellBats );
	DECLARE_NETWORKCLASS();

	virtual float		GetDamageRadius()	const			{ return 1.0f; }
	virtual void		SetCustomPipebombModel()			{ SetModel( "models/props_mvm/mvm_human_skull_collide.mdl" ); }
	virtual float		GetModelScale() const				{ return 1.0f; }
	virtual int			GetCustomDamageType() const OVERRIDE { return TF_DMG_CUSTOM_SPELL_SKELETON; }

#ifdef GAME_DLL
	//virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) { }
	//virtual void PipebombTouch( CBaseEntity *pOther ) { }

	virtual bool InitialExplodeEffects( CTFPlayer *pThrower, const trace_t *pTrace ) OVERRIDE
	{
		// Spawn a tonne of extra skelatone grenades (mirv style)

		CTFSpellBook *pSpellBook = dynamic_cast< CTFSpellBook* >( pThrower->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) );
		if ( !pSpellBook )
			return false;

		for ( int i = 0; i < 3; i++ )
		{
			Vector offset = RandomVector( -32, 32 );
			offset.z = 16;
			CreateSpellSpawnZombie( pThrower, GetAbsOrigin(), 0 );
		}

		if ( TFGameRules() )
		{
			TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_PLAYER_SPELL_SKELETON_HORDE, ( pThrower->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED );
		}

		return true;
	}
	virtual void		ExplodeEffectOnTarget( CTFPlayer *pThrower, CTFPlayer *pTarget, CBaseCombatCharacter *pBaseTarget ) { }
	virtual const char *GetExplodeEffectParticle() const	{ return GetTeamNumber() == TF_TEAM_RED ? "spell_pumpkin_mirv_goop_red" : "spell_pumpkin_mirv_goop_blue"; }
	virtual const char *GetExplodeEffectSound()	const	{ return "Halloween.spell_skeleton_horde_rise"; }
#endif

#ifdef CLIENT_DLL
	virtual const char*			GetTrailParticleName( void ) { return "unusual_bubbles_green_fumes"; }
#endif
};

// Spawn Horde of Skels
IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SpellSpawnHorde, DT_TFProjectile_SpellSpawnHorde )
	BEGIN_NETWORK_TABLE( CTFProjectile_SpellSpawnHorde, DT_TFProjectile_SpellSpawnHorde )
	END_NETWORK_TABLE()

	LINK_ENTITY_TO_CLASS( tf_projectile_spellspawnhorde, CTFProjectile_SpellSpawnHorde );
PRECACHE_WEAPON_REGISTER( tf_projectile_spellspawnhorde);


// *************************************************************************************************************************
class CTFProjectile_SpellPumpkin : public CTFProjectile_SpellBats
{
public:
	DECLARE_CLASS( CTFProjectile_SpellPumpkin, CTFProjectile_SpellBats );
	DECLARE_NETWORKCLASS();

	CTFProjectile_SpellPumpkin ()
	{
#ifdef GAME_DLL
		m_flImpactTime = gpGlobals->curtime + 1.0f;
#endif
	}
	virtual float		GetDamageRadius()	const			{ return 1.0f; }
	virtual void		SetCustomPipebombModel()			{ SetModel( "models/weapons/w_models/w_cannonball.mdl" ); }
	virtual float		GetModelScale() const				{ return 0.75f; }
	virtual int			GetCustomDamageType() const OVERRIDE	{ return TF_DMG_CUSTOM_SPELL_MIRV; }
#ifdef GAME_DLL
	// ignore collisions early in its lifetime
	virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) 
	{
		if ( gpGlobals->curtime < m_flImpactTime )
			return;
		BaseClass::VPhysicsCollision( index, pEvent );
	}
	virtual void PipebombTouch( CBaseEntity *pOther )
	{
		if ( gpGlobals->curtime < m_flImpactTime )
			return;
		BaseClass::PipebombTouch( pOther );
	}

	virtual void ApplyBlastDamage ( CTFPlayer *pThrower, Vector vecOrigin ) { }

	virtual bool InitialExplodeEffects( CTFPlayer *pThrower, const trace_t *pTrace ) OVERRIDE
	{
		// Spawn a pumkin bomb here
		// Set the angles to what I want
		QAngle angle(0, RandomFloat( 0, 360 ) ,0);
		CTFPumpkinBomb *pGrenade = static_cast<CTFPumpkinBomb*>( CBaseEntity::CreateNoSpawn( "tf_pumpkin_bomb", GetAbsOrigin(), angle, NULL ) );
		if ( pGrenade )
		{
			pGrenade->SetInitParams( 0.60, 80.0f, 200.0f, GetTeamNumber(), 40.0f + RandomFloat(0 , 1.0f) );
			DispatchSpawn( pGrenade );
			pGrenade->SetSpell( true );
		}

		if ( TFGameRules() )
		{
			TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_PLAYER_SPELL_MIRV, ( pThrower->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED );
		}

		return true;
	}
	virtual void		ExplodeEffectOnTarget( CTFPlayer *pThrower, CTFPlayer *pTarget, CBaseCombatCharacter *pBaseTarget ) { }
	virtual const char *GetExplodeEffectParticle() const	{ return GetTeamNumber() == TF_TEAM_RED ? "spell_pumpkin_mirv_goop_red" : "spell_pumpkin_mirv_goop_blue"; }
	virtual const char *GetExplodeEffectSound()	const	{ return "Halloween.spell_mirv_explode_secondary"; }

	float m_flImpactTime;
#endif

#ifdef CLIENT_DLL
	virtual const char*			GetTrailParticleName( void ) { return "unusual_bubbles_green_fumes"; }
#endif
};

IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SpellPumpkin, DT_TFProjectile_SpellPumpkin )
	BEGIN_NETWORK_TABLE( CTFProjectile_SpellPumpkin, DT_TFProjectile_SpellPumpkin)
	END_NETWORK_TABLE()

	LINK_ENTITY_TO_CLASS( tf_projectile_spellpumpkin, CTFProjectile_SpellPumpkin );
PRECACHE_WEAPON_REGISTER( tf_projectile_spellpumpkin);


// *************************************************************************************************************************
class CTFProjectile_SpellMirv : public CTFProjectile_SpellBats
{
public:
	DECLARE_CLASS( CTFProjectile_SpellMirv, CTFProjectile_SpellBats );
	DECLARE_NETWORKCLASS();

	virtual float		GetDamageRadius()	const			{ return 1.0f; }
	virtual void		SetCustomPipebombModel()			{ SetModel( "models/weapons/w_models/w_cannonball.mdl" ); }
	virtual float		GetModelScale() const				{ return 0.9f; }
	virtual int			GetCustomDamageType() const OVERRIDE	{ return TF_DMG_CUSTOM_SPELL_MIRV; }

#ifdef GAME_DLL
	//virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) { }
	//virtual void PipebombTouch( CBaseEntity *pOther ) { }

	virtual bool InitialExplodeEffects( CTFPlayer *pThrower, const trace_t *pTrace ) OVERRIDE
	{
		// Spawn a tonne of extra grenades (mirv style)
		CTFSpellBook *pSpellBook = dynamic_cast< CTFSpellBook* >( pThrower->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) );
		if ( !pSpellBook )
			return false;

		// Create bomblets
		Vector offset = Vector( 0, -100, 400 );

		for ( int i = 0; i < 6; i++ )
		{
			AngularImpulse angVelocity = AngularImpulse( 0, 0, RandomFloat( 100, 300) );

			switch ( i )
			{
				case 0: offset = Vector(  75,  110, 400 ); break;
				case 1: offset = Vector(  75, -110, 400 ); break;
				case 2: offset = Vector( -75,  110, 400 ); break;
				case 3: offset = Vector( -75, -110, 400 ); break;
				case 4: offset = Vector( 135,  0, 400 ); break;
				case 5: offset = Vector( -135, 0, 400 ); break;
			}

			CTFProjectile_SpellPumpkin *pGrenade = static_cast<CTFProjectile_SpellPumpkin*>( CBaseEntity::CreateNoSpawn( "tf_projectile_spellpumpkin", GetAbsOrigin(), pThrower->EyeAngles(), pThrower ) );
			if ( pGrenade )
			{
				// Set the pipebomb mode before calling spawn, so the model & associated vphysics get setup properly.
				pGrenade->SetPipebombMode();
				DispatchSpawn( pGrenade );
				pGrenade->InitGrenade( offset, angVelocity, pThrower, pSpellBook->GetTFWpnData() );
				pGrenade->m_flFullDamage = 0;
				pGrenade->ApplyLocalAngularVelocityImpulse( angVelocity );
				pGrenade->SetDetonateTimerLength( 2.0f + 0.05f * i );
			}
		}

		if ( TFGameRules() )
		{
			TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_PLAYER_SPELL_MIRV, ( pThrower->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED );
		}

		return true;
	}
	virtual void		ExplodeEffectOnTarget( CTFPlayer *pThrower, CTFPlayer *pTarget, CBaseCombatCharacter *pBaseTarget ) { }
	virtual const char *GetExplodeEffectParticle() const	{ return GetTeamNumber() == TF_TEAM_RED ? "spell_pumpkin_mirv_goop_red" : "spell_pumpkin_mirv_goop_blue"; }
	virtual const char *GetExplodeEffectSound()	const	{ return "Halloween.spell_mirv_explode_primary"; }
#endif

#ifdef CLIENT_DLL
	virtual const char*			GetTrailParticleName( void ) { return "unusual_bubbles_green_fumes"; }
#endif
};

IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SpellMirv, DT_TFProjectile_SpellMirv )
	BEGIN_NETWORK_TABLE( CTFProjectile_SpellMirv, DT_TFProjectile_SpellMirv)
	END_NETWORK_TABLE()

	LINK_ENTITY_TO_CLASS( tf_projectile_spellmirv, CTFProjectile_SpellMirv );
PRECACHE_WEAPON_REGISTER( tf_projectile_spellmirv);

// *************************************************************************************************************************
class CTFProjectile_SpellSpawnBoss : public CTFProjectile_SpellBats
{
public:
	DECLARE_CLASS( CTFProjectile_SpellSpawnBoss, CTFProjectile_SpellBats );
	DECLARE_NETWORKCLASS();

	virtual float		GetDamageRadius()	const			{ return 1.0f; }
	virtual void		SetCustomPipebombModel()			{ SetModel( "models/props_mvm/mvm_human_skull_collide.mdl" ); }
	virtual float		GetModelScale() const				{ return 1.5f; }
	virtual int			GetCustomDamageType() const OVERRIDE { return TF_DMG_CUSTOM_SPELL_MONOCULUS; }

#ifdef GAME_DLL

		//virtual void		Explode( trace_t *pTrace, int bitsDamageType );	
		virtual bool InitialExplodeEffects( CTFPlayer *pThrower, const trace_t *pTrace ) OVERRIDE
		{
			const Vector &vContactPoint = pTrace->endpos;
			CHalloweenBaseBoss *pBoss = CHalloweenBaseBoss::SpawnBossAtPos( HALLOWEEN_BOSS_MONOCULUS, vContactPoint, pThrower->GetTeamNumber(), pThrower );
			if ( pBoss )
			{
				float flDesiredHeight = tf_eyeball_boss_hover_height.GetFloat();

				const Vector &vMins = pBoss->WorldAlignMins();
				const Vector &vMaxs = pBoss->WorldAlignMaxs();
				Vector vSize = vMaxs - vMins;

				float flBossHeight = vSize.z;
				float flBossHalfX = 0.5f * vSize.x;
				float flBossHalfY = 0.5f * vSize.y;

				static Vector vTest[] =
				{
					Vector( 0, 0, flBossHeight ),
					Vector( flBossHalfX, flBossHalfY, flBossHeight ),
					Vector( -flBossHalfX, -flBossHalfY, flBossHeight ),
					Vector( flBossHalfX, -flBossHalfY, flBossHeight ),
					Vector( -flBossHalfX, flBossHalfY, flBossHeight )
				};

				bool bFoundValidSpawnPos = false;
				for ( int i=0; i<ARRAYSIZE( vTest ); ++i )
				{
					trace_t result;
					float bloat = 5.0f;
					Vector vStart = vContactPoint + vTest[i] + 30.f * pTrace->plane.normal;
					Vector vEnd = vStart + Vector( 0, 0, flDesiredHeight );

					CTraceFilterNoNPCsOrPlayer filter( pBoss, COLLISION_GROUP_NONE );
					UTIL_TraceHull( vStart, vEnd, vMins - Vector( bloat, bloat, 0 ), vMaxs + Vector( bloat, bloat, bloat ), MASK_SOLID | CONTENTS_PLAYERCLIP, &filter, &result );
					if ( !result.startsolid )
					{
						pBoss->SetAbsOrigin( result.endpos );
						bFoundValidSpawnPos = true;
						//NDebugOverlay::SweptBox( vStart, vEnd, vMins - Vector( bloat, bloat, 0 ), vMaxs + Vector( bloat, bloat, bloat ), vec3_angle, 0, 255, 0, 0, 5.f );
						//NDebugOverlay::Sphere( result.endpos, 10.f, 0, 255, 0, true, 5.f );
						
						break;
					}
					else
					{
						// Maybe we should play fail sound here?
						//NDebugOverlay::SweptBox( vStart, vEnd, vMins - Vector( bloat, bloat, 0 ), vMaxs + Vector( bloat, bloat, bloat ), vec3_angle, 255, 0, 0, 0, 5.f );
						//NDebugOverlay::Sphere( result.endpos, 10.f, 255, 0, 0, true, 5.f );
					}
				}

				// couldn't find any valid position
				if ( !bFoundValidSpawnPos )
				{
					UTIL_Remove( pBoss );
					pBoss = NULL;
				}
			}

			// refund the player the spell
			if ( !pBoss )
			{
				CTFSpellBook *pSpellBook = dynamic_cast< CTFSpellBook* >( pThrower->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) );
				if ( pSpellBook )
				{
					pSpellBook->SetSelectedSpell( GetSpellIndexFromContext( MP_CONCEPT_PLAYER_SPELL_MONOCULOUS ) );
				}

				return false;
			}

			if ( TFGameRules() )
			{
				TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_PLAYER_SPELL_MONOCULOUS, ( pThrower->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED );
			}

			return true;
		}
		virtual void		ExplodeEffectOnTarget( CTFPlayer *pThrower, CTFPlayer *pTarget, CBaseCombatCharacter *pBaseTarget ) { }
		virtual const char *GetExplodeEffectParticle() const	{ return "eyeboss_death"; }
		virtual const char *GetExplodeEffectSound()	const	{ return "Halloween.spell_spawn_boss"; }
#endif

#ifdef CLIENT_DLL
	virtual const char*			GetTrailParticleName( void ) { return "unusual_bubbles_green_fumes"; }
#endif
};

// Spawn Boss
IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SpellSpawnBoss, DT_TFProjectile_SpellSpawnBoss )
	BEGIN_NETWORK_TABLE( CTFProjectile_SpellSpawnBoss, DT_TFProjectile_SpellSpawnBoss )
	END_NETWORK_TABLE()

	LINK_ENTITY_TO_CLASS( tf_projectile_spellspawnboss, CTFProjectile_SpellSpawnBoss );
PRECACHE_WEAPON_REGISTER( tf_projectile_spellspawnboss );


// *************************************************************************************************************************
#ifdef GAME_DLL
class CTFSpell_MeteorShowerSpawner : public CBaseEntity
{
public:
	DECLARE_DATADESC();
	DECLARE_CLASS( CTFSpell_MeteorShowerSpawner, CBaseEntity );

	virtual void Spawn() OVERRIDE
	{
		m_flFinishTime = gpGlobals->curtime + 4.f;
		SetContextThink( &CTFSpell_MeteorShowerSpawner::MeteorShowerThink, gpGlobals->curtime, "MeteorShowerThink" );
	}

	void MeteorShowerThink( void )
	{
		CTFPlayer *pPlayer = ToTFPlayer( GetOwnerEntity() );
		if ( pPlayer && m_flFinishTime > gpGlobals->curtime )
		{
			// the owner changed team? remove this
			if ( pPlayer->GetTeamNumber() != GetTeamNumber() )
			{
				UTIL_Remove( this );
				return;
			}

			// Determine our "height" offset range based on surface normal
			Vector vecDir = Vector( 0.f, 0.f, 1.f );
			float flOffsetMin = 400.f;
			float flOffsetMax = 500.f;
			if ( m_vecImpactNormal.z <= -0.6f )	// Ceiling?
			{
				flOffsetMin = 45.f;
				flOffsetMax = 60.f;
				vecDir.z = -1.f;
			}
			const float flRange = 200.f;
			const float flRandomAngleOffset = 75.f;

			const int nNumToSpawn = random->RandomInt( 1, 2 );
			for ( int i = 0; i < nNumToSpawn; ++i )
			{
				// Vary start point away from surface center
				Vector vecOnPlane = Vector( RandomFloat( -flRange, flRange ), RandomFloat( -flRange, flRange ), 0.f ).Normalized();
				Vector vecPointOnPlane = GetAbsOrigin() + random->RandomFloat( -flRange, flRange ) * vecOnPlane;
				const float flOffsetFromPlane = random->RandomFloat( flOffsetMin, flOffsetMax );
				Vector vecEmit = vecPointOnPlane + flOffsetFromPlane * vecDir;

				// debugoverlay->AddLineOverlay( GetAbsOrigin(), vecEmit, 255, 0, 0, false, 10 );

				Vector vecVelocity = Vector( RandomFloat( -flRandomAngleOffset, flRandomAngleOffset ), RandomFloat( -flRandomAngleOffset, flRandomAngleOffset ), -700.f );

				// Check for a spot
				trace_t trace;
				UTIL_TraceLine( GetAbsOrigin(), vecEmit, ( MASK_SHOT & ~( CONTENTS_HITBOX ) ), NULL, COLLISION_GROUP_NONE, &trace );
				if ( !trace.DidHit() )
				{
					SpawnMeteor( pPlayer, trace.endpos, vec3_angle, vecVelocity );
				}
				else
				{
					// Pull back and try again
					vecEmit = trace.endpos + ( trace.plane.normal * 1.0f );
					SpawnMeteor( pPlayer, vecEmit, vec3_angle, vecVelocity );
				}
			}

			SetContextThink( &CTFSpell_MeteorShowerSpawner::MeteorShowerThink, gpGlobals->curtime + 0.2f, "MeteorShowerThink" );
			return;
		}

		UTIL_Remove( this );
	}

	void SpawnMeteor( CTFPlayer *pOwner, const Vector &origin, const QAngle &angles, const Vector &velocity )
	{
		CTFProjectile_SpellFireball *pRocket = static_cast< CTFProjectile_SpellFireball* >( CBaseEntity::CreateNoSpawn( "tf_projectile_spellfireball", origin, angles, pOwner ) );
		if ( pRocket )
		{
			pRocket->SetOwnerEntity( pOwner );
			pRocket->SetLauncher( pOwner ); 
			pRocket->SetAbsVelocity( velocity );
			pRocket->SetDamage( 50.f );
			pRocket->SetMeteor( true );
			pRocket->ChangeTeam( GetTeamNumber() );
			const char *pszParticle = GetTeamNumber() == TF_TEAM_BLUE ? "spell_fireball_tendril_parent_blue" : "spell_fireball_tendril_parent_red";
			pRocket->SetExplodeParticleName( pszParticle );

			IPhysicsObject *pPhysicsObject = pRocket->VPhysicsGetObject();
			if ( pPhysicsObject )
			{
				pPhysicsObject->AddVelocity( &velocity, NULL );
			}

			DispatchSpawn( pRocket );
		}
	}

	void SetImpaceNormal( Vector &vecNormal ) { m_vecImpactNormal = vecNormal; }

private:
	float m_flFinishTime;
	Vector m_vecImpactNormal;
};

// Meteor Shower
LINK_ENTITY_TO_CLASS( tf_spell_meteorshowerspawner, CTFSpell_MeteorShowerSpawner );

BEGIN_DATADESC( CTFSpell_MeteorShowerSpawner )
END_DATADESC()
#endif // GAME_DLL

// *************************************************************************************************************************
class CTFProjectile_SpellMeteorShower : public CTFProjectile_SpellBats
{
public:
	DECLARE_CLASS( CTFProjectile_SpellMeteorShower, CTFProjectile_SpellBats );
	DECLARE_NETWORKCLASS();

	virtual float		GetModelScale() const				{ return 0.01f; }

#ifdef GAME_DLL
	virtual void Explode( trace_t *pTrace, int bitsDamageType ) OVERRIDE
	{
		m_takedamage = DAMAGE_NO;

		// Pull out of the wall a bit.
		if ( pTrace->fraction != 1.0 )
		{
			SetAbsOrigin( pTrace->endpos + ( pTrace->plane.normal * 1.0f ) );
		}

		Vector vecOrigin = GetAbsOrigin();

		// Particle
		if ( GetExplodeEffectParticle() )
		{	
			CPVSFilter filter( vecOrigin );
			TE_TFParticleEffect( filter, 0.0, GetExplodeEffectParticle(), vecOrigin, vec3_angle );
		}

		// Sounds
		if ( GetExplodeEffectSound() )
		{
			EmitSound( GetExplodeEffectSound() );
		}

		CTFSpell_MeteorShowerSpawner *pSpawner = static_cast< CTFSpell_MeteorShowerSpawner* >( CBaseEntity::CreateNoSpawn( "tf_spell_meteorshowerspawner", vecOrigin, vec3_angle, GetThrower() ) );
		if ( pSpawner )
		{
			pSpawner->SetImpaceNormal( pTrace->plane.normal );
			pSpawner->ChangeTeam( GetTeamNumber() );
			DispatchSpawn( pSpawner );
		}

		if ( TFGameRules() )
		{
			CTFPlayer *pThrower = ToTFPlayer( GetOwnerEntity() );
			if ( pThrower )
			{
				TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_PLAYER_SPELL_METEOR_SWARM, ( pThrower->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED );
			}
		}

		SetModelName( NULL_STRING );
		AddSolidFlags( FSOLID_NOT_SOLID );
		SetTouch( NULL );
		AddEffects( EF_NODRAW );
		SetAbsVelocity( vec3_origin );

		SetContextThink( &CBaseGrenade::SUB_Remove, gpGlobals->curtime, "RemoveThink" );
	}

	virtual const char *GetExplodeEffectParticle() const	{ return "bomibomicon_ring"; }
	virtual const char *GetExplodeEffectSound()	const		{ return "Halloween.spell_meteor_impact"; }
#endif // GAME_DLL

#ifdef CLIENT_DLL
	virtual const char *GetTrailParticleName( void )
	{ 
		return GetTeamNumber() == TF_TEAM_BLUE ? "spell_fireball_small_blue" : "spell_fireball_small_red";
	}
#endif
};

// Meteor Shower
IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SpellMeteorShower, DT_TFProjectile_SpellMeteorShower )
	BEGIN_NETWORK_TABLE( CTFProjectile_SpellMeteorShower, DT_TFProjectile_SpellMeteorShower)
	END_NETWORK_TABLE()

LINK_ENTITY_TO_CLASS( tf_projectile_spellmeteorshower, CTFProjectile_SpellMeteorShower );
PRECACHE_WEAPON_REGISTER( tf_projectile_spellmeteorshower );


// *************************************************************************************************************************
class CTFProjectile_SpellTransposeTeleport : public CTFProjectile_SpellBats
{
public:
	DECLARE_CLASS( CTFProjectile_SpellTransposeTeleport, CTFProjectile_SpellBats );
	DECLARE_NETWORKCLASS();

	virtual void Spawn( void )
	{
		SetModelScale( 0.01f );
		BaseClass::Spawn();
		SetCollisionGroup( COLLISION_GROUP_PLAYER_MOVEMENT );
#ifdef GAME_DLL
		SetContextThink( &CTFProjectile_SpellTransposeTeleport::RecordPosThink, gpGlobals->curtime + 0.05f, "RecordThink" );
#endif
	}

	// FIX
	virtual int			GetWeaponID( void ) const			{ return TF_PROJECTILE_SPELL; }
	virtual float		GetDamageRadius()	const			{ return 5.0f; }
	virtual int			GetCustomDamageType() const OVERRIDE { return TF_DMG_CUSTOM_SPELL_TELEPORT; }

	virtual unsigned int PhysicsSolidMaskForEntity( void ) const
	{
		return BaseClass::PhysicsSolidMaskForEntity() | CONTENTS_PLAYERCLIP;
	}

#ifdef GAME_DLL

	void RecordPosThink( void )
	{
		m_vecTrailingPos.AddToTail( GetAbsOrigin() );

		// Only retain 5 positions
		if ( m_vecTrailingPos.Count() > 5 )
		{
			m_vecTrailingPos.Remove( 0 );
		}

		SetContextThink( &CTFProjectile_SpellTransposeTeleport::RecordPosThink, gpGlobals->curtime + 0.05f, "RecordThink" );
	}

	virtual bool InitialExplodeEffects( CTFPlayer *pThrower, const trace_t *pTrace ) OVERRIDE
	{
		if ( !pThrower->IsAlive() )
			return false;

		// Grant a small amount of health
		pThrower->TakeHealth( 30, DMG_GENERIC );

		trace_t result;
		CTraceFilterIgnoreTeammates traceFilter( this, COLLISION_GROUP_PLAYER_MOVEMENT, GetTeamNumber() );
		unsigned int nMask = pThrower->GetTeamNumber() == TF_TEAM_RED ? CONTENTS_BLUETEAM : CONTENTS_REDTEAM;
		nMask |= MASK_PLAYERSOLID;

		m_vecTrailingPos.AddToTail( pTrace->endpos + ( pTrace->plane.normal * 50.f ) );

		// Try a few spots
		FOR_EACH_VEC_BACK( m_vecTrailingPos, i )
		{
			// Try positions starting with the current, and moving back in time a bit
			Vector vecStart = m_vecTrailingPos[i];
			UTIL_TraceHull( vecStart, vecStart, VEC_HULL_MIN, VEC_HULL_MAX, nMask, &traceFilter, &result );

			if( !result.DidHit() )
			{
				// Place a teleport effect where they came from
				const Vector& vecOrigin = pThrower->GetAbsOrigin();
				CPVSFilter pvsFilter( vecOrigin );
				TE_TFParticleEffect( pvsFilter, 0.0, GetExplodeEffectParticle(), vecOrigin, vec3_angle );

				// Move 'em!
				pThrower->Teleport( &vecStart, &pThrower->GetAbsAngles(), NULL );

				// Do a zoom effect
				pThrower->SetFOV( pThrower, 0, 0.3f, 120 );

				// Screen flash
				color32 fadeColor = {255,255,255,100};
				UTIL_ScreenFade( pThrower, fadeColor, 0.25, 0.4, FFADE_IN );

				if ( TFGameRules() )
				{
					TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_PLAYER_SPELL_TELEPORT, ( pThrower->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED );
				}
				
				// Success!
				return true;
			}
		}

		return false;
	}
	virtual void ExplodeEffectOnTarget( CTFPlayer *pThrower, CTFPlayer *pTarget, CBaseCombatCharacter *pBaseTarget )
	{
		// ...
	}
	virtual const char *GetExplodeEffectParticle() const	{ return "eyeboss_tp_player"; }
	virtual const char *GetExplodeEffectSound()	const	{ return "Building_Teleporter.Ready"; }
#endif

#ifdef CLIENT_DLL
	virtual const char*			GetTrailParticleName( void ) { return GetTeamNumber() == TF_TEAM_RED ? "spell_teleport_red" : "spell_teleport_blue"; }
#endif

private:
#ifdef GAME_DLL
	CUtlVector< Vector > m_vecTrailingPos;
#endif
};

// Spawn Boss
IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SpellTransposeTeleport, SpellTransposeTeleport )
	BEGIN_NETWORK_TABLE( CTFProjectile_SpellTransposeTeleport, SpellTransposeTeleport )
	END_NETWORK_TABLE()

	LINK_ENTITY_TO_CLASS( tf_projectile_spelltransposeteleport, CTFProjectile_SpellTransposeTeleport );
PRECACHE_WEAPON_REGISTER( tf_projectile_spelltransposeteleport );

#ifdef GAME_DLL
void RemoveAll2013HalloweenTeleportSpellsInMidFlight( void )
{
	CBaseEntity *pTeleport = NULL;
	while ( ( pTeleport = gEntList.FindEntityByClassname( pTeleport, "tf_projectile_spelltransposeteleport" ) ) != NULL )
	{
		UTIL_Remove( pTeleport );
	}
}
#endif

// *************************************************************************************************************************
class CTFProjectile_SpellLightningOrb : public CTFProjectile_SpellFireball
{
public:
	DECLARE_CLASS( CTFProjectile_SpellLightningOrb, CTFProjectile_SpellFireball );
	DECLARE_NETWORKCLASS();

	~CTFProjectile_SpellLightningOrb()
	{
#ifdef CLIENT_DLL
		if ( m_pTrailParticle )
		{
			ParticleProp()->StopEmissionAndDestroyImmediately( m_pTrailParticle );
			m_pTrailParticle = NULL;
		}
#endif // CLIENT_DLL
	}

#ifdef GAME_DLL

	virtual void Spawn() OVERRIDE
	{
		BaseClass::Spawn();

		// We dont want to collide with anything but the world
		SetSolid( SOLID_NONE );

		SetExplodeParticleName( GetTeamNumber() == TF_TEAM_BLUE ? "drg_cow_explosioncore_charged_blue" : "drg_cow_explosioncore_charged" );
		SetContextThink( &CTFProjectile_SpellLightningOrb::ZapThink, gpGlobals->curtime + 0.25f, "ZapThink" );
		SetContextThink( &CTFProjectile_SpellLightningOrb::VortexThink, gpGlobals->curtime + 0.2f, "VortexThink" );
		SetContextThink( &CTFProjectile_SpellLightningOrb::ExplodeAndRemove, gpGlobals->curtime + 5.f, "ExplodeAndRemoveThink" );
		SetDamage( 20.f );
	}

	virtual const char *GetProjectileModelName( void ) { return ""; } // We dont have a model by default, and that's OK

	virtual float		GetDamageRadius()	const			{ return 200.f; }
	virtual int			GetCustomDamageType() const OVERRIDE { return TF_DMG_CUSTOM_SPELL_LIGHTNING; }

	virtual void RocketTouch( CBaseEntity *pOther ) OVERRIDE
	{
		Assert( pOther );
		if ( !pOther )
			return;

		if ( !pOther->IsSolid() || pOther->IsSolidFlagSet( FSOLID_VOLUME_CONTENTS ) )
			return;

		const trace_t *pTrace = &CBaseEntity::GetTouchTrace();
		// Bounce off the world
		if ( pOther->IsWorld() )
		{
			Vector vIntoSurface = pTrace->plane.normal * pTrace->plane.normal.Dot( GetAbsVelocity() );
			SetAbsVelocity( GetAbsVelocity() + ( -1.5f * vIntoSurface ) );
			return;
		}

		// Handle hitting skybox (disappear).
		if ( pTrace->surface.flags & SURF_SKY )
		{
			UTIL_Remove( this );
			return;
		}

		// pass through ladders
		if( pTrace->surface.flags & CONTENTS_LADDER )
			return;

		if ( pOther->IsPlayer() )
			return;

		// Spell ends when we run into something
		ExplodeAndRemove();
		return;
	}
	
	virtual bool InitialExplodeEffects( CTFPlayer *pThrower, const trace_t *pTrace ) OVERRIDE
	{
		Zap( 16 );

		return true;
	}

	virtual const char *GetExplodeEffectSound()	const		{ return "Halloween.spell_lightning_impact"; }
	virtual void ExplodeEffectOnTarget( CTFPlayer *pThrower, CTFPlayer *pTarget, CBaseCombatCharacter *pBaseTarget ) OVERRIDE
	{}

	void ExplodeAndRemove()
	{
		// Particle
		if ( GetExplodeEffectParticle() )
		{	
			CPVSFilter filter( GetAbsOrigin() );
			TE_TFParticleEffect( filter, 0.0, GetExplodeEffectParticle(), GetAbsOrigin(), vec3_angle );

			EmitSound( filter, entindex(), GetExplodeEffectSound() );
		}

		// Go out with a bang
		Zap( 16 );
			
		SetContextThink( &CBaseGrenade::SUB_Remove, gpGlobals->curtime, "RemoveThink" );
		return;
	}

	void ZapThink()
	{
		Zap( 2 );
		SetContextThink( &CTFProjectile_SpellLightningOrb::ZapThink, gpGlobals->curtime + RandomFloat( 0.25f, 0.35f ), "ZapThink" );
	}

	void Zap( int nNumToZap )
	{
		CBaseEntity *pOwner = GetOwnerEntity();

		if ( !pOwner )
			return;
		
		CTakeDamageInfo info;
		info.SetAttacker( pOwner );
		info.SetInflictor( this ); 
		info.SetWeapon( GetLauncher() );
		info.SetDamage( GetDamage() );
		info.SetDamageCustom( GetCustomDamageType() );
		info.SetDamagePosition( GetAbsOrigin() );
		info.SetDamageType( DMG_BURN );

		CBaseEntity *pListOfEntities[5];
		int iEntities = UTIL_EntitiesInSphere( pListOfEntities, 5, GetAbsOrigin(), GetDamageRadius(), FL_CLIENT | FL_FAKECLIENT | FL_NPC );

		// Shuffle the list
		for( int i = iEntities - 1; i > 0; --i )
		{
			V_swap( pListOfEntities[i], pListOfEntities[ RandomInt( 0, i ) ] );
		}

		// Zap as many targets as we're told to, if we can
		int nHits = 0;
		for ( int i = 0; i < iEntities && nHits < nNumToZap; ++i )
		{
			CBaseEntity* pTarget = pListOfEntities[i];

			if ( !pTarget )
				continue;

			if ( !pTarget->IsAlive() )
				continue;

			if ( pOwner->InSameTeam( pTarget ) )
				continue;

			if ( !FVisible( pTarget, MASK_OPAQUE ) )
				continue;

			CTFPlayer *pTFPlayer = ToTFPlayer( pTarget );
			if ( pTFPlayer )
			{
				if ( pTFPlayer->m_Shared.InCond( TF_COND_PHASE ) || pTFPlayer->m_Shared.InCond( TF_COND_PASSTIME_INTERCEPTION ) )
					continue;

				if ( pTFPlayer->m_Shared.IsInvulnerable() )
					continue;
			}

			CTraceFilterIgnoreTeammates tracefilter( this, COLLISION_GROUP_NONE, GetTeamNumber() );
			trace_t trace;
			UTIL_TraceLine( GetAbsOrigin(), pTarget->GetAbsOrigin(), ( MASK_SHOT & ~( CONTENTS_HITBOX ) ), &tracefilter, &trace );
			if ( trace.DidHitWorld() )
				continue;

			// Shoot a beam at them
			CPVSFilter filter( pTarget->WorldSpaceCenter() );
			Vector vStart = WorldSpaceCenter();
			Vector vEnd = pTarget->EyePosition();
			const char *pszHitEffect = GetTeamNumber() == TF_TEAM_BLUE ? "spell_lightningball_hit_blue" : "spell_lightningball_hit_red";
			te_tf_particle_effects_control_point_t controlPoint = { PATTACH_ABSORIGIN, vEnd };
			TE_TFParticleEffectComplex( filter, 0.0f, pszHitEffect, vStart, QAngle( 0, 0, 0 ), NULL, &controlPoint, pTFPlayer, PATTACH_CUSTOMORIGIN );

			// Hurt 'em.
			Vector dir;
			AngleVectors( GetAbsAngles(), &dir );
			pTarget->DispatchTraceAttack( info, dir, &trace );
			ApplyMultiDamage();

			++nHits;
		}

		// We zapped someone.  Play a sound
		if ( nHits > 0 )
		{
			pOwner->EmitSound( "TFPlayer.MedicChargedDeath" );

			if ( TFGameRules() )
			{
				CTFPlayer *pThrower = ToTFPlayer( GetOwnerEntity() );
				if ( pThrower )
				{
					TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_PLAYER_SPELL_LIGHTNING_BALL, ( pThrower->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED );
				}
			}
		}
	}

	void VortexThink( void )
	{
		const int nMaxEnts = 32;

		Vector vecPos = GetAbsOrigin();
		CBaseEntity	*pObjects[ nMaxEnts ];
		int nCount = UTIL_EntitiesInSphere( pObjects, nMaxEnts, vecPos, GetDamageRadius(), FL_CLIENT | FL_NPC );

		// NDebugOverlay::Sphere( vecPos, GetDamageRadius(), 0, 255, 0, false, 0.35f );

		// Iterate through sphere's contents
		for ( int i = 0; i < nCount; i++ )
		{
			CBaseCombatCharacter *pEntity = pObjects[i]->MyCombatCharacterPointer();
			if ( !pEntity )
				continue;

			if ( InSameTeam( pEntity ) )
				continue;

			if ( !FVisible( pEntity, MASK_OPAQUE ) )
				continue;

 			// Draw player toward us
			Vector vecSourcePos = pEntity->GetAbsOrigin();
			Vector vecTargetPos = GetAbsOrigin();
			Vector vecVelocity = ( vecTargetPos - vecSourcePos ) * 2.f;
			vecVelocity.z += 50.f;

			if ( pEntity->GetFlags() & FL_ONGROUND )
			{
				vecVelocity.z += 150.f;
				pEntity->SetGroundEntity( NULL );
				pEntity->SetGroundChangeTime( gpGlobals->curtime + 0.5f );
			}

			pEntity->Teleport( NULL, NULL, &vecVelocity );
		}

		SetContextThink( &CTFProjectile_SpellLightningOrb::VortexThink, gpGlobals->curtime + 0.2f, "VortexThink" );
		return;
	}
#endif

#ifdef CLIENT_DLL
	virtual const char *GetTrailParticleName( void )		{ return NULL; } // CRUTGUN!
	virtual void CreateTrails( void )
	{
		BaseClass::CreateTrails();

		if ( !m_pTrailParticle )
		{
			m_pTrailParticle = ParticleProp()->Create( ( GetTeamNumber() == TF_TEAM_BLUE ? "spell_lightningball_parent_blue" : "spell_lightningball_parent_red" ), PATTACH_ABSORIGIN_FOLLOW );
		}
	}

private:
	CNewParticleEffect *m_pTrailParticle;
#endif // CLIENT_DLL
};

// Lightning ball
IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SpellLightningOrb, DT_TFProjectile_SpellLightningOrb )
	BEGIN_NETWORK_TABLE( CTFProjectile_SpellLightningOrb, DT_TFProjectile_SpellLightningOrb )
	END_NETWORK_TABLE()

LINK_ENTITY_TO_CLASS( tf_projectile_lightningorb, CTFProjectile_SpellLightningOrb );
PRECACHE_WEAPON_REGISTER( tf_projectile_lightningorb);


#ifdef CLIENT_DLL
	#define CTFHellZap C_TFHellZap
#endif

#ifdef GAME_DLL
	#include "tf_obj_dispenser.h"
	#include "particle_parse.h"
	#include "tf_fx.h"
#endif

class CTFHellZap : public CBaseEntity 
{
	DECLARE_CLASS( CTFHellZap, CBaseEntity )
	DECLARE_NETWORKCLASS();
	DECLARE_DATADESC();
public:
	CTFHellZap()
#ifdef GAME_DLL
		: m_eType( ZAP_ON_TOUCH )
#endif
	{}
#ifdef GAME_DLL

	enum EZapperType
	{
		ZAP_ON_TOUCH,
		ZAP_ON_TEST,
	};

	virtual void Spawn()
	{
		m_bEnabled = true;

		if ( m_iszCustomTouchTrigger != NULL_STRING )
		{
			m_hTouchTrigger = dynamic_cast<CDispenserTouchTrigger *> ( gEntList.FindEntityByName( NULL, m_iszCustomTouchTrigger ) );

			if ( m_hTouchTrigger.Get() != NULL )
			{
				Assert( m_hTouchTrigger->GetOwnerEntity() == NULL );
				m_hTouchTrigger->SetOwnerEntity( this );	//owned
			}
		}
	}

	void ZapAllTouching()
	{
		FOR_EACH_VEC_BACK( m_vecZapTargets, i )
		{
			CBaseEntity* pZapTarget = m_vecZapTargets[i].Get();
			// Remove targets that have disappeared
			if ( !pZapTarget )
			{
				m_vecZapTargets.Remove( i );
				continue;
			}

			// Shoot a beam at them
			CPVSFilter filter( pZapTarget->WorldSpaceCenter() );
			Vector vStart = WorldSpaceCenter();
			Vector vEnd = pZapTarget->WorldSpaceCenter();
			te_tf_particle_effects_control_point_t controlPoint = { PATTACH_CUSTOMORIGIN, vEnd };
			TE_TFParticleEffectComplex( filter, 0.0f, m_iszParticleName.ToCStr(), vStart, QAngle( 0, 0, 0 ), NULL, &controlPoint, this, PATTACH_CUSTOMORIGIN );
		}
	}

	void ZapThink()
	{
		ZapAllTouching();

		// Keep zapping if we have targets
		if ( m_vecZapTargets.Count() )
		{
			SetContextThink( &CTFHellZap::ZapThink, gpGlobals->curtime + 0.25f, "ZapThink" );
		}
	}

	virtual void StartTouch( CBaseEntity *pEntity )
	{
		m_vecZapTargets.AddToTail( pEntity );

		if ( m_eType == ZAP_ON_TOUCH )
		{
			SetContextThink( &CTFHellZap::ZapThink, gpGlobals->curtime, "ZapThink" );
		}
	}

	virtual void EndTouch( CBaseEntity *pEntity )
	{
		int nIndex = m_vecZapTargets.Find( pEntity );
		if( nIndex != m_vecZapTargets.InvalidIndex() )
		{
			m_vecZapTargets.Remove( nIndex );
		}

		// No more targets.  Stop thinking!
		if ( m_vecZapTargets.Count() == 0 && m_eType == ZAP_ON_TOUCH )
		{
			SetContextThink( NULL, 0, "ZapThink" );
		}
	}

	void InputEnable( inputdata_t &inputdata )
	{
		m_bEnabled = true;

		if ( m_vecZapTargets.Count() > 0 && m_eType == ZAP_ON_TOUCH )
		{
			SetContextThink( &CTFHellZap::ZapThink, gpGlobals->curtime, "ZapThink" );
		}
	}

	void InputDisable( inputdata_t &inputdata )
	{
		m_bEnabled = false;
		SetContextThink( NULL, 0, "ZapThink" );
	}

	void InputZapAllTouching( inputdata_t &inputdata )
	{
		ZapAllTouching();
	}
private:

	EZapperType m_eType;
	bool	m_bEnabled;
	EHANDLE m_hTouchTrigger;
	string_t m_iszCustomTouchTrigger;
	string_t m_iszParticleName;
	CUtlVector< EHANDLE > m_vecZapTargets;
#endif
};


BEGIN_DATADESC( CTFHellZap )
#ifdef GAME_DLL
 	DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
	DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
	DEFINE_INPUTFUNC( FIELD_VOID, "ZapTouching", InputZapAllTouching ),

	DEFINE_KEYFIELD( m_iszCustomTouchTrigger, FIELD_STRING, "touch_trigger" ),
	DEFINE_KEYFIELD( m_iszParticleName, FIELD_STRING, "ParticleEffect" ),
	DEFINE_KEYFIELD( m_eType, FIELD_INTEGER, "ZapperType" ),
#endif
END_DATADESC()

LINK_ENTITY_TO_CLASS( halloween_zapper, CTFHellZap );
IMPLEMENT_NETWORKCLASS_ALIASED( TFHellZap, DT_TFHellZap )

BEGIN_NETWORK_TABLE( CTFHellZap, DT_TFHellZap )
END_NETWORK_TABLE()


//*******************************************************************************************************************************************************
// Kart Spells
//*******************************************************************************************************************************************************
class CTFProjectile_SpellKartOrb: public CTFProjectile_SpellFireball
{
public:
	DECLARE_CLASS( CTFProjectile_SpellKartOrb, CTFProjectile_SpellFireball );
	DECLARE_NETWORKCLASS();

#ifdef GAME_DLL
	virtual void Spawn() OVERRIDE
	{
		BaseClass::Spawn();
		SetContextThink( &CTFProjectile_SpellKartOrb::ExplodeAndRemove, gpGlobals->curtime + tf_halloween_kart_rocketspell_lifetime.GetFloat(), "ExplodeAndRemoveThink" );
		SetContextThink( &CTFProjectile_SpellKartOrb::MoveChecking, gpGlobals->curtime + 0.05f, "MoveCheckingThink" );

		SetModel( SPELL_BOXING_GLOVE );
		SetModelScale( 2.5f );

		Vector mins( -20, -20, 0 );
		Vector maxs( 20, 20, 20 );
		UTIL_SetSize( this, mins, maxs );
	}

	virtual void RocketTouch( CBaseEntity *pOther ) OVERRIDE
	{
		Assert( pOther );
		if ( !pOther )
			return;

		if ( !pOther->IsSolid() || pOther->IsSolidFlagSet( FSOLID_VOLUME_CONTENTS ) )
			return;

		// Handle hitting skybox (disappear).
		const trace_t *pTrace = &CBaseEntity::GetTouchTrace();
		if ( pTrace->surface.flags & SURF_SKY )
		{
			UTIL_Remove( this );
			return;
		}

		// Bounce off the world
		if ( pOther->IsWorld() )
		{
			Vector vOld = GetAbsVelocity();
			//float flSpeed = vOld.Length();
			Vector vNew = ( -2.0f * pTrace->plane.normal.Dot( vOld ) * pTrace->plane.normal + vOld );
			vNew.NormalizeInPlace();
			vNew *= tf_halloween_kart_rocketspell_speed.GetFloat();
			SetAbsVelocity( vNew );
			return;
		}

		// pass through ladders
		if ( pTrace->surface.flags & CONTENTS_LADDER )
			return;

		if ( pOther->IsPlayer() )
			ExplodeAndRemove();

		// Spell ends when we run into something
		//ExplodeAndRemove();
		return;
	}

	void MoveChecking ()
	{
		// do a short trace down, if nothing is there, add a bit of downward velocity
		trace_t pTrace;
		Vector vecSpot = GetAbsOrigin() ;
		UTIL_TraceLine( vecSpot, vecSpot - Vector(0, 0, 32), MASK_SOLID, this, COLLISION_GROUP_NONE, &pTrace );

		if ( pTrace.fraction >= 1.0  )
		{
			// Start moving down
			SetAbsVelocity( GetAbsVelocity() - Vector( 0, 0, 128 ) );
		}
		
		SetContextThink( &CTFProjectile_SpellKartOrb::MoveChecking, gpGlobals->curtime + 0.05f, "MoveCheckingThink" );
	}

	void ExplodeAndRemove()
	{
		// Handle hitting skybox (disappear).
		const trace_t *pTrace = &CBaseEntity::GetTouchTrace();

		if ( pTrace->surface.flags & SURF_SKY )
		{
			UTIL_Remove( this );
			return;
		}

		// pass through ladders
		if ( pTrace->surface.flags & CONTENTS_LADDER )
			return;

		Explode( pTrace );

		UTIL_Remove( this );
	}

	// We dont deal actual damage, just Car damage
	virtual void Explode( const trace_t *pTrace )
	{
		SetModelName( NULL_STRING );//invisible
		AddSolidFlags( FSOLID_NOT_SOLID );

		m_takedamage = DAMAGE_NO;

		// Pull out of the wall a bit.
		if ( pTrace->fraction != 1.0 )
		{
			SetAbsOrigin( pTrace->endpos + ( pTrace->plane.normal * 1.0f ) );
		}

		CTFPlayer *pThrower = ToTFPlayer( GetOwnerEntity() );
		if ( pThrower )
		{
			const Vector &vecOrigin = GetAbsOrigin();

			// Any effects from the initial explosion
			if ( InitialExplodeEffects( pThrower, pTrace ) )
			{
				// Particle
				if ( GetExplodeEffectParticle() )
				{
					CPVSFilter filter( vecOrigin );
					TE_TFParticleEffect( filter, 0.0, GetExplodeEffectParticle(), vecOrigin, vec3_angle );
				}

				// Sounds
				if ( GetExplodeEffectSound() )
				{
					EmitSound( GetExplodeEffectSound() );
				}

				// Treat this trace exactly like radius damage
				CTraceFilterIgnorePlayers traceFilter( pThrower, COLLISION_GROUP_PROJECTILE );

				// Splash pee on everyone nearby.
				CBaseEntity *pListOfEntities[32];
				int iEntities = UTIL_EntitiesInSphere( pListOfEntities, 32, vecOrigin, GetDamageRadius(), FL_CLIENT | FL_FAKECLIENT | FL_NPC );
				for ( int i = 0; i < iEntities; ++i )
				{
					CBaseCombatCharacter *pBasePlayer = NULL;
					CTFPlayer *pPlayer = ToTFPlayer( pListOfEntities[i] );
					if ( !pPlayer )
					{
						pBasePlayer = dynamic_cast<CBaseCombatCharacter*>( pListOfEntities[i] );
					}
					else
					{
						pBasePlayer = pPlayer;
					}

					if ( !pBasePlayer || !pPlayer || !pPlayer->IsAlive() || InSameTeam(pPlayer) )
						continue;

					// Do a quick trace to see if there's any geometry in the way.
					trace_t trace;
					UTIL_TraceLine( vecOrigin, pPlayer->WorldSpaceCenter(), ( MASK_SHOT & ~( CONTENTS_HITBOX ) ), &traceFilter, &trace );
					//debugoverlay->AddLineOverlay( vecOrigin, pPlayer->WorldSpaceCenter(), 255, 0, 0, false, 10 );
					if ( trace.DidHitWorld() )
						continue;

					// Effects on the individual players
					//ExplodeEffectOnTarget( pThrower, pPlayer, pBasePlayer );

					// Apply Car Damage and a force
					Vector vecDir = pPlayer->WorldSpaceCenter() - GetAbsOrigin();
					vecDir.NormalizeInPlace();
					Vector vecForward, vecRight, vecUp;
					AngleVectors( pPlayer->GetAnimRenderAngles(), &vecForward, &vecRight, &vecUp );
					vecDir += ( vecUp * 0.5f );
					pPlayer->AddHalloweenKartPushEvent( pThrower, this, pThrower->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ), vecDir * tf_halloween_kart_rocketspell_force.GetFloat(), 50.0f );
				}
			}
			else
			{
				pThrower->EmitSound( "Player.DenyWeaponSelection" );
			}
		}
	}

	virtual const char *GetExplodeEffectParticle() const	{ return "ExplosionCore_MidAir"; }
#endif

#ifdef CLIENT_DLL
	virtual const char *GetTrailParticleName( void )
	{
		return "halloween_rockettrail";
	}
#endif
};

// Kart Spell Orbs
IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SpellKartOrb, DT_TFProjectile_SpellKartOrb )
BEGIN_NETWORK_TABLE( CTFProjectile_SpellKartOrb, DT_TFProjectile_SpellKartOrb )
END_NETWORK_TABLE()

LINK_ENTITY_TO_CLASS( tf_projectile_spellkartorb, CTFProjectile_SpellKartOrb );
PRECACHE_WEAPON_REGISTER( tf_projectile_spellkartorb );

class CTFProjectile_SpellKartBats : public CTFProjectile_SpellBats
{
public:
	DECLARE_CLASS( CTFProjectile_SpellKartBats, CTFProjectile_SpellBats );
	DECLARE_NETWORKCLASS();

#ifdef GAME_DLL
	virtual void ApplyBlastDamage( CTFPlayer *pThrower, Vector vecOrigin )
	{
		
	}

	virtual void ExplodeEffectOnTarget( CTFPlayer *pThrower, CTFPlayer *pTarget, CBaseCombatCharacter *pBaseTarget )
	{
		if ( pBaseTarget->GetTeamNumber() == GetTeamNumber() )
			return;

		if ( !pTarget )
			return;

		if ( pTarget->m_Shared.IsInvulnerable() )
			return;

		if ( pTarget->m_Shared.InCond( TF_COND_PHASE ) || pTarget->m_Shared.InCond( TF_COND_PASSTIME_INTERCEPTION ) )
			return;

		// Stun the target
		pTarget->m_Shared.StunPlayer( 0.5, 0.5, TF_STUN_MOVEMENT, pThrower );

		Vector vecDir = pBaseTarget->WorldSpaceCenter() - GetAbsOrigin();
		VectorNormalize( vecDir );
		Vector vecForward, vecRight, vecUp;
		AngleVectors( pTarget->GetAnimRenderAngles(), &vecForward, &vecRight, &vecUp );
		vecDir += ( vecUp * 0.5f );

		if ( pTarget )
		{
			pTarget->AddHalloweenKartPushEvent( pThrower, this, pThrower->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ), vecDir * tf_halloween_kart_normal_speed.GetFloat() * 1.10f, 45.0f );

			const char* pszEffectName = GetTeamNumber() == TF_TEAM_RED ? "spell_batball_red" : "spell_batball_blue";
			DispatchParticleEffect( pszEffectName, PATTACH_ABSORIGIN_FOLLOW, pTarget );
		}
	}
#endif
};


IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SpellKartBats, DT_TFProjectile_SpellKartBats )
BEGIN_NETWORK_TABLE( CTFProjectile_SpellKartBats, DT_TFProjectile_SpellKartBats )
END_NETWORK_TABLE()

LINK_ENTITY_TO_CLASS( tf_projectile_spellkartbats, CTFProjectile_SpellKartBats );
PRECACHE_WEAPON_REGISTER( tf_projectile_spellkartbats );

//// *************************************************************************************************************************
//class CTFProjectile_SpellKartPumpkin : public CTFProjectile_SpellPumpkin
//{
//public:
//	DECLARE_CLASS( CTFProjectile_SpellKartPumpkin, CTFProjectile_SpellPumpkin );
//	DECLARE_NETWORKCLASS();
//
//#ifdef GAME_DLL
//	virtual bool InitialExplodeEffects( CTFPlayer *pThrower, const trace_t *pTrace ) OVERRIDE
//	{
//		// Spawn a pumkin bomb here
//		// Set the angles to what I want
//		QAngle angle( 0, RandomFloat( 0, 360 ), 0 );
//		CTFPumpkinBomb *pGrenade = static_cast<CTFPumpkinBomb*>( CBaseEntity::CreateNoSpawn( "tf_pumpkin_bomb", GetAbsOrigin(), angle, NULL ) );
//		if ( pGrenade )
//		{
//			pGrenade->SetInitParams( 0.60, 80.0f, 200.0f, GetTeamNumber(), 40.0f + RandomFloat( 0, 1.0f ) );
//			DispatchSpawn( pGrenade );
//			pGrenade->SetSpell( true );
//			pGrenade->TakeDamage( CTakeDamageInfo( pThrower, pThrower, 10.f, DMG_CRUSH ) );
//		}
//
//		if ( TFGameRules() )
//		{
//			TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_PLAYER_SPELL_MIRV, ( pThrower->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED );
//		}
//
//		return true;
//	}
//#endif
//};
//
//IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SpellKartPumpkin, DT_TFProjectile_SpellKartPumpkin )
//BEGIN_NETWORK_TABLE( CTFProjectile_SpellKartPumpkin, DT_TFProjectile_SpellKartPumpkin )
//END_NETWORK_TABLE()
//
//LINK_ENTITY_TO_CLASS( tf_projectile_spellkartpumpkin, CTFProjectile_SpellKartPumpkin );
//PRECACHE_WEAPON_REGISTER( tf_projectile_spellkartpumpkin );
//
////*************
//// *************************************************************************************************************************
//class CTFProjectile_SpellKartMirv : public CTFProjectile_SpellMirv
//{
//public:
//	DECLARE_CLASS( CTFProjectile_SpellKartMirv, CTFProjectile_SpellMirv );
//	DECLARE_NETWORKCLASS();
//
//#ifdef GAME_DLL
//		
//	virtual bool InitialExplodeEffects( CTFPlayer *pThrower, const trace_t *pTrace ) OVERRIDE
//	{
//		// Spawn a tonne of extra grenades (mirv style)
//		CTFSpellBook *pSpellBook = dynamic_cast<CTFSpellBook*>( pThrower->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) );
//		if ( !pSpellBook )
//			return false;
//
//		// Create bomblets
//		Vector offset = Vector( 0, -100, 400 );
//
//		for ( int i = 0; i < 6; i++ )
//		{
//			AngularImpulse angVelocity = AngularImpulse( 0, 0, RandomFloat( 100, 300 ) );
//
//			switch ( i )
//			{
//			case 0: offset = Vector( 75, 110, 400 ); break;
//			case 1: offset = Vector( 75, -110, 400 ); break;
//			case 2: offset = Vector( -75, 110, 400 ); break;
//			case 3: offset = Vector( -75, -110, 400 ); break;
//			case 4: offset = Vector( 135, 0, 400 ); break;
//			case 5: offset = Vector( -135, 0, 400 ); break;
//			}
//
//			CTFProjectile_SpellPumpkin *pGrenade = static_cast<CTFProjectile_SpellPumpkin*>( CBaseEntity::CreateNoSpawn( "tf_projectile_spellkartpumpkin", GetAbsOrigin(), pThrower->EyeAngles(), pThrower ) );
//			if ( pGrenade )
//			{
//				// Set the pipebomb mode before calling spawn, so the model & associated vphysics get setup properly.
//				pGrenade->SetPipebombMode();
//				DispatchSpawn( pGrenade );
//				pGrenade->InitGrenade( offset, angVelocity, pThrower, pSpellBook->GetTFWpnData() );
//				pGrenade->m_flFullDamage = 0;
//				pGrenade->ApplyLocalAngularVelocityImpulse( angVelocity );
//				pGrenade->SetDetonateTimerLength( 2.0f + 0.05f * i );
//			}
//		}
//
//		if ( TFGameRules() )
//		{
//			TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_PLAYER_SPELL_MIRV, ( pThrower->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED );
//		}
//
//		return true;
//	}
//#endif
//};
//
//IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SpellKartMirv, DT_TFProjectile_SpellKartMirv )
//BEGIN_NETWORK_TABLE( CTFProjectile_SpellKartMirv, DT_TFProjectile_SpellKartMirv )
//END_NETWORK_TABLE()
//
//LINK_ENTITY_TO_CLASS( tf_projectile_spellkartmirv, CTFProjectile_SpellKartMirv );
//PRECACHE_WEAPON_REGISTER( tf_projectile_spellkartmirv );