//========= 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 #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; iGetViewport(); 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( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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; iplane.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 ( 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( 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( 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( 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( 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 );