summaryrefslogtreecommitdiff
path: root/game/shared/tf/halloween/tf_weapon_spellbook.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/shared/tf/halloween/tf_weapon_spellbook.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/shared/tf/halloween/tf_weapon_spellbook.cpp')
-rw-r--r--game/shared/tf/halloween/tf_weapon_spellbook.cpp3547
1 files changed, 3547 insertions, 0 deletions
diff --git a/game/shared/tf/halloween/tf_weapon_spellbook.cpp b/game/shared/tf/halloween/tf_weapon_spellbook.cpp
new file mode 100644
index 0000000..723fa78
--- /dev/null
+++ b/game/shared/tf/halloween/tf_weapon_spellbook.cpp
@@ -0,0 +1,3547 @@
+//========= 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 ); \ No newline at end of file