You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3547 lines
112 KiB
3547 lines
112 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//============================================================================= |
|
|
|
#include "cbase.h" |
|
#include "tf_weapon_spellbook.h" |
|
#include "decals.h" |
|
#include "tf_gamerules.h" |
|
#include "tf_pumpkin_bomb.h" |
|
|
|
// Client specific. |
|
#ifdef CLIENT_DLL |
|
#include "c_basedoor.h" |
|
#include "c_tf_player.h" |
|
#include "IEffects.h" |
|
#include "bone_setup.h" |
|
#include "c_tf_gamestats.h" |
|
#include "iclientmode.h" |
|
#include <vgui_controls/AnimationController.h> |
|
#include "econ_notifications.h" |
|
#include "gc_clientsystem.h" |
|
#include "tf_logic_halloween_2014.h" |
|
#include "tf_hud_itemeffectmeter.h" |
|
extern void AddSubKeyNamed( KeyValues *pKeys, const char *pszName ); |
|
// Server specific. |
|
#else |
|
#include "doors.h" |
|
#include "tf_player.h" |
|
#include "tf_ammo_pack.h" |
|
#include "tf_gamestats.h" |
|
#include "ilagcompensationmanager.h" |
|
#include "collisionutils.h" |
|
#include "particle_parse.h" |
|
#include "tf_projectile_base.h" |
|
#include "tf_gamerules.h" |
|
#include "tf_fx.h" |
|
#include "takedamageinfo.h" |
|
#include "halloween/zombie/zombie.h" |
|
#include "halloween/eyeball_boss/eyeball_boss.h" |
|
#include "halloween/halloween_base_boss.h" |
|
#include "entity_healthkit.h" |
|
#include "eyeball_boss/teleport_vortex.h" |
|
#include "in_buttons.h" |
|
#include "halloween/merasmus/merasmus.h" |
|
#include "tf_weapon_grenade_pipebomb.h" |
|
#include "tf_obj_dispenser.h" |
|
#endif |
|
|
|
ConVar tf_test_spellindex( "tf_test_spellindex", "-1", FCVAR_CHEAT | FCVAR_REPLICATED, "Set to index to always get a specific spell" ); |
|
#ifdef GAME_DLL |
|
ConVar tf_halloween_kart_rocketspell_speed( "tf_halloween_kart_rocketspell_speed", "1500", FCVAR_CHEAT ); |
|
ConVar tf_halloween_kart_rocketspell_lifetime( "tf_halloween_kart_rocketspell_lifetime", "0.5f", FCVAR_CHEAT ); |
|
ConVar tf_halloween_kart_rocketspell_force( "tf_halloween_kart_rocketspell_force", "900.0f", FCVAR_CHEAT ); |
|
#endif |
|
|
|
extern ConVar tf_eyeball_boss_hover_height; |
|
extern ConVar tf_halloween_kart_normal_speed; |
|
extern ConVar tf_halloween_kart_dash_speed; |
|
//============================================================================= |
|
// |
|
// Weapon Tables |
|
// |
|
|
|
// SpellBook -- |
|
IMPLEMENT_NETWORKCLASS_ALIASED( TFSpellBook, DT_TFWeaponSpellBook ) |
|
|
|
BEGIN_NETWORK_TABLE( CTFSpellBook, DT_TFWeaponSpellBook ) |
|
#ifdef CLIENT_DLL |
|
RecvPropInt( RECVINFO( m_iSelectedSpellIndex ) ), |
|
RecvPropInt( RECVINFO( m_iSpellCharges ) ), |
|
RecvPropFloat( RECVINFO( m_flTimeNextSpell ) ), |
|
RecvPropBool( RECVINFO( m_bFiredAttack ) ), |
|
#else |
|
SendPropInt( SENDINFO( m_iSelectedSpellIndex ) ), |
|
SendPropInt( SENDINFO( m_iSpellCharges ) ), |
|
SendPropFloat( SENDINFO( m_flTimeNextSpell ) ), |
|
SendPropBool( SENDINFO( m_bFiredAttack ) ), |
|
#endif |
|
|
|
END_NETWORK_TABLE() |
|
|
|
BEGIN_PREDICTION_DATA( CTFSpellBook ) |
|
END_PREDICTION_DATA() |
|
|
|
LINK_ENTITY_TO_CLASS( tf_weapon_spellbook, CTFSpellBook ); |
|
PRECACHE_WEAPON_REGISTER( tf_weapon_spellbook ); |
|
// -- SpellBook |
|
|
|
#define SPELL_EMPTY -1 |
|
#define SPELL_UNKNOWN -2 |
|
#define SPELL_BOXING_GLOVE "models/props_halloween/hwn_spell_boxing_glove.mdl" |
|
|
|
//============================================================================= |
|
// Spell Data Structures |
|
//============================================================================= |
|
enum SpellType_t |
|
{ |
|
SPELL_ROCKET, |
|
SPELL_JAR, // Explodes on Contact |
|
SPELL_SELF, |
|
}; |
|
|
|
struct spell_data_t |
|
{ |
|
spell_data_t( |
|
const char *pSpellUiName, |
|
int iSpellCharges, |
|
SpellType_t eSpelltype, |
|
const char *pSpellEntityName, |
|
bool (*pCastSpell)(CTFPlayer*), |
|
const char *pszCastSound, |
|
float flSpeedScale, |
|
int iCastContext, |
|
int iSpellContext, |
|
const char *pIconName, |
|
bool bAutoCast = false |
|
) { |
|
m_pSpellUiName = pSpellUiName; |
|
m_eSpellType = eSpelltype; |
|
m_pSpellEntityName = pSpellEntityName; |
|
m_iSpellCharges = iSpellCharges; |
|
m_pCastSpell = pCastSpell; |
|
m_pszCastSound = pszCastSound; |
|
m_flSpeedScale = flSpeedScale; |
|
m_iCastContext = iCastContext; |
|
m_iSpellContext = iSpellContext; |
|
m_pIconName = pIconName; |
|
m_bAutoCast = bAutoCast; |
|
} |
|
|
|
const char * m_pSpellUiName; |
|
SpellType_t m_eSpellType; |
|
const char *m_pSpellEntityName; |
|
int m_iSpellCharges; |
|
const char *m_pszCastSound; |
|
float m_flSpeedScale; |
|
int m_iCastContext; // context for the spell caster |
|
int m_iSpellContext; // context for enemies who witness the spell |
|
|
|
bool (*m_pCastSpell)(CTFPlayer*); |
|
const char *m_pIconName; |
|
bool m_bAutoCast; |
|
}; |
|
|
|
static const spell_data_t g_NormalSpellList[] = |
|
{ |
|
spell_data_t( "#TF_Spell_Fireball", 2, SPELL_ROCKET, "tf_projectile_spellfireball", NULL, "Halloween.spell_fireball_cast", 1.f,MP_CONCEPT_PLAYER_CAST_BOMB_HEAD_CURSE, MP_CONCEPT_PLAYER_SPELL_BOMB_HEAD_CURSE, "spellbook_fireball" ), |
|
spell_data_t( "#TF_Spell_Bats", 2, SPELL_JAR, "tf_projectile_spellbats", NULL, "Halloween.spell_bat_cast", 1.f, MP_CONCEPT_PLAYER_CAST_MERASMUS_ZAP, MP_CONCEPT_PLAYER_SPELL_MERASMUS_ZAP, "spellbook_bats" ), |
|
spell_data_t( "#TF_Spell_OverHeal", 1, SPELL_SELF, NULL, CTFSpellBook::CastSelfHeal, "Halloween.spell_overheal", 1.f, MP_CONCEPT_PLAYER_CAST_SELF_HEAL, MP_CONCEPT_PLAYER_SPELL_SELF_HEAL, "spellbook_overheal" ), |
|
spell_data_t( "#TF_Spell_MIRV", 1, SPELL_JAR, "tf_projectile_spellmirv", NULL, "Halloween.spell_mirv_cast", 1.f, MP_CONCEPT_PLAYER_CAST_MIRV, MP_CONCEPT_PLAYER_SPELL_MIRV, "spellbook_mirv" ), |
|
spell_data_t( "#TF_Spell_BlastJump", 2, SPELL_SELF, NULL, CTFSpellBook::CastRocketJump, "Halloween.spell_blastjump", 1.f, MP_CONCEPT_PLAYER_CAST_BLAST_JUMP, MP_CONCEPT_PLAYER_SPELL_BLAST_JUMP, "spellbook_blastjump"), |
|
spell_data_t( "#TF_Spell_Stealth", 1, SPELL_SELF, NULL, CTFSpellBook::CastSelfStealth, "Halloween.spell_stealth", 1.f, MP_CONCEPT_PLAYER_CAST_STEALTH, MP_CONCEPT_PLAYER_SPELL_STEALTH, "spellbook_stealth"), |
|
spell_data_t( "#TF_Spell_Teleport", 2, SPELL_JAR, "tf_projectile_spelltransposeteleport", NULL, "Halloween.spell_teleport", 1.f, MP_CONCEPT_PLAYER_CAST_TELEPORT, MP_CONCEPT_PLAYER_SPELL_TELEPORT, "spellbook_teleport"), |
|
}; |
|
|
|
static const int g_NavMeshSpells = 2; // Number of spells in this list that require a navmesh, they must be at the end of this array |
|
static const spell_data_t g_RareSpellList[] = |
|
{ |
|
spell_data_t( "#TF_Spell_LightningBall", 1, SPELL_ROCKET, "tf_projectile_lightningorb", NULL, "Halloween.spell_lightning_cast", 0.4f, MP_CONCEPT_PLAYER_CAST_LIGHTNING_BALL, MP_CONCEPT_PLAYER_SPELL_LIGHTNING_BALL, "spellbook_lightning"), |
|
spell_data_t( "#TF_Spell_Athletic", 1, SPELL_SELF, NULL, CTFSpellBook::CastSelfSpeedBoost, "Halloween.spell_athletic", 1.f, MP_CONCEPT_PLAYER_CAST_MOVEMENT_BUFF, MP_CONCEPT_PLAYER_SPELL_MOVEMENT_BUFF, "spellbook_athletic"), |
|
spell_data_t( "#TF_Spell_Meteor", 1, SPELL_JAR, "tf_projectile_spellmeteorshower", NULL, "Halloween.spell_meteor_cast", 1.f, MP_CONCEPT_PLAYER_CAST_METEOR_SWARM, MP_CONCEPT_PLAYER_SPELL_METEOR_SWARM, "spellbook_meteor"), |
|
spell_data_t( "#TF_Spell_SpawnBoss", 1, SPELL_JAR, "tf_projectile_spellspawnboss", NULL, "Halloween.Merasmus_Spell", 1.f, MP_CONCEPT_PLAYER_CAST_MONOCULOUS, MP_CONCEPT_PLAYER_SPELL_MONOCULOUS, "spellbook_boss"), |
|
spell_data_t( "#TF_Spell_SkeletonHorde", 1, SPELL_JAR, "tf_projectile_spellspawnhorde", NULL, "Halloween.spell_skeleton_horde_cast", 1.f, MP_CONCEPT_PLAYER_CAST_SKELETON_HORDE, MP_CONCEPT_PLAYER_SPELL_SKELETON_HORDE, "spellbook_skeleton"), |
|
}; |
|
|
|
static const spell_data_t g_KartSpellList[] = |
|
{ |
|
// Kart Spells |
|
spell_data_t( "#TF_Spell_Fireball", 1, SPELL_ROCKET, "tf_projectile_spellkartorb", NULL, "Halloween.spell_fireball_cast", 1.f, MP_CONCEPT_PLAYER_CAST_MERASMUS_ZAP, MP_CONCEPT_PLAYER_SPELL_MERASMUS_ZAP, "../hud/Punchglove_icon" ), |
|
spell_data_t( "#TF_Spell_BlastJump", 1, SPELL_SELF, NULL, CTFSpellBook::CastKartRocketJump, "Halloween.spell_blastjump", 1.f, MP_CONCEPT_PLAYER_CAST_BLAST_JUMP, MP_CONCEPT_PLAYER_SPELL_BLAST_JUMP, "../hud/Parachute_icon"), |
|
spell_data_t( "#TF_Spell_OverHeal", 1, SPELL_SELF, NULL, CTFSpellBook::CastKartUber, "Halloween.spell_overheal", 1.f, MP_CONCEPT_PLAYER_CAST_SELF_HEAL, MP_CONCEPT_PLAYER_SPELL_SELF_HEAL, "spellbook_overheal" ), |
|
spell_data_t( "#TF_Spell_BombHead", 1, SPELL_SELF, NULL, CTFSpellBook::CastKartBombHead, "Halloween.spell_overheal", 1.f, MP_CONCEPT_PLAYER_CAST_FIREBALL, MP_CONCEPT_PLAYER_SPELL_FIREBALL, "../hud/bombhead_icon" ), |
|
}; |
|
|
|
// Do not allow all spells in doomsday |
|
static const int g_doomsdayNormalSpellIndexList[] = |
|
{ |
|
0, //Fireball |
|
0, //Fireball x2 |
|
2, //overheal |
|
4, //Jump |
|
5, //Stealth |
|
}; |
|
|
|
static const int g_doomsdayRareSpellIndexList[] = |
|
{ |
|
ARRAYSIZE( g_NormalSpellList ) + 0, // Lightning |
|
ARRAYSIZE( g_NormalSpellList ) + 1, // Mini |
|
ARRAYSIZE( g_NormalSpellList ) + 2, // Meteor |
|
ARRAYSIZE( g_NormalSpellList ) + 0, // Lightning |
|
ARRAYSIZE( g_NormalSpellList ) + 1, // Mini |
|
ARRAYSIZE( g_NormalSpellList ) + 2, // Meteor |
|
ARRAYSIZE( g_NormalSpellList ) + 3 // Boss / Monoculus. Smaller chance |
|
}; |
|
|
|
// Regular SpellList |
|
// teleport and summons removed |
|
static const int g_generalSpellIndexList[] = |
|
{ |
|
0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, |
|
ARRAYSIZE ( g_NormalSpellList ) + 0, |
|
ARRAYSIZE ( g_NormalSpellList ) + 1, |
|
ARRAYSIZE ( g_NormalSpellList ) + 2 |
|
}; |
|
|
|
int GetTotalSpellCount( CTFPlayer *pPlayer ) |
|
{ |
|
int iSpellCount = ARRAYSIZE( g_NormalSpellList ) + ARRAYSIZE( g_RareSpellList ); |
|
if ( pPlayer && pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) |
|
{ |
|
iSpellCount += ARRAYSIZE( g_KartSpellList ); |
|
} |
|
return iSpellCount; |
|
} |
|
|
|
bool IsRareSpell( int iSpellIndex ) |
|
{ |
|
if ( tf_test_spellindex.GetInt() > 0 ) |
|
{ |
|
iSpellIndex = tf_test_spellindex.GetInt(); |
|
} |
|
|
|
return ( ( iSpellIndex >= ARRAYSIZE( g_NormalSpellList ) ) && ( iSpellIndex < ARRAYSIZE( g_NormalSpellList ) + ARRAYSIZE( g_RareSpellList ) ) ); |
|
} |
|
|
|
const spell_data_t* GetSpellData( int iSpellIndex ) |
|
{ |
|
if ( tf_test_spellindex.GetInt() > -1 ) |
|
{ |
|
iSpellIndex = tf_test_spellindex.GetInt(); |
|
} |
|
|
|
if ( iSpellIndex < 0 ) |
|
return NULL; |
|
|
|
const int nNormalSpellCount = ARRAYSIZE( g_NormalSpellList ); |
|
if ( iSpellIndex < nNormalSpellCount ) |
|
return &g_NormalSpellList[ iSpellIndex ]; |
|
|
|
const int nRareSpellRange = nNormalSpellCount + ARRAYSIZE( g_RareSpellList ); |
|
if ( iSpellIndex < nRareSpellRange ) |
|
return &g_RareSpellList[ iSpellIndex - nNormalSpellCount ]; |
|
|
|
const int nKartSpellRange = nRareSpellRange + ARRAYSIZE( g_KartSpellList ); |
|
if ( iSpellIndex < nKartSpellRange ) |
|
return &g_KartSpellList[ iSpellIndex - nRareSpellRange]; |
|
|
|
return NULL; |
|
} |
|
|
|
int GetSpellIndexFromContext( int iContext ) |
|
{ |
|
const int nNormalSpellCount = ARRAYSIZE( g_NormalSpellList ); |
|
for ( int i=0; i<nNormalSpellCount; ++i ) |
|
{ |
|
if ( g_NormalSpellList[i].m_iSpellContext == iContext ) |
|
{ |
|
return i; |
|
} |
|
} |
|
|
|
const int nRareSpellCount = ARRAYSIZE( g_RareSpellList ); |
|
for ( int i=0; i<nRareSpellCount; ++i ) |
|
{ |
|
if ( g_RareSpellList[i].m_iSpellContext == iContext ) |
|
{ |
|
return i + nNormalSpellCount; |
|
} |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
//============================================================================= |
|
#ifdef CLIENT_DLL |
|
//============================================================================= |
|
// Ui Hud |
|
//============================================================================= |
|
extern ConVar cl_hud_minmode; |
|
|
|
DECLARE_HUDELEMENT_DEPTH( CHudSpellMenu, 2 ); |
|
CHudSpellMenu::CHudSpellMenu( const char *pElementName ) : CHudElement( pElementName ), BaseClass ( NULL, "HudSpellMenu" ) |
|
{ |
|
Panel *pParent = g_pClientMode->GetViewport(); |
|
SetParent( pParent ); |
|
|
|
SetHiddenBits( HIDEHUD_MISCSTATUS | HIDEHUD_HEALTH | HIDEHUD_PLAYERDEAD ); |
|
|
|
m_iNextRollTime = 0; |
|
m_flRollTickGap = 0.05f; |
|
m_bTickSoundA = false; |
|
|
|
m_bKillstreakMeterDrawing = false; |
|
|
|
m_pSpellIcon = new vgui::ImagePanel( this, "SpellIcon" ); |
|
m_pKeyBinding = new CExLabel( this, "ActionText", "" ); |
|
|
|
ListenForGameEvent( "inventory_updated" ); |
|
ListenForGameEvent( "localplayer_respawn" ); |
|
ListenForGameEvent( "localplayer_changeclass" ); |
|
ListenForGameEvent( "post_inventory_application" ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CHudSpellMenu::ApplySchemeSettings( vgui::IScheme *pScheme ) |
|
{ |
|
BaseClass::ApplySchemeSettings( pScheme ); |
|
|
|
KeyValues *pConditions = NULL; |
|
if ( m_bKillstreakMeterDrawing ) |
|
{ |
|
pConditions = new KeyValues( "conditions" ); |
|
if ( pConditions ) |
|
{ |
|
AddSubKeyNamed( pConditions, "if_killstreak_visible" ); |
|
} |
|
} |
|
|
|
// load control settings... |
|
LoadControlSettings( "resource/UI/HudSpellSelection.res", NULL, NULL, pConditions ); |
|
SetVisible( false ); |
|
UpdateSpellText( -1, -1 ); |
|
|
|
if ( pConditions ) |
|
{ |
|
pConditions->deleteThis(); |
|
} |
|
} |
|
|
|
//============================================================================= |
|
void CHudSpellMenu::OnTick( void ) |
|
{ |
|
bool bKillstreakMeterDrawing = false; |
|
CHudItemEffectMeter *pMeter = NULL; |
|
for ( int i = 0; i < IHudItemEffectMeterAutoList::AutoList().Count(); ++i ) |
|
{ |
|
pMeter = static_cast<CHudItemEffectMeter*>( IHudItemEffectMeterAutoList::AutoList()[i] ); |
|
if ( pMeter->IsKillstreakMeter() ) // we found the killstreak meter |
|
{ |
|
if ( pMeter->IsEnabled() ) |
|
{ |
|
bKillstreakMeterDrawing = true; |
|
} |
|
break; |
|
} |
|
} |
|
|
|
if ( m_bKillstreakMeterDrawing != bKillstreakMeterDrawing ) |
|
{ |
|
m_bKillstreakMeterDrawing = bKillstreakMeterDrawing; |
|
InvalidateLayout( false, true ); |
|
} |
|
|
|
vgui::ivgui()->RemoveTickSignal( GetVPanel() ); |
|
} |
|
|
|
//============================================================================= |
|
void CHudSpellMenu::FireGameEvent( IGameEvent * event ) |
|
{ |
|
if ( FStrEq( event->GetName(), "post_inventory_application" ) || |
|
FStrEq( event->GetName(), "localplayer_respawn" ) || |
|
FStrEq( event->GetName(), "localplayer_changeclass" ) || |
|
FStrEq( event->GetName(), "inventory_updated" ) ) |
|
{ |
|
vgui::ivgui()->AddTickSignal( GetVPanel(), 10 ); |
|
} |
|
} |
|
|
|
//============================================================================= |
|
bool CHudSpellMenu::ShouldDraw( void ) |
|
{ |
|
if ( TFGameRules() && TFGameRules()->IsUsingSpells() ) |
|
{ |
|
if ( CTFMinigameLogic::GetMinigameLogic() && CTFMinigameLogic::GetMinigameLogic()->GetActiveMinigame() && ( TFGameRules()->State_Get() != GR_STATE_RND_RUNNING ) ) |
|
return false; |
|
|
|
C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer(); |
|
if ( pPlayer && pPlayer->IsAlive() && !pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) ) |
|
{ |
|
CTFSpellBook *pSpellBook = dynamic_cast<CTFSpellBook*>( pPlayer->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) ); |
|
if ( pSpellBook ) |
|
{ |
|
UpdateSpellText( pSpellBook->m_iSelectedSpellIndex, pSpellBook->m_iSpellCharges ); |
|
return CHudElement::ShouldDraw(); |
|
} |
|
} |
|
} |
|
return false; |
|
} |
|
//============================================================================= |
|
void CHudSpellMenu::UpdateSpellText( int iSpellIndex, int iChargeCount ) |
|
{ |
|
if ( iSpellIndex == SPELL_EMPTY || ( iChargeCount <= 0 && iSpellIndex != SPELL_UNKNOWN ) ) |
|
{ |
|
SetDialogVariable( "counttext", "..." ); |
|
//SetDialogVariable( "selectedspell", g_pVGuiLocalize->Find( pSpellData->m_pSpellUiName ) ); |
|
m_pSpellIcon->SetImage( "spellbook_nospell" ); |
|
m_flRollTickGap = 0.01f; |
|
m_iNextRollTime = 0; |
|
m_pKeyBinding->SetVisible( false ); |
|
return; |
|
} |
|
|
|
m_pSpellIcon->SetVisible( true ); |
|
|
|
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); |
|
if ( !pLocalPlayer ) |
|
return; |
|
|
|
static wchar_t wLabel[256]; |
|
|
|
if ( iSpellIndex == SPELL_UNKNOWN ) |
|
{ |
|
if ( m_iNextRollTime > gpGlobals->curtime ) |
|
return; |
|
m_iNextRollTime = gpGlobals->curtime + m_flRollTickGap; |
|
m_flRollTickGap += 0.015f; |
|
static int s_iRandSpell = 0; |
|
s_iRandSpell = ( s_iRandSpell + 1 ) % GetTotalSpellCount( pLocalPlayer ); |
|
const spell_data_t *pSpellData = GetSpellData( s_iRandSpell ); |
|
SetDialogVariable( "counttext", "?" ); |
|
m_pSpellIcon->SetImage( pSpellData->m_pIconName ); |
|
|
|
pLocalPlayer->EmitSound( m_bTickSoundA ? "Halloween.spelltick_a" : "Halloween.spelltick_b" ); |
|
m_bTickSoundA = !m_bTickSoundA; |
|
|
|
m_iPrevSelectedSpell = SPELL_UNKNOWN; |
|
m_pKeyBinding->SetVisible( false ); |
|
} |
|
else |
|
{ |
|
m_flRollTickGap = 0.01f; |
|
m_iNextRollTime = 0; |
|
const spell_data_t *pSpellData = GetSpellData( iSpellIndex ); |
|
if ( pSpellData ) |
|
{ |
|
SetDialogVariable( "counttext", iChargeCount ); |
|
m_pSpellIcon->SetImage( pSpellData->m_pIconName ); |
|
if ( m_iPrevSelectedSpell != iSpellIndex && iSpellIndex != SPELL_EMPTY ) |
|
{ |
|
pLocalPlayer->EmitSound( "Halloween.spelltick_set" ); |
|
} |
|
m_iPrevSelectedSpell = iSpellIndex; |
|
m_pKeyBinding->SetVisible( !cl_hud_minmode.GetBool() ); |
|
|
|
// Action Key Text |
|
wchar_t wKeyReplaced[256]; |
|
UTIL_ReplaceKeyBindings( g_pVGuiLocalize->Find( "#TF_Spell_Action" ), 0, wKeyReplaced, sizeof( wKeyReplaced ) ); |
|
SetDialogVariable( "actiontext", wKeyReplaced ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// CEquipSpellbookNotification |
|
//----------------------------------------------------------------------------- |
|
void CEquipSpellbookNotification::Accept() |
|
{ |
|
m_bHasTriggered = true; |
|
|
|
CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory(); |
|
if ( !pLocalInv ) |
|
{ |
|
MarkForDeletion(); |
|
return; |
|
} |
|
|
|
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); |
|
if ( !pLocalPlayer ) |
|
{ |
|
MarkForDeletion(); |
|
return; |
|
} |
|
|
|
// try to equip non-stock-spellbook first |
|
static CSchemaItemDefHandle pItemDef_Spellbook( "Basic Spellbook" ); |
|
static CSchemaItemDefHandle pItemDef_Diary( "Secret Diary" ); |
|
static CSchemaItemDefHandle pItemDef_FancySpellbook( "Halloween Spellbook" ); |
|
|
|
Assert( pItemDef_Spellbook ); |
|
Assert( pItemDef_Diary ); |
|
Assert( pItemDef_FancySpellbook ); |
|
|
|
CEconItemView *pSpellBook = NULL; |
|
|
|
if ( pItemDef_Spellbook && pItemDef_Diary && pItemDef_FancySpellbook ) |
|
{ |
|
for ( int i = 0 ; i < pLocalInv->GetItemCount() ; ++i ) |
|
{ |
|
CEconItemView *pItem = pLocalInv->GetItem( i ); |
|
Assert( pItem ); |
|
if ( pItem->GetItemDefinition() == pItemDef_Spellbook |
|
|| pItem->GetItemDefinition() == pItemDef_Diary |
|
|| pItem->GetItemDefinition() == pItemDef_FancySpellbook |
|
) { |
|
pSpellBook = pItem; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
// Default item becomes a spellbook in this mode |
|
itemid_t iItemId = INVALID_ITEM_ID; |
|
if ( pSpellBook ) |
|
{ |
|
iItemId = pSpellBook->GetItemID(); |
|
} |
|
|
|
TFInventoryManager()->EquipItemInLoadout( pLocalPlayer->GetPlayerClass()->GetClassIndex(), LOADOUT_POSITION_ACTION, iItemId ); |
|
|
|
// Tell the GC to tell server that we should respawn if we're in a respawn room |
|
GCSDK::CGCMsg< GCSDK::MsgGCEmpty_t > msg( k_EMsgGCRespawnPostLoadoutChange ); |
|
GCClientSystem()->BSendMessage( msg ); |
|
|
|
MarkForDeletion(); |
|
} |
|
|
|
//=========================================================================================== |
|
void CEquipSpellbookNotification::UpdateTick() |
|
{ |
|
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); |
|
if ( pLocalPlayer ) |
|
{ |
|
CTFSpellBook *pSpellBook = dynamic_cast<CTFSpellBook*>( pLocalPlayer->Weapon_OwnsThisID( TF_WEAPON_SPELLBOOK ) ); |
|
if ( pSpellBook ) |
|
{ |
|
MarkForDeletion(); |
|
} |
|
} |
|
} |
|
#endif // CLIENT_DLL |
|
|
|
//=========================================================================================== |
|
// |
|
// CTFSpellBook |
|
// |
|
//=========================================================================================== |
|
CTFSpellBook::CTFSpellBook() |
|
{ |
|
m_iSelectedSpellIndex = -1; |
|
m_iSpellCharges = 0; |
|
m_flTimeNextSpell = 0; |
|
m_bFiredAttack = false; |
|
#ifdef CLIENT_DLL |
|
m_flTimeNextErrorSound = 0; |
|
m_hHandEffect = NULL; |
|
m_hHandEffectWeapon = NULL; |
|
#endif // CLIENT_DLL |
|
|
|
#ifdef GAME_DLL |
|
m_pStoredLastWpn = NULL; |
|
m_iPreviouslyCastSpell = -1; |
|
#endif // GAME_DLL |
|
} |
|
|
|
void CTFSpellBook::Precache() |
|
{ |
|
PrecacheScriptSound( "Halloween.Merasmus_Spell" ); |
|
PrecacheScriptSound( "Weapon_SniperRailgun_Large.SingleCrit" ); |
|
PrecacheScriptSound( "Halloween.spelltick_a" ); |
|
PrecacheScriptSound( "Halloween.spelltick_b" ); |
|
PrecacheScriptSound( "Halloween.spelltick_set" ); |
|
|
|
PrecacheScriptSound( "Halloween.spell_athletic" ); |
|
PrecacheScriptSound( "Halloween.spell_bat_cast" ); |
|
PrecacheScriptSound( "Halloween.spell_bat_impact" ); |
|
PrecacheScriptSound( "Halloween.spell_blastjump" ); |
|
PrecacheScriptSound( "Halloween.spell_fireball_cast" ); |
|
PrecacheScriptSound( "Halloween.spell_fireball_impact" ); |
|
PrecacheScriptSound( "Halloween.spell_lightning_cast" ); |
|
PrecacheScriptSound( "Halloween.spell_lightning_impact" ); |
|
PrecacheScriptSound( "Halloween.spell_meteor_cast" ); |
|
PrecacheScriptSound( "Halloween.spell_meteor_impact" ); |
|
PrecacheScriptSound( "Halloween.spell_mirv_cast" ); |
|
PrecacheScriptSound( "Halloween.spell_mirv_explode_primary" ); |
|
PrecacheScriptSound( "Halloween.spell_mirv_explode_secondary" ); |
|
PrecacheScriptSound( "Halloween.spell_skeleton_horde_cast" ); |
|
PrecacheScriptSound( "Halloween.spell_skeleton_horde_rise" ); |
|
PrecacheScriptSound( "Halloween.spell_spawn_boss" ); |
|
PrecacheScriptSound( "Halloween.spell_stealth" ); |
|
PrecacheScriptSound( "Halloween.spell_teleport" ); |
|
PrecacheScriptSound( "Halloween.spell_overheal" ); |
|
|
|
PrecacheParticleSystem( "merasmus_zap" ); |
|
PrecacheParticleSystem( "spell_cast_wheel_red" ); |
|
PrecacheParticleSystem( "spell_cast_wheel_blue" ); |
|
PrecacheParticleSystem( "Explosion_bubbles" ); |
|
PrecacheParticleSystem( "ExplosionCore_buildings" ); |
|
PrecacheParticleSystem( "water_splash01" ); |
|
PrecacheParticleSystem( "healshot_trail_blue" ); |
|
PrecacheParticleSystem( "healshot_trail_red" ); |
|
PrecacheParticleSystem( "xms_snowburst" ); |
|
PrecacheParticleSystem( "bomibomicon_ring" ); |
|
PrecacheParticleSystem( "bombinomicon_burningdebris" ); |
|
PrecacheParticleSystem( "merasmus_tp_bits" ); |
|
PrecacheParticleSystem( "spell_fireball_tendril_parent_red" ); |
|
PrecacheParticleSystem( "spell_fireball_tendril_parent_blue" ); |
|
PrecacheParticleSystem( "spell_fireball_small_blue" ); |
|
PrecacheParticleSystem( "spell_fireball_small_red" ); |
|
PrecacheParticleSystem( "spell_lightningball_parent_blue" ); |
|
PrecacheParticleSystem( "spell_lightningball_parent_red" ); |
|
PrecacheParticleSystem( "spell_lightningball_hit_blue" ); |
|
PrecacheParticleSystem( "spell_lightningball_hit_red" ); |
|
|
|
PrecacheParticleSystem( "eyeboss_tp_vortex" ); |
|
PrecacheParticleSystem( "spell_overheal_red" ); |
|
PrecacheParticleSystem( "spell_overheal_blue" ); |
|
PrecacheParticleSystem( "spell_teleport_red" ); |
|
PrecacheParticleSystem( "spell_teleport_blue" ); |
|
PrecacheParticleSystem( "spell_batball_red" ); |
|
PrecacheParticleSystem( "spell_batball_blue" ); |
|
PrecacheParticleSystem( "spell_batball_throw_red" ); |
|
PrecacheParticleSystem( "spell_batball_throw_blue" ); |
|
PrecacheParticleSystem( "spell_batball_impact_red" ); |
|
PrecacheParticleSystem( "spell_batball_impact_blue" ); |
|
|
|
PrecacheParticleSystem( "spell_pumpkin_mirv_goop_red" ); |
|
PrecacheParticleSystem( "spell_pumpkin_mirv_goop_blue" ); |
|
PrecacheParticleSystem( "spell_skeleton_goop_green" ); |
|
|
|
PrecacheParticleSystem( "spellbook_rainbow" ); |
|
PrecacheParticleSystem( "spellbook_major_burning" ); |
|
PrecacheParticleSystem( "spellbook_minor_burning" ); |
|
PrecacheModel( "models/props_mvm/mvm_human_skull_collide.mdl" ); |
|
PrecacheModel( "models/props_lakeside_event/bomb_temp_hat.mdl" ); |
|
PrecacheModel( SPELL_BOXING_GLOVE ); |
|
PrecacheModel( "models/props_halloween/bombonomicon.mdl" ); // bomb head spell |
|
PrecacheParticleSystem( "halloween_rockettrail" ); |
|
PrecacheParticleSystem( "ExplosionCore_MidAir" ); |
|
|
|
#ifdef GAME_DLL |
|
CEyeballBoss::PrecacheEyeballBoss(); |
|
CZombie::PrecacheZombie(); |
|
#endif // GAME_DLL |
|
|
|
BaseClass::Precache(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFSpellBook::PrimaryAttack() |
|
{ |
|
// cast spell |
|
if ( m_flTimeNextSpell > gpGlobals->curtime ) |
|
return; |
|
|
|
CTFPlayer *pPlayer = GetTFPlayerOwner(); |
|
if ( !pPlayer ) |
|
return; |
|
|
|
bool bCastSuccessful = false; |
|
|
|
bCastSuccessful = CanCastSpell( pPlayer ); |
|
|
|
if ( bCastSuccessful ) |
|
{ |
|
#ifdef GAME_DLL |
|
SpeakSpellConceptIfAllowed(); |
|
|
|
// We need to do this before PrimaryAttack so we use the right spell index |
|
if ( pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) |
|
{ |
|
CastKartSpell(); |
|
pPlayer->DoAnimationEvent( PLAYERANIMEVENT_CUSTOM_GESTURE, ACT_KART_ACTION_SHOOT ); |
|
} |
|
else |
|
{ |
|
CastSpell( pPlayer, m_iSelectedSpellIndex ); |
|
BaseClass::PrimaryAttack(); |
|
} |
|
#endif |
|
|
|
#ifdef GAME_DLL |
|
// set a default time cast time if none added |
|
if ( m_flTimeNextSpell < gpGlobals->curtime ) |
|
{ |
|
m_flTimeNextSpell = gpGlobals->curtime + 0.5f; |
|
} |
|
#endif // GAME_DLL |
|
} |
|
#ifdef CLIENT_DLL |
|
else |
|
{ |
|
if ( m_flTimeNextErrorSound < gpGlobals->curtime ) |
|
{ |
|
m_flTimeNextErrorSound = gpGlobals->curtime + 0.5f; |
|
pPlayer->EmitSound( "Player.DenyWeaponSelection" ); |
|
} |
|
} |
|
#endif // CLIENT_DLL |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFSpellBook::ItemBusyFrame( void ) |
|
{ |
|
#ifdef CLIENT_DLL |
|
if ( m_hHandEffectWeapon && m_hHandEffect ) |
|
return; |
|
|
|
CTFPlayer *pPlayer = GetTFPlayerOwner(); |
|
if ( !pPlayer ) |
|
return; |
|
|
|
if ( IsFirstPersonView() ) |
|
{ |
|
m_hHandEffectWeapon = pPlayer->GetViewModel(); |
|
} |
|
else |
|
{ |
|
m_hHandEffectWeapon = pPlayer; |
|
} |
|
|
|
if ( !m_hHandEffectWeapon ) |
|
return; |
|
|
|
if ( UsingViewModel() && !g_pClientMode->ShouldDrawViewModel() ) |
|
{ |
|
// Prevent effects when the ViewModel is hidden with r_drawviewmodel=0 |
|
return; |
|
} |
|
|
|
C_BaseAnimating* pBase = (C_BaseAnimating*)m_hHandEffectWeapon.Get(); |
|
int iAttachment = pBase->C_BaseAnimating::LookupAttachment( "effect_hand_R" ); |
|
|
|
// Start the muzzle flash, if a system hasn't already been started. |
|
if ( iAttachment > 0 ) |
|
{ |
|
const char *pszEffectName = GetHandEffect( GetAttributeContainer()->GetItem(), m_iSelectedSpellIndex >= ARRAYSIZE( g_NormalSpellList ) ); |
|
if ( pszEffectName ) |
|
{ |
|
m_hHandEffect = pBase->ParticleProp()->Create( pszEffectName, PATTACH_POINT_FOLLOW, iAttachment ); |
|
} |
|
} |
|
else |
|
{ |
|
if ( m_hHandEffect ) |
|
{ |
|
m_hHandEffectWeapon->ParticleProp()->StopEmission( m_hHandEffect ); |
|
m_hHandEffectWeapon = NULL; |
|
m_hHandEffect = NULL; |
|
} |
|
} |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFSpellBook::ItemHolsterFrame( void ) |
|
{ |
|
#ifdef CLIENT_DLL |
|
if ( !m_hHandEffectWeapon ) |
|
return; |
|
|
|
// Stop the muzzle flash. |
|
if ( m_hHandEffect ) |
|
{ |
|
m_hHandEffectWeapon->ParticleProp()->StopEmission( m_hHandEffect ); |
|
m_hHandEffectWeapon = NULL; |
|
m_hHandEffect = NULL; |
|
} |
|
#endif |
|
|
|
#ifdef GAME_DLL |
|
m_bFiredAttack = false; |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFSpellBook::ItemPostFrame( void ) |
|
{ |
|
BaseClass::ItemPostFrame(); |
|
|
|
#ifdef CLIENT_DLL |
|
// attempt to attack then switch back |
|
if ( !m_bFiredAttack && m_iSpellCharges > 0 ) |
|
{ |
|
PrimaryAttack(); |
|
|
|
if ( m_hHandEffect ) |
|
{ |
|
m_hHandEffectWeapon->ParticleProp()->StopEmission( m_hHandEffect ); |
|
m_hHandEffectWeapon = NULL; |
|
m_hHandEffect = NULL; |
|
} |
|
} |
|
#endif |
|
|
|
#ifdef GAME_DLL |
|
if ( tf_test_spellindex.GetInt() > -1 ) |
|
{ |
|
SetSelectedSpell( tf_test_spellindex.GetInt() ); |
|
} |
|
|
|
// attempt to attack then switch back |
|
if ( !m_bFiredAttack && m_iSpellCharges > 0 ) |
|
{ |
|
PrimaryAttack(); |
|
m_bFiredAttack = true; |
|
} |
|
else |
|
{ |
|
if ( m_flTimeNextSpell > gpGlobals->curtime ) |
|
return; |
|
|
|
CTFPlayer *pPlayer = GetTFPlayerOwner(); |
|
if ( !pPlayer ) |
|
return; |
|
|
|
if ( pPlayer->Weapon_Switch( pPlayer->GetLastWeapon() ) ) |
|
{ |
|
if ( m_pStoredLastWpn != NULL && pPlayer->Weapon_CanSwitchTo( m_pStoredLastWpn.Get() ) ) |
|
{ |
|
pPlayer->Weapon_SetLast( m_pStoredLastWpn.Get() ); |
|
m_pStoredLastWpn = NULL; |
|
} |
|
else |
|
{ |
|
pPlayer->Weapon_SetLast( NULL ); |
|
} |
|
m_bFiredAttack = false; |
|
} |
|
} |
|
#endif //GAME_DLL |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
/* static */ const char* CTFSpellBook::GetHandEffect( CEconItemView *pItem, int iTier ) |
|
{ |
|
// if fancy spellbook //1069 |
|
int defIndex = pItem->GetItemDefIndex(); |
|
if ( defIndex == 1069 ) |
|
{ |
|
if ( iTier > 0 ) |
|
{ |
|
return "spellbook_major_burning"; |
|
} |
|
else |
|
{ |
|
return "spellbook_minor_burning"; |
|
} |
|
} |
|
else if ( defIndex == 5605 ) // secret diary |
|
{ |
|
return "spellbook_rainbow"; |
|
} |
|
else // else Basic SpellBook |
|
{ |
|
if ( iTier > 0 ) |
|
{ |
|
return "spellbook_major_fire"; |
|
} |
|
else |
|
{ |
|
return "spellbook_minor_fire"; |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
bool CTFSpellBook::HasASpellWithCharges() |
|
{ |
|
return tf_test_spellindex.GetInt() > -1 || m_iSpellCharges > 0 || m_iSelectedSpellIndex == SPELL_UNKNOWN; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
bool CTFSpellBook::CanCastSpell( CTFPlayer *pPlayer ) |
|
{ |
|
if ( !pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART) && !pPlayer->CanAttack() ) |
|
return false; |
|
|
|
if ( pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_THRILLER ) ) |
|
return false; |
|
|
|
if ( tf_test_spellindex.GetInt() > -1 && tf_test_spellindex.GetInt() < GetTotalSpellCount( pPlayer ) ) |
|
return true; |
|
|
|
return m_iSpellCharges > 0 && m_iSelectedSpellIndex >= 0 && m_iSelectedSpellIndex < GetTotalSpellCount( pPlayer ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFSpellBook::PaySpellCost( CTFPlayer *pPlayer ) |
|
{ |
|
m_iSpellCharges--; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFSpellBook::ClearSpell() |
|
{ |
|
m_iSpellCharges = 0; |
|
#ifdef GAME_DLL |
|
// If rolling for a spell, clear that too |
|
m_iNextSpell = SPELL_EMPTY; |
|
#endif // GAME_DLL |
|
} |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CTFSpellBook::FireJar( CTFPlayer *pPlayer ) |
|
{ |
|
#ifdef GAME_DLL |
|
if ( pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) |
|
{ |
|
TossJarThink(); |
|
} |
|
else |
|
{ |
|
SetContextThink( &CTFJar::TossJarThink, gpGlobals->curtime + 0.01f, "TOSS_JAR_THINK" ); |
|
} |
|
#endif |
|
return NULL; |
|
} |
|
|
|
#ifdef GAME_DLL |
|
//----------------------------------------------------------------------------- |
|
void CTFSpellBook::TossJarThink( void ) |
|
{ |
|
CTFPlayer *pPlayer = GetTFPlayerOwner(); |
|
if ( !pPlayer ) |
|
return; |
|
|
|
// Self casts |
|
const spell_data_t *pSpellData = GetSpellData( m_iPreviouslyCastSpell ); |
|
if ( !pSpellData ) |
|
return; |
|
|
|
if ( pSpellData->m_eSpellType == SPELL_SELF ) |
|
{ |
|
// Self casts |
|
if ( TFGameRules() ) |
|
{ |
|
TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( pSpellData->m_iSpellContext, ( pPlayer->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED ); |
|
} |
|
// Play a sound immediately for self-cast spells |
|
EmitSound( pSpellData->m_pszCastSound ); |
|
pSpellData->m_pCastSpell( pPlayer ); |
|
return; |
|
} |
|
|
|
Vector vecForward, vecRight, vecUp; |
|
AngleVectors( pPlayer->EyeAngles(), &vecForward, &vecRight, &vecUp ); |
|
|
|
float fRight = 8.f; |
|
if ( IsViewModelFlipped() ) |
|
{ |
|
fRight *= -1; |
|
} |
|
Vector vecSrc = pPlayer->Weapon_ShootPosition(); |
|
// Make spell toss position at the hand |
|
vecSrc = vecSrc + (vecUp * -9.0f) + (vecRight * 7.0f) + (vecForward * 3.0f); |
|
|
|
Vector vecVelocity = GetVelocityVector( vecForward, vecRight, vecUp ) * pSpellData->m_flSpeedScale; |
|
QAngle angForward = pPlayer->EyeAngles(); |
|
|
|
// Halloween Hack |
|
// Eye Angles slighty higher |
|
if ( pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) |
|
{ |
|
// Add More up for Jar |
|
angForward = pPlayer->GetAbsAngles(); |
|
if ( pSpellData->m_eSpellType == SPELL_JAR ) |
|
{ |
|
angForward.x -= 10.0f; |
|
} |
|
|
|
AngleVectors( angForward, &vecForward, &vecRight, &vecUp ); |
|
vecVelocity = vecForward * tf_halloween_kart_rocketspell_speed.GetFloat(); |
|
} |
|
|
|
trace_t trace; |
|
Vector vecEye = pPlayer->EyePosition(); |
|
CTraceFilterSimple traceFilter( this, COLLISION_GROUP_NONE ); |
|
UTIL_TraceHull( vecEye, vecSrc, -Vector(8,8,8), Vector(8,8,8), MASK_SOLID_BRUSHONLY, &traceFilter, &trace ); |
|
|
|
// If we started in solid, don't let them fire at all |
|
if ( trace.startsolid ) |
|
return; |
|
|
|
// Play a sound when we actually cast the projectile |
|
EmitSound( pSpellData->m_pszCastSound ); |
|
|
|
switch ( pSpellData->m_eSpellType ) |
|
{ |
|
case SPELL_ROCKET : |
|
{ |
|
//QAngle angForward; |
|
//GetProjectileFireSetup( pPlayer, Vector(0,0,0), &vecSrc, &angForward, false ); |
|
CreateSpellRocket( trace.endpos, angForward, vecVelocity, GetAngularImpulse(), pPlayer, GetTFWpnData() ); |
|
} |
|
break; |
|
case SPELL_JAR : |
|
{ |
|
CreateSpellJar( trace.endpos, angForward, vecVelocity, GetAngularImpulse(), pPlayer, GetTFWpnData() ); |
|
} |
|
break; |
|
case SPELL_SELF : |
|
break; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFSpellBook::CreateSpellRocket( const Vector &position, const QAngle &angles, const Vector &velocity, |
|
const AngularImpulse &angVelocity, CBaseCombatCharacter *pOwner, const CTFWeaponInfo &weaponInfo ) |
|
{ |
|
const spell_data_t* pSpellData = GetSpellData( m_iPreviouslyCastSpell ); |
|
if ( !pSpellData ) |
|
{ |
|
return; |
|
} |
|
|
|
ASSERT( pSpellData->m_eSpellType == SPELL_ROCKET ); |
|
CTFProjectile_Rocket *pRocket = static_cast<CTFProjectile_Rocket*>( CBaseEntity::CreateNoSpawn( pSpellData->m_pSpellEntityName, position, angles, pOwner ) ); |
|
if ( pRocket ) |
|
{ |
|
pRocket->SetOwnerEntity( pOwner ); |
|
pRocket->SetLauncher( this ); |
|
|
|
Vector vForward; |
|
AngleVectors( angles, &vForward, NULL, NULL ); |
|
pRocket->SetAbsVelocity( vForward * velocity.Length() ); |
|
|
|
pRocket->SetDamage( weaponInfo.GetWeaponData(TF_WEAPON_PRIMARY_MODE).m_nDamage ); |
|
pRocket->ChangeTeam( pOwner ? pOwner->GetTeamNumber() : TEAM_UNASSIGNED ); |
|
|
|
IPhysicsObject *pPhysicsObject = pRocket->VPhysicsGetObject(); |
|
if ( pPhysicsObject ) |
|
{ |
|
pPhysicsObject->AddVelocity( &velocity, &angVelocity ); |
|
} |
|
|
|
DispatchSpawn( pRocket ); |
|
} |
|
} |
|
//----------------------------------------------------------------------------- |
|
void CTFSpellBook::CreateSpellJar( const Vector &position, const QAngle &angles, const Vector &velocity, |
|
const AngularImpulse &angVelocity, CBaseCombatCharacter *pOwner, const CTFWeaponInfo &weaponInfo ) |
|
{ |
|
const spell_data_t* pSpellData = GetSpellData( m_iPreviouslyCastSpell ); |
|
if ( !pSpellData ) |
|
{ |
|
return; |
|
} |
|
|
|
ASSERT( pSpellData->m_eSpellType == SPELL_JAR ); |
|
CTFProjectile_Jar *pGrenade = static_cast<CTFProjectile_Jar*>( CBaseEntity::CreateNoSpawn( pSpellData->m_pSpellEntityName, position, angles, pOwner ) ); |
|
if ( pGrenade ) |
|
{ |
|
// Set the pipebomb mode before calling spawn, so the model & associated vphysics get setup properly. |
|
pGrenade->SetPipebombMode(); |
|
DispatchSpawn( pGrenade ); |
|
|
|
IPhysicsObject *pPhys = pGrenade->VPhysicsGetObject(); |
|
if ( pPhys ) |
|
{ |
|
pPhys->SetMass( 5.0f ); |
|
} |
|
|
|
pGrenade->InitGrenade( velocity, vec3_origin, pOwner, weaponInfo ); |
|
pGrenade->m_flFullDamage = 0; |
|
pGrenade->ApplyLocalAngularVelocityImpulse( vec3_origin ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFSpellBook::RollNewSpell( int iTier, bool bForceReroll /*= false*/ ) |
|
{ |
|
// do not do anything if we already have a spell for low tier, always roll for high tier |
|
if ( m_iSpellCharges > 0 && iTier == 0 && !bForceReroll ) |
|
return; |
|
|
|
CTFPlayer *pPlayer = GetTFPlayerOwner(); |
|
if ( !pPlayer ) |
|
return; |
|
|
|
int iNextSpell = SPELL_EMPTY; |
|
// Halloween 2014 |
|
// This is dumb, make spell lists better somehow |
|
if ( pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) |
|
{ |
|
iNextSpell = RandomInt( ARRAYSIZE( g_NormalSpellList ) + ARRAYSIZE( g_RareSpellList ), GetTotalSpellCount( pPlayer ) - 1 ); |
|
} |
|
else if ( iTier == 0 ) |
|
{ |
|
// Doomsday has special spell list |
|
if ( TFGameRules() && TFGameRules()->GetHalloweenScenario( ) == CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) |
|
{ |
|
iNextSpell = g_doomsdayNormalSpellIndexList[ RandomInt( 0, ARRAYSIZE( g_doomsdayNormalSpellIndexList ) - 1 ) ]; |
|
} |
|
// Helltower has normal spell list |
|
else if ( TFGameRules() && TFGameRules()->GetHalloweenScenario( ) == CTFGameRules::HALLOWEEN_SCENARIO_HIGHTOWER ) |
|
{ |
|
iNextSpell = RandomInt( 0, ARRAYSIZE( g_NormalSpellList ) - 1 ); |
|
} |
|
// everyone else uses special list |
|
else |
|
{ |
|
iNextSpell = g_generalSpellIndexList[ RandomInt( 0, ARRAYSIZE( g_generalSpellIndexList ) - 1 ) ]; |
|
} |
|
} |
|
else // rare spell should not be the else |
|
{ |
|
// Doomsday has special spell list |
|
if ( TFGameRules() && TFGameRules()->GetHalloweenScenario( ) == CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) |
|
{ |
|
iNextSpell = g_doomsdayRareSpellIndexList[ RandomInt( 0, ARRAYSIZE( g_doomsdayRareSpellIndexList ) - 1 ) ]; |
|
} |
|
else |
|
{ |
|
// g_NavMeshSpells |
|
// If there's no Nav mesh do not allow the upper range of spells (summons) |
|
int iIndexReduction = 1; |
|
if ( ( TheNavMesh == NULL ) || ( TheNavMesh->GetNavAreaCount() <= 0 ) ) |
|
{ |
|
iIndexReduction += g_NavMeshSpells; |
|
} |
|
iNextSpell = RandomInt( ARRAYSIZE( g_NormalSpellList ), GetTotalSpellCount( pPlayer ) - 1 ); |
|
} |
|
} |
|
|
|
const float flRollTime = 2.f; |
|
|
|
m_iNextSpell = iNextSpell; |
|
SetSelectedSpell( SPELL_UNKNOWN ); |
|
SetContextThink( &CTFSpellBook::RollNewSpellFinish, gpGlobals->curtime + flRollTime, "SpellRollFinish" ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFSpellBook::RollNewSpellFinish( void ) |
|
{ |
|
SetSelectedSpell( m_iNextSpell ); |
|
|
|
if ( m_iNextSpell < 0 ) |
|
return; |
|
|
|
// response rules |
|
CTFPlayer *pPlayer = GetTFPlayerOwner(); |
|
if ( !pPlayer ) |
|
{ |
|
return; |
|
} |
|
int iConcept = MP_CONCEPT_NONE; |
|
if ( m_iNextSpell < ARRAYSIZE( g_NormalSpellList ) ) |
|
{ |
|
iConcept = MP_CONCEPT_PLAYER_SPELL_PICKUP_COMMON; |
|
} |
|
else |
|
{ |
|
iConcept = MP_CONCEPT_PLAYER_SPELL_PICKUP_RARE; |
|
} |
|
|
|
if ( iConcept != MP_CONCEPT_NONE ) |
|
{ |
|
pPlayer->SpeakConceptIfAllowed( iConcept ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFSpellBook::SetSelectedSpell( int index ) |
|
{ |
|
m_iSelectedSpellIndex = index; |
|
|
|
const spell_data_t *pSpellData = GetSpellData( m_iSelectedSpellIndex ); |
|
m_iSpellCharges = pSpellData ? pSpellData->m_iSpellCharges : 0; |
|
|
|
if ( pSpellData && pSpellData->m_bAutoCast ) |
|
{ |
|
PrimaryAttack(); |
|
} |
|
} |
|
//----------------------------------------------------------------------------- |
|
void CTFSpellBook::SpeakSpellConceptIfAllowed() |
|
{ |
|
CTFPlayer *pPlayer = GetTFPlayerOwner(); |
|
if ( !pPlayer || m_iSpellCharges <= 0 ) |
|
return; |
|
|
|
const spell_data_t *pSpellData = GetSpellData( m_iSelectedSpellIndex ); |
|
if ( pSpellData ) |
|
{ |
|
pPlayer->SpeakConceptIfAllowed( pSpellData->m_iCastContext ); |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------------------------------------------------------------ |
|
// KART FUNCTIONS |
|
//------------------------------------------------------------------------------------------------------------------------------------ |
|
void CTFSpellBook::CastKartSpell() |
|
{ |
|
#ifdef GAME_DLL |
|
// cast spell time |
|
if ( m_flTimeNextSpell > gpGlobals->curtime ) |
|
return; |
|
|
|
CTFPlayer *pPlayer = GetTFPlayerOwner(); |
|
if ( !pPlayer ) |
|
return; |
|
|
|
if ( m_iSpellCharges <= 0 ) |
|
{ |
|
if ( tf_test_spellindex.GetInt() < 0 || tf_test_spellindex.GetInt() > GetTotalSpellCount( pPlayer ) ) |
|
return; |
|
} |
|
|
|
// Save off what we cast for jar think |
|
PaySpellCost( pPlayer ); |
|
|
|
m_iPreviouslyCastSpell = m_iSelectedSpellIndex; |
|
FireProjectile( pPlayer ); |
|
|
|
m_flTimeNextSpell = gpGlobals->curtime + 0.5f; |
|
|
|
// Create one off spell effect in front of the player |
|
Vector origin = pPlayer->GetAbsOrigin(); |
|
CPVSFilter filter( origin ); |
|
|
|
if ( GetTeamNumber() == TF_TEAM_RED ) |
|
{ |
|
TE_TFParticleEffect( filter, 0.0, "spell_cast_wheel_red", origin + Vector( 0, 0, 100 ), vec3_angle, pPlayer, PATTACH_ABSORIGIN_FOLLOW ); |
|
} |
|
else |
|
{ |
|
TE_TFParticleEffect( filter, 0.0, "spell_cast_wheel_blue", origin + Vector( 0, 0, 100 ), vec3_angle, pPlayer, PATTACH_ABSORIGIN_FOLLOW ); |
|
} |
|
|
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Individual spells |
|
//----------------------------------------------------------------------------- |
|
bool CTFSpellBook::CastSpell( CTFPlayer *pPlayer, int iSpellIndex ) |
|
{ |
|
if ( CanCastSpell( pPlayer ) ) |
|
{ |
|
PaySpellCost( pPlayer ); |
|
const spell_data_t *pSpellData = GetSpellData( m_iSelectedSpellIndex ); |
|
if ( !pSpellData) |
|
return false; |
|
|
|
if ( IsRareSpell( iSpellIndex ) ) |
|
{ |
|
if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_HIGHTOWER ) ) |
|
{ |
|
pPlayer->AwardAchievement( ACHIEVEMENT_TF_HALLOWEEN_HELLTOWER_RARE_SPELL ); |
|
} |
|
} |
|
|
|
// Save off what we cast for jar think |
|
m_iPreviouslyCastSpell = m_iSelectedSpellIndex; |
|
|
|
// Create one off spell effect in front of the player |
|
Vector origin = pPlayer->GetAbsOrigin(); |
|
CPVSFilter filter( origin ); |
|
|
|
//const spell_data_t *pSpellData = GetSpellData( m_iSelectedSpellIndex ); |
|
if ( pSpellData && !FStrEq( pSpellData->m_pSpellUiName, "#TF_Spell_Stealth" ) ) // do NOT create for Stealth |
|
{ |
|
if ( GetTeamNumber() == TF_TEAM_RED ) |
|
{ |
|
TE_TFParticleEffect( filter, 0.0, "spell_cast_wheel_red", origin + Vector( 0, 0, 100 ), vec3_angle, pPlayer, PATTACH_ABSORIGIN_FOLLOW ); |
|
} |
|
else |
|
{ |
|
TE_TFParticleEffect( filter, 0.0, "spell_cast_wheel_blue", origin + Vector( 0, 0, 100 ), vec3_angle, pPlayer, PATTACH_ABSORIGIN_FOLLOW ); |
|
} |
|
} |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
#endif |
|
|
|
//----------------------------------------------------------------------------- |
|
bool CTFSpellBook::CastSelfHeal( CTFPlayer *pPlayer ) |
|
{ |
|
#ifdef GAME_DLL |
|
Vector origin = pPlayer->GetAbsOrigin(); |
|
CPVSFilter filter( origin ); |
|
const char* pszEffectName = pPlayer->GetTeamNumber() == TF_TEAM_RED ? "spell_overheal_red" : "spell_overheal_blue"; |
|
TE_TFParticleEffect( filter, 0.0, pszEffectName, origin, vec3_angle, pPlayer, PATTACH_ABSORIGIN_FOLLOW ); |
|
|
|
//pPlayer->EmitSound( "BaseExplosionEffect.Sound" ); |
|
|
|
// Collect players and cause knockback to enemies |
|
// Treat this trace exactly like radius damage |
|
CTraceFilterIgnorePlayers traceFilter( pPlayer, COLLISION_GROUP_PROJECTILE ); |
|
|
|
// Splash pee on everyone nearby. |
|
CBaseEntity *pListOfEntities[32]; |
|
int iEntities = UTIL_EntitiesInSphere( pListOfEntities, 32, origin, 250.0f, FL_CLIENT | FL_FAKECLIENT | FL_NPC ); |
|
for ( int i = 0; i < iEntities; ++i ) |
|
{ |
|
CBaseCombatCharacter *pBaseTarget = NULL; |
|
CTFPlayer *pTarget = ToTFPlayer( pListOfEntities[i] ); |
|
if ( !pTarget ) |
|
{ |
|
pBaseTarget = dynamic_cast<CBaseCombatCharacter*>( pListOfEntities[i] ); |
|
} |
|
else |
|
{ |
|
pBaseTarget = pTarget; |
|
} |
|
|
|
if ( !pBaseTarget || !pTarget || !pTarget->IsAlive() ) |
|
continue; |
|
|
|
// Do a quick trace to see if there's any geometry in the way. |
|
trace_t trace; |
|
UTIL_TraceLine( origin, pBaseTarget->GetAbsOrigin(), ( MASK_SHOT & ~( CONTENTS_HITBOX ) ), &traceFilter, &trace ); |
|
if ( trace.DidHitWorld() ) |
|
continue; |
|
|
|
Vector vecDir = pBaseTarget->WorldSpaceCenter() - origin; |
|
VectorNormalize( vecDir ); |
|
|
|
// help allies |
|
if ( pBaseTarget->GetTeamNumber() == pPlayer->GetTeamNumber() ) |
|
{ |
|
pBaseTarget->TakeHealth( 50, DMG_GENERIC ); |
|
|
|
if ( pTarget ) |
|
{ |
|
pTarget->m_Shared.AddCond( TF_COND_INVULNERABLE_USER_BUFF, 1, pPlayer ); |
|
pTarget->m_Shared.AddCond( TF_COND_HALLOWEEN_QUICK_HEAL, 3, pPlayer ); |
|
} |
|
} |
|
else // knockback enemies |
|
{ |
|
if ( pTarget ) |
|
{ |
|
pTarget->ApplyAirBlastImpulse( vecDir * 300.0f ); |
|
} |
|
else |
|
{ |
|
pBaseTarget->ApplyAbsVelocityImpulse( vecDir * 300.0f ); |
|
} |
|
} |
|
} |
|
|
|
#endif |
|
return true; |
|
} |
|
//----------------------------------------------------------------------------- |
|
bool CTFSpellBook::CastRocketJump( CTFPlayer *pPlayer ) |
|
{ |
|
#ifdef GAME_DLL |
|
const float flBlastRadius = 100.f; |
|
|
|
// Set z to zero then add impulse |
|
// make this proper jumping later |
|
Vector vel = pPlayer->GetAbsVelocity(); |
|
if ( vel.z < 0 ) |
|
{ |
|
vel.z = 0; |
|
} |
|
pPlayer->SetAbsVelocity( vel ); |
|
|
|
Vector vForward( 0, 0, 800 ); |
|
pPlayer->ApplyAbsVelocityImpulse( vForward ); |
|
|
|
const Vector& origin = pPlayer->GetAbsOrigin(); |
|
CPVSFilter filter( origin ); |
|
TE_TFParticleEffect( filter, 0.0, "bombinomicon_burningdebris", origin, vec3_angle ); |
|
TE_TFParticleEffect( filter, 0.0, "heavy_ring_of_fire", origin, vec3_angle ); |
|
DispatchParticleEffect( "rocketjump_smoke", PATTACH_POINT_FOLLOW, pPlayer, "foot_L" ); |
|
DispatchParticleEffect( "rocketjump_smoke", PATTACH_POINT_FOLLOW, pPlayer, "foot_R" ); |
|
|
|
// Give a little health to compensate for fall damage |
|
pPlayer->TakeHealth( 25, DMG_GENERIC ); |
|
|
|
// Collect players and cause knockback to enemies |
|
// Treat this trace exactly like radius damage |
|
CTraceFilterIgnorePlayers traceFilter( pPlayer, COLLISION_GROUP_PROJECTILE ); |
|
|
|
// Splash pee on everyone nearby. |
|
CBaseEntity *pListOfEntities[32]; |
|
int iEntities = UTIL_EntitiesInSphere( pListOfEntities, 32, origin, flBlastRadius, FL_CLIENT | FL_FAKECLIENT | FL_NPC ); |
|
for ( int i = 0; i < iEntities; ++i ) |
|
{ |
|
CBaseCombatCharacter *pBaseTarget = NULL; |
|
CTFPlayer *pTarget = ToTFPlayer( pListOfEntities[i] ); |
|
if ( !pTarget ) |
|
{ |
|
pBaseTarget = dynamic_cast<CBaseCombatCharacter*>( pListOfEntities[i] ); |
|
} |
|
else |
|
{ |
|
pBaseTarget = pTarget; |
|
} |
|
|
|
if ( !pBaseTarget || !pTarget || !pTarget->IsAlive() || pBaseTarget->GetTeamNumber() == pPlayer->GetTeamNumber() ) |
|
continue; |
|
|
|
// Do a quick trace to see if there's any geometry in the way. |
|
trace_t trace; |
|
UTIL_TraceLine( origin, pBaseTarget->GetAbsOrigin(), ( MASK_SHOT & ~( CONTENTS_HITBOX ) ), &traceFilter, &trace ); |
|
if ( trace.DidHitWorld() ) |
|
continue; |
|
|
|
Vector vecDir = pBaseTarget->WorldSpaceCenter() - origin; |
|
VectorNormalize( vecDir ); |
|
|
|
pBaseTarget->RemoveFlag( FL_ONGROUND ); |
|
|
|
if ( pTarget ) |
|
{ |
|
pTarget->ApplyAirBlastImpulse( vecDir * 800.0f ); |
|
} |
|
else |
|
{ |
|
pBaseTarget->ApplyAbsVelocityImpulse( vecDir * 800.0f ); |
|
} |
|
} |
|
|
|
CTakeDamageInfo info; |
|
info.SetAttacker( pPlayer ); |
|
info.SetInflictor( pPlayer ); |
|
info.SetDamage( 20.f ); |
|
info.SetDamageCustom( TF_DMG_CUSTOM_SPELL_BLASTJUMP ); |
|
info.SetDamagePosition( origin ); |
|
info.SetDamageType( DMG_BLAST ); |
|
|
|
CTFRadiusDamageInfo radiusinfo( &info, origin, flBlastRadius, pPlayer ); |
|
TFGameRules()->RadiusDamage( radiusinfo ); |
|
|
|
#endif |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
bool CTFSpellBook::CastSelfSpeedBoost( CTFPlayer *pPlayer ) |
|
{ |
|
#ifdef GAME_DLL |
|
// Give a little health |
|
pPlayer->TakeHealth( 100, DMG_GENERIC ); |
|
|
|
pPlayer->m_Shared.AddCond( TF_COND_HALLOWEEN_TINY, 20, pPlayer ); |
|
pPlayer->m_Shared.AddCond( TF_COND_HALLOWEEN_SPEED_BOOST, 20, pPlayer ); |
|
#endif |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
bool CTFSpellBook::CastSelfStealth( CTFPlayer *pPlayer ) |
|
{ |
|
#ifdef GAME_DLL |
|
// Grant a small amount of health |
|
pPlayer->TakeHealth( 40, DMG_GENERIC ); |
|
pPlayer->m_Shared.AddCond( TF_COND_STEALTHED_USER_BUFF, 8, pPlayer ); |
|
#endif |
|
return true; |
|
} |
|
|
|
//******************************************************************************************************************************** |
|
//----------------------------------------------------------------------------- |
|
// Kart Self Spells |
|
//----------------------------------------------------------------------------- |
|
bool CTFSpellBook::CastKartRocketJump( CTFPlayer *pPlayer ) |
|
{ |
|
#ifdef GAME_DLL |
|
const float flBlastRadius = 250.f; |
|
|
|
// Set z to zero then add impulse |
|
// make this proper jumping later |
|
Vector vel = pPlayer->GetAbsVelocity(); |
|
if ( vel.z < 0 ) |
|
{ |
|
vel.z = 0; |
|
} |
|
pPlayer->SetAbsVelocity( vel ); |
|
|
|
Vector vForward( 0, 0, 1200 ); |
|
pPlayer->ApplyAbsVelocityImpulse( vForward ); |
|
|
|
const Vector& origin = pPlayer->GetAbsOrigin(); |
|
CPVSFilter filter( origin ); |
|
TE_TFParticleEffect( filter, 0.0, "bombinomicon_burningdebris", origin, vec3_angle ); |
|
TE_TFParticleEffect( filter, 0.0, "heavy_ring_of_fire", origin, vec3_angle ); |
|
DispatchParticleEffect( "rocketjump_smoke", PATTACH_POINT_FOLLOW, pPlayer, "foot_L" ); |
|
DispatchParticleEffect( "rocketjump_smoke", PATTACH_POINT_FOLLOW, pPlayer, "foot_R" ); |
|
|
|
// Give a little health to compensate for fall damage |
|
//pPlayer->TakeHealth( 25, DMG_GENERIC ); |
|
pPlayer->RemoveFlag( FL_ONGROUND ); |
|
pPlayer->m_Shared.AddCond( TF_COND_PARACHUTE_DEPLOYED ); |
|
|
|
// Collect players and cause knockback to enemies |
|
// Treat this trace exactly like radius damage |
|
CTraceFilterIgnorePlayers traceFilter( pPlayer, COLLISION_GROUP_PROJECTILE ); |
|
|
|
// Trace entity radius |
|
CBaseEntity *pListOfEntities[32]; |
|
int iEntities = UTIL_EntitiesInSphere( pListOfEntities, 32, origin, flBlastRadius, FL_CLIENT | FL_FAKECLIENT | FL_NPC ); |
|
for ( int i = 0; i < iEntities; ++i ) |
|
{ |
|
CTFPlayer *pTarget = ToTFPlayer( pListOfEntities[i] ); |
|
|
|
if ( !pTarget || !pTarget->IsAlive() || pTarget->GetTeamNumber() == pPlayer->GetTeamNumber() ) |
|
continue; |
|
|
|
// Do a quick trace to see if there's any geometry in the way. |
|
trace_t trace; |
|
UTIL_TraceLine( origin, pTarget->GetAbsOrigin(), ( MASK_SHOT & ~( CONTENTS_HITBOX ) ), &traceFilter, &trace ); |
|
if ( trace.DidHitWorld() ) |
|
continue; |
|
|
|
Vector vecDir = pTarget->WorldSpaceCenter() - origin; |
|
vecDir.NormalizeInPlace(); |
|
vecDir.z += 0.5f; |
|
pTarget->AddHalloweenKartPushEvent( pPlayer, pPlayer->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ), pPlayer->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ), vecDir * tf_halloween_kart_normal_speed.GetFloat(), 30.0f ); |
|
} |
|
#endif |
|
return true; |
|
} |
|
|
|
bool CTFSpellBook::CastKartUber( CTFPlayer *pPlayer ) |
|
{ |
|
#ifdef GAME_DLL |
|
Vector origin = pPlayer->GetAbsOrigin(); |
|
CPVSFilter filter( origin ); |
|
const char* pszEffectName = pPlayer->GetTeamNumber() == TF_TEAM_RED ? "spell_overheal_red" : "spell_overheal_blue"; |
|
TE_TFParticleEffect( filter, 0.0, pszEffectName, origin, vec3_angle, pPlayer, PATTACH_ABSORIGIN_FOLLOW ); |
|
|
|
//pPlayer->EmitSound( "BaseExplosionEffect.Sound" ); |
|
|
|
// Collect players and cause knockback to enemies |
|
// Treat this trace exactly like radius damage |
|
CTraceFilterIgnorePlayers traceFilter( pPlayer, COLLISION_GROUP_PROJECTILE ); |
|
|
|
pPlayer->m_Shared.AddCond( TF_COND_INVULNERABLE_USER_BUFF, 7, pPlayer ); |
|
pPlayer->AddKartDamage( -50 ); //Heal |
|
#endif |
|
return true; |
|
} |
|
|
|
|
|
bool CTFSpellBook::CastKartBombHead( CTFPlayer *pPlayer ) |
|
{ |
|
#ifdef GAME_DLL |
|
pPlayer->m_Shared.AddCond( TF_COND_HALLOWEEN_BOMB_HEAD, 10, pPlayer ); |
|
#endif |
|
return true; |
|
} |
|
|
|
//************************************************************************************************************************ |
|
// Spell Projectiles |
|
//************************************************************************************************************************ |
|
class CTFProjectile_SpellFireball : public CTFProjectile_Rocket |
|
{ |
|
public: |
|
DECLARE_CLASS( CTFProjectile_SpellFireball, CTFProjectile_Rocket ); |
|
DECLARE_NETWORKCLASS(); |
|
|
|
virtual int GetWeaponID( void ) const { return TF_WEAPON_SPELLBOOK_PROJECTILE; } |
|
virtual float GetDamageRadius() const { return 200.0f; } |
|
virtual int GetCustomDamageType() const OVERRIDE { return m_bIsMeteor ? TF_DMG_CUSTOM_SPELL_METEOR : TF_DMG_CUSTOM_SPELL_FIREBALL; } |
|
virtual bool IsDeflectable() OVERRIDE { return false; } |
|
|
|
void SetMeteor( bool bIsMeteor ) { m_bIsMeteor = bIsMeteor; } |
|
|
|
CTFProjectile_SpellFireball() |
|
{ |
|
m_bIsMeteor = false; |
|
#ifdef GAME_DLL |
|
//m_pszExplodeParticleName = "ExplosionCore_buildings"; |
|
m_pszExplodeParticleName = "bombinomicon_burningdebris"; |
|
#endif // GAME_DLL |
|
} |
|
|
|
#ifdef GAME_DLL |
|
virtual void Spawn() OVERRIDE |
|
{ |
|
SetModelScale( 0.01f ); |
|
BaseClass::Spawn(); |
|
} |
|
virtual int UpdateTransmitState() OVERRIDE { return SetTransmitState( FL_EDICT_PVSCHECK ); } |
|
|
|
virtual void RocketTouch( CBaseEntity *pOther ) OVERRIDE |
|
{ |
|
Assert( pOther ); |
|
if ( !pOther->IsSolid() || pOther->IsSolidFlagSet( FSOLID_VOLUME_CONTENTS ) ) |
|
return; |
|
|
|
if ( pOther->GetParent() == GetOwnerEntity() ) |
|
return; |
|
|
|
// Handle hitting skybox (disappear). |
|
const trace_t *pTrace = &CBaseEntity::GetTouchTrace(); |
|
|
|
if( pTrace->surface.flags & SURF_SKY ) |
|
{ |
|
UTIL_Remove( this ); |
|
return; |
|
} |
|
|
|
// pass through ladders |
|
if( pTrace->surface.flags & CONTENTS_LADDER ) |
|
return; |
|
|
|
Explode( pTrace ); |
|
|
|
UTIL_Remove( this ); |
|
} |
|
|
|
virtual void Explode( const trace_t *pTrace ) |
|
{ |
|
SetModelName( NULL_STRING );//invisible |
|
AddSolidFlags( FSOLID_NOT_SOLID ); |
|
|
|
m_takedamage = DAMAGE_NO; |
|
|
|
// Pull out of the wall a bit. |
|
if ( pTrace->fraction != 1.0 ) |
|
{ |
|
SetAbsOrigin( pTrace->endpos + ( pTrace->plane.normal * 1.0f ) ); |
|
} |
|
|
|
CTFPlayer *pThrower = ToTFPlayer( GetOwnerEntity() ); |
|
if ( pThrower ) |
|
{ |
|
const Vector &vecOrigin = GetAbsOrigin(); |
|
|
|
// Any effects from the initial explosion |
|
if ( InitialExplodeEffects( pThrower, pTrace ) ) |
|
{ |
|
// Particle |
|
if ( GetExplodeEffectParticle() ) |
|
{ |
|
CPVSFilter filter( vecOrigin ); |
|
TE_TFParticleEffect( filter, 0.0, GetExplodeEffectParticle(), vecOrigin, vec3_angle ); |
|
} |
|
|
|
// Sounds |
|
if ( GetExplodeEffectSound() ) |
|
{ |
|
EmitSound( GetExplodeEffectSound() ); |
|
} |
|
|
|
// Treat this trace exactly like radius damage |
|
CTraceFilterIgnorePlayers traceFilter( pThrower, COLLISION_GROUP_PROJECTILE ); |
|
|
|
// Splash pee on everyone nearby. |
|
CBaseEntity *pListOfEntities[32]; |
|
int iEntities = UTIL_EntitiesInSphere( pListOfEntities, 32, vecOrigin, GetDamageRadius(), FL_CLIENT | FL_FAKECLIENT | FL_NPC ); |
|
for ( int i = 0; i < iEntities; ++i ) |
|
{ |
|
CBaseCombatCharacter *pBasePlayer = NULL; |
|
CTFPlayer *pPlayer = ToTFPlayer( pListOfEntities[i] ); |
|
if ( !pPlayer ) |
|
{ |
|
pBasePlayer = dynamic_cast<CBaseCombatCharacter*>( pListOfEntities[i] ); |
|
} |
|
else |
|
{ |
|
pBasePlayer = pPlayer; |
|
} |
|
|
|
if ( !pBasePlayer || !pPlayer || !pPlayer->IsAlive() ) |
|
continue; |
|
|
|
// Do a quick trace to see if there's any geometry in the way. |
|
trace_t trace; |
|
UTIL_TraceLine( vecOrigin, pPlayer->WorldSpaceCenter(), ( MASK_SHOT & ~( CONTENTS_HITBOX ) ), &traceFilter, &trace ); |
|
//debugoverlay->AddLineOverlay( vecOrigin, pPlayer->WorldSpaceCenter(), 255, 0, 0, false, 10 ); |
|
if ( trace.DidHitWorld() ) |
|
continue; |
|
|
|
// Effects on the individual players |
|
ExplodeEffectOnTarget( pThrower, pPlayer, pBasePlayer ); |
|
} |
|
|
|
if ( TFGameRules() ) |
|
{ |
|
TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_PLAYER_SPELL_FIREBALL, ( pThrower->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED ); |
|
} |
|
|
|
CTakeDamageInfo info; |
|
info.SetAttacker( pThrower ); |
|
info.SetInflictor( this ); |
|
info.SetWeapon( GetLauncher() ); |
|
info.SetDamage( 10.f ); |
|
info.SetDamageCustom( GetCustomDamageType() ); |
|
info.SetDamagePosition( vecOrigin ); |
|
info.SetDamageType( DMG_BLAST ); |
|
|
|
CTFRadiusDamageInfo radiusinfo( &info, vecOrigin, GetDamageRadius(), pThrower ); |
|
TFGameRules()->RadiusDamage( radiusinfo ); |
|
} |
|
else |
|
{ |
|
pThrower->EmitSound( "Player.DenyWeaponSelection" ); |
|
} |
|
} |
|
|
|
// Grenade remove |
|
//SetContextThink( &CBaseGrenade::SUB_Remove, gpGlobals->curtime, "RemoveThink" ); |
|
|
|
// Remove the rocket. |
|
UTIL_Remove( this ); |
|
|
|
SetTouch( NULL ); |
|
AddEffects( EF_NODRAW ); |
|
SetAbsVelocity( vec3_origin ); |
|
} |
|
|
|
virtual const char *GetProjectileModelName( void ) { return ""; } // We dont have a model by default, and that's OK |
|
|
|
virtual bool InitialExplodeEffects( CTFPlayer *pThrower, const trace_t *pTrace ) { return true; } |
|
virtual void ExplodeEffectOnTarget( CTFPlayer *pThrower, CTFPlayer *pTarget, CBaseCombatCharacter *pBaseTarget ) |
|
{ |
|
if ( pBaseTarget->GetTeamNumber() == GetTeamNumber() ) |
|
return; |
|
|
|
if ( pTarget ) |
|
{ |
|
if ( pTarget->m_Shared.IsInvulnerable() ) |
|
return; |
|
|
|
if ( pTarget->m_Shared.InCond( TF_COND_PHASE ) || pTarget->m_Shared.InCond( TF_COND_PASSTIME_INTERCEPTION ) ) |
|
return; |
|
|
|
pTarget->m_Shared.SelfBurn( 5.0f ); |
|
} |
|
|
|
const trace_t *pTrace = &CBaseEntity::GetTouchTrace(); |
|
trace_t *pNewTrace = const_cast<trace_t*>( pTrace ); |
|
|
|
CBaseEntity *pInflictor = GetLauncher(); |
|
CTakeDamageInfo info; |
|
info.SetAttacker( pThrower ); |
|
info.SetInflictor( this ); |
|
info.SetWeapon( pInflictor ); |
|
info.SetDamage( 100.f ); |
|
info.SetDamageCustom( GetCustomDamageType() ); |
|
info.SetDamagePosition( GetAbsOrigin() ); |
|
info.SetDamageType( DMG_BURN ); |
|
|
|
// Hurt 'em. |
|
Vector dir; |
|
AngleVectors( GetAbsAngles(), &dir ); |
|
pBaseTarget->DispatchTraceAttack( info, dir, pNewTrace ); |
|
ApplyMultiDamage(); |
|
|
|
|
|
Vector vecDir = pBaseTarget->WorldSpaceCenter() - GetAbsOrigin(); |
|
VectorNormalize( vecDir ); |
|
vecDir.z = 0.1f; |
|
|
|
if ( pTarget ) |
|
{ |
|
pTarget->ApplyAirBlastImpulse( vecDir * 5 ); |
|
} |
|
} |
|
|
|
virtual const char *GetExplodeEffectParticle() const { return m_pszExplodeParticleName; } |
|
void SetExplodeParticleName( const char *pszName ) { m_pszExplodeParticleName = pszName; } |
|
virtual const char *GetExplodeEffectSound() const { return "Halloween.spell_fireball_impact"; } |
|
#endif |
|
|
|
#ifdef CLIENT_DLL |
|
virtual const char *GetTrailParticleName( void ) |
|
{ |
|
return GetTeamNumber() == TF_TEAM_BLUE ? "spell_fireball_small_blue" : "spell_fireball_small_red"; |
|
} |
|
#endif |
|
|
|
private: |
|
bool m_bIsMeteor; |
|
|
|
#ifdef GAME_DLL |
|
const char *m_pszExplodeParticleName; |
|
#endif // GAME_DLL |
|
}; |
|
|
|
// Fireball |
|
IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SpellFireball, DT_TFProjectile_SpellFireball ) |
|
BEGIN_NETWORK_TABLE( CTFProjectile_SpellFireball, DT_TFProjectile_SpellFireball ) |
|
END_NETWORK_TABLE() |
|
|
|
LINK_ENTITY_TO_CLASS( tf_projectile_spellfireball, CTFProjectile_SpellFireball ); |
|
PRECACHE_WEAPON_REGISTER( tf_projectile_spellfireball); |
|
|
|
|
|
// ************************************************************************************************************************* |
|
class CTFProjectile_SpellBats : public CTFProjectile_Jar |
|
{ |
|
public: |
|
DECLARE_CLASS( CTFProjectile_SpellBats, CTFProjectile_Jar ); |
|
DECLARE_NETWORKCLASS(); |
|
|
|
virtual int GetWeaponID( void ) const { return TF_WEAPON_SPELLBOOK_PROJECTILE; } |
|
virtual float GetDamageRadius() const { return 250.0f; } |
|
virtual float GetModelScale() const { return 0.01f; } |
|
virtual int GetCustomDamageType() const OVERRIDE { return TF_DMG_CUSTOM_SPELL_BATS; } |
|
virtual bool IsDeflectable() OVERRIDE { return false; } |
|
|
|
#ifdef GAME_DLL |
|
virtual void Spawn( void ) |
|
{ |
|
SetModelScale( GetModelScale() ); |
|
BaseClass::Spawn(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Lightning Ball / Base |
|
//----------------------------------------------------------------------------- |
|
virtual void Explode( trace_t *pTrace, int bitsDamageType ) OVERRIDE |
|
{ |
|
SetModelName( NULL_STRING );//invisible |
|
AddSolidFlags( FSOLID_NOT_SOLID ); |
|
|
|
m_takedamage = DAMAGE_NO; |
|
|
|
// Pull out of the wall a bit. |
|
if ( pTrace->fraction != 1.0 ) |
|
{ |
|
SetAbsOrigin( pTrace->endpos + ( pTrace->plane.normal * 1.0f ) ); |
|
} |
|
|
|
CTFPlayer *pThrower = ToTFPlayer( GetThrower() ); |
|
|
|
if ( pThrower ) |
|
{ |
|
const Vector& vecOrigin = GetAbsOrigin(); |
|
|
|
// Any effects from the initial explosion |
|
if ( InitialExplodeEffects( pThrower, pTrace ) ) |
|
{ |
|
// Particle |
|
if ( GetExplodeEffectParticle() ) |
|
{ |
|
CPVSFilter filter( vecOrigin ); |
|
TE_TFParticleEffect( filter, 0.0, GetExplodeEffectParticle(), vecOrigin, vec3_angle ); |
|
} |
|
|
|
// Sounds |
|
if ( GetExplodeEffectSound() ) |
|
{ |
|
EmitSound( GetExplodeEffectSound() ); |
|
} |
|
|
|
// Treat this trace exactly like radius damage |
|
CTraceFilterIgnorePlayers traceFilter( pThrower, COLLISION_GROUP_PROJECTILE ); |
|
|
|
// Splash pee on everyone nearby. |
|
CBaseEntity *pListOfEntities[32]; |
|
int iEntities = UTIL_EntitiesInSphere( pListOfEntities, 32, vecOrigin, GetDamageRadius(), FL_CLIENT | FL_FAKECLIENT | FL_NPC ); |
|
for ( int i = 0; i < iEntities; ++i ) |
|
{ |
|
CBaseCombatCharacter *pBasePlayer = NULL; |
|
CTFPlayer *pPlayer = ToTFPlayer( pListOfEntities[i] ); |
|
if ( !pPlayer ) |
|
{ |
|
pBasePlayer = dynamic_cast<CBaseCombatCharacter*>( pListOfEntities[i] ); |
|
} |
|
else |
|
{ |
|
pBasePlayer = pPlayer; |
|
} |
|
|
|
if ( !pBasePlayer || !pBasePlayer->IsAlive() ) |
|
continue; |
|
|
|
// Do a quick trace to see if there's any geometry in the way. |
|
trace_t trace; |
|
UTIL_TraceLine( GetAbsOrigin(), pBasePlayer->GetAbsOrigin(), ( MASK_SHOT & ~( CONTENTS_HITBOX ) ), &traceFilter, &trace ); |
|
if ( trace.DidHitWorld() ) |
|
continue; |
|
|
|
// Effects on the individual players |
|
ExplodeEffectOnTarget( pThrower, pPlayer, pBasePlayer ); |
|
} |
|
|
|
if ( TFGameRules() ) |
|
{ |
|
TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_PLAYER_SPELL_MERASMUS_ZAP, ( pThrower->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED ); |
|
} |
|
|
|
ApplyBlastDamage( pThrower, vecOrigin ); |
|
} |
|
else |
|
{ |
|
pThrower->EmitSound( "Player.DenyWeaponSelection" ); |
|
} |
|
} |
|
|
|
SetContextThink( &CBaseGrenade::SUB_Remove, gpGlobals->curtime, "RemoveThink" ); |
|
SetTouch( NULL ); |
|
|
|
AddEffects( EF_NODRAW ); |
|
SetAbsVelocity( vec3_origin ); |
|
} |
|
|
|
virtual void ApplyBlastDamage( CTFPlayer *pThrower, Vector vecOrigin ) |
|
{ |
|
CTakeDamageInfo info; |
|
info.SetAttacker( pThrower ); |
|
info.SetInflictor( this ); |
|
info.SetWeapon( GetLauncher() ); |
|
info.SetDamage( 10.f ); |
|
info.SetDamageCustom( GetCustomDamageType() ); |
|
info.SetDamagePosition( vecOrigin ); |
|
info.SetDamageType( DMG_BLAST ); |
|
|
|
CTFRadiusDamageInfo radiusinfo( &info, vecOrigin, GetDamageRadius(), pThrower ); |
|
TFGameRules()->RadiusDamage( radiusinfo ); |
|
} |
|
|
|
virtual bool InitialExplodeEffects( CTFPlayer *pThrower, const trace_t *pTrace ) |
|
{ |
|
// Added Particle |
|
Vector vecOrigin = GetAbsOrigin(); |
|
// Particle |
|
CPVSFilter filter( vecOrigin ); |
|
TE_TFExplosion( filter, 0.0f, vecOrigin, pTrace->plane.normal, TF_WEAPON_GRENADE_PIPEBOMB, kInvalidEHandleExplosion, -1, SPECIAL1, INVALID_STRING_INDEX ); |
|
|
|
return true; |
|
} |
|
|
|
virtual void ExplodeEffectOnTarget( CTFPlayer *pThrower, CTFPlayer *pTarget, CBaseCombatCharacter *pBaseTarget ) |
|
{ |
|
if ( pBaseTarget->GetTeamNumber() == GetTeamNumber() ) |
|
return; |
|
|
|
if ( pTarget ) |
|
{ |
|
if ( pTarget->m_Shared.IsInvulnerable() ) |
|
return; |
|
|
|
if ( pTarget->m_Shared.InCond( TF_COND_PHASE ) || pTarget->m_Shared.InCond( TF_COND_PASSTIME_INTERCEPTION ) ) |
|
return; |
|
|
|
// Stun the target |
|
pTarget->m_Shared.StunPlayer( 0.5, 0.5, TF_STUN_MOVEMENT, pThrower ); |
|
} |
|
|
|
Vector vecDir = pBaseTarget->WorldSpaceCenter() - GetAbsOrigin(); |
|
VectorNormalize( vecDir ); |
|
|
|
if ( pTarget ) |
|
{ |
|
pTarget->ApplyAirBlastImpulse( vecDir * 200.0f + Vector(0, 0, 800 ) ); |
|
const char* pszEffectName = GetTeamNumber() == TF_TEAM_RED ? "spell_batball_red" : "spell_batball_blue"; |
|
DispatchParticleEffect( pszEffectName, PATTACH_ABSORIGIN_FOLLOW, pTarget ); |
|
|
|
CTFSpellBook *pSpellBook = dynamic_cast< CTFSpellBook* >( pThrower->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) ); |
|
if ( pSpellBook ) |
|
{ |
|
pTarget->m_Shared.MakeBleed( pThrower, pSpellBook, 3.0f ); |
|
} |
|
} |
|
else |
|
{ |
|
pBaseTarget->ApplyAbsVelocityImpulse( vecDir * 1000.0f ); |
|
} |
|
|
|
const trace_t *pTrace = &CBaseEntity::GetTouchTrace(); |
|
trace_t *pNewTrace = const_cast<trace_t*>( pTrace ); |
|
|
|
CBaseEntity *pInflictor = GetLauncher(); |
|
CTakeDamageInfo info; |
|
info.SetAttacker( pThrower ); |
|
info.SetInflictor( this ); |
|
info.SetWeapon( pInflictor ); |
|
info.SetDamage( 40 ); |
|
info.SetDamageCustom( GetCustomDamageType() ); |
|
info.SetDamagePosition( GetAbsOrigin() ); |
|
info.SetDamageType( DMG_BURN ); |
|
|
|
// Hurt 'em. |
|
Vector dir; |
|
AngleVectors( GetAbsAngles(), &dir ); |
|
pBaseTarget->DispatchTraceAttack( info, dir, pNewTrace ); |
|
ApplyMultiDamage(); |
|
} |
|
|
|
virtual const char *GetExplodeEffectParticle() const { return GetTeamNumber() == TF_TEAM_RED ? "spell_batball_impact_red" : "spell_batball_impact_blue"; } |
|
virtual const char *GetExplodeEffectSound() const { return "Halloween.spell_bat_impact"; } |
|
#endif |
|
|
|
#ifdef CLIENT_DLL |
|
virtual const char* GetTrailParticleName( void ) { return GetTeamNumber() == TF_TEAM_RED ? "spell_batball_throw_red" : "spell_batball_throw_blue"; } |
|
#endif |
|
}; |
|
|
|
IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SpellBats, DT_TFProjectile_SpellBats ) |
|
BEGIN_NETWORK_TABLE( CTFProjectile_SpellBats, DT_TFProjectile_SpellBats ) |
|
END_NETWORK_TABLE() |
|
|
|
LINK_ENTITY_TO_CLASS( tf_projectile_spellbats, CTFProjectile_SpellBats ); |
|
PRECACHE_WEAPON_REGISTER( tf_projectile_spellbats ); |
|
|
|
// ************************************************************************************************************************* |
|
class CTFProjectile_SpellSpawnZombie : public CTFProjectile_SpellBats |
|
{ |
|
public: |
|
DECLARE_CLASS( CTFProjectile_SpellSpawnZombie, CTFProjectile_SpellBats ); |
|
DECLARE_NETWORKCLASS(); |
|
|
|
CTFProjectile_SpellSpawnZombie() |
|
{ |
|
#ifdef GAME_DLL |
|
m_skeletonType = 0; |
|
#endif // GAME_DLL |
|
} |
|
|
|
virtual float GetDamageRadius() const { return 1.0f; } |
|
virtual void SetCustomPipebombModel() { SetModel( "models/props_mvm/mvm_human_skull_collide.mdl" ); } |
|
virtual float GetModelScale() const { return 1.0f; } |
|
virtual int GetCustomDamageType() const OVERRIDE { return TF_DMG_CUSTOM_SPELL_SKELETON; } |
|
|
|
#ifdef GAME_DLL |
|
|
|
virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) { } |
|
virtual void PipebombTouch( CBaseEntity *pOther ) { } |
|
|
|
virtual void Explode( trace_t *pTrace, int bitsDamageType ) OVERRIDE |
|
{ |
|
// no owner? spawn skeletons anyways |
|
if ( !GetThrower() ) |
|
{ |
|
InitialExplodeEffects( NULL, pTrace ); |
|
|
|
// Particle |
|
if ( GetExplodeEffectParticle() ) |
|
{ |
|
CPVSFilter filter( GetAbsOrigin() ); |
|
TE_TFParticleEffect( filter, 0.0, GetExplodeEffectParticle(), GetAbsOrigin(), vec3_angle ); |
|
} |
|
|
|
// Sounds |
|
if ( GetExplodeEffectSound() ) |
|
{ |
|
EmitSound( GetExplodeEffectSound() ); |
|
} |
|
|
|
SetContextThink( &CBaseGrenade::SUB_Remove, gpGlobals->curtime, "RemoveThink" ); |
|
SetTouch( NULL ); |
|
|
|
AddEffects( EF_NODRAW ); |
|
SetAbsVelocity( vec3_origin ); |
|
|
|
return; |
|
} |
|
|
|
BaseClass::Explode( pTrace, bitsDamageType ); |
|
} |
|
|
|
virtual bool InitialExplodeEffects( CTFPlayer *pThrower, const trace_t *pTrace ) OVERRIDE |
|
{ |
|
// Pull in a little |
|
Vector vSpawnPoint = ( pTrace->endpos + ( pTrace->plane.normal * 2.0f ) ); |
|
CZombie::SpawnAtPos( vSpawnPoint, 30.0f, GetTeamNumber(), pThrower, (CZombie::SkeletonType_t)m_skeletonType ); |
|
return true; |
|
} |
|
virtual void ExplodeEffectOnTarget( CTFPlayer *pThrower, CTFPlayer *pTarget, CBaseCombatCharacter *pBaseTarget ) { } |
|
virtual const char *GetExplodeEffectParticle() const |
|
{ |
|
if ( GetTeamNumber() == TF_TEAM_HALLOWEEN ) |
|
{ |
|
return "spell_skeleton_goop_green"; |
|
} |
|
|
|
return GetTeamNumber() == TF_TEAM_RED ? "spell_pumpkin_mirv_goop_red" : "spell_pumpkin_mirv_goop_blue"; |
|
} |
|
virtual const char *GetExplodeEffectSound() const { return "Cleaver.ImpactFlesh"; } |
|
|
|
void SetSkeletonType ( int iType ) { m_skeletonType = iType; } |
|
|
|
int m_skeletonType; |
|
#endif |
|
|
|
#ifdef CLIENT_DLL |
|
virtual const char* GetTrailParticleName( void ) { return "unusual_bubbles_green_fumes"; } |
|
#endif |
|
}; |
|
|
|
// Spawn Zombie |
|
IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SpellSpawnZombie, DT_TFProjectile_SpellSpawnZombie ) |
|
BEGIN_NETWORK_TABLE( CTFProjectile_SpellSpawnZombie, DT_TFProjectile_SpellSpawnZombie ) |
|
END_NETWORK_TABLE() |
|
|
|
LINK_ENTITY_TO_CLASS( tf_projectile_spellspawnzombie, CTFProjectile_SpellSpawnZombie ); |
|
PRECACHE_WEAPON_REGISTER( tf_projectile_spellspawnzombie ); |
|
|
|
#ifdef GAME_DLL |
|
|
|
CBaseEntity* CreateSpellSpawnZombie( CBaseCombatCharacter *pCaster, const Vector& vSpawnPosition, int nSkeletonType ) |
|
{ |
|
Vector offset = RandomVector( -32, 32 ); |
|
offset.z = 16; |
|
CTFProjectile_SpellSpawnZombie *pGrenade = static_cast<CTFProjectile_SpellSpawnZombie*>( CBaseEntity::CreateNoSpawn( "tf_projectile_spellspawnzombie", vSpawnPosition + offset, RandomAngle( 0, 360 ), pCaster ) ); |
|
if ( pGrenade ) |
|
{ |
|
// Set the pipebomb mode before calling spawn, so the model & associated vphysics get setup properly. |
|
pGrenade->SetPipebombMode(); |
|
DispatchSpawn( pGrenade ); |
|
|
|
Vector vecImpulse = RandomVector( -1, 1 ); |
|
VectorNormalize( vecImpulse ); |
|
vecImpulse.z = RandomFloat( 1.0f, 1.6f ); |
|
Vector vecVelocity = vecImpulse * RandomFloat( 250.0f, 300.0f ); |
|
|
|
AngularImpulse angVelocity = AngularImpulse( 300, 300, 100 ); |
|
pGrenade->InitGrenade( vecVelocity, angVelocity, pCaster, 0, 0 ); |
|
pGrenade->ApplyLocalAngularVelocityImpulse( angVelocity ); |
|
pGrenade->SetDetonateTimerLength( RandomFloat( 2.f, 2.5f ) ); |
|
|
|
pGrenade->SetSkeletonType( nSkeletonType ); |
|
} |
|
|
|
return pGrenade; |
|
} |
|
|
|
#endif |
|
|
|
// ************************************************************************************************************************* |
|
class CTFProjectile_SpellSpawnHorde : public CTFProjectile_SpellBats |
|
{ |
|
public: |
|
DECLARE_CLASS( CTFProjectile_SpellSpawnHorde, CTFProjectile_SpellBats ); |
|
DECLARE_NETWORKCLASS(); |
|
|
|
virtual float GetDamageRadius() const { return 1.0f; } |
|
virtual void SetCustomPipebombModel() { SetModel( "models/props_mvm/mvm_human_skull_collide.mdl" ); } |
|
virtual float GetModelScale() const { return 1.0f; } |
|
virtual int GetCustomDamageType() const OVERRIDE { return TF_DMG_CUSTOM_SPELL_SKELETON; } |
|
|
|
#ifdef GAME_DLL |
|
//virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) { } |
|
//virtual void PipebombTouch( CBaseEntity *pOther ) { } |
|
|
|
virtual bool InitialExplodeEffects( CTFPlayer *pThrower, const trace_t *pTrace ) OVERRIDE |
|
{ |
|
// Spawn a tonne of extra skelatone grenades (mirv style) |
|
|
|
CTFSpellBook *pSpellBook = dynamic_cast< CTFSpellBook* >( pThrower->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) ); |
|
if ( !pSpellBook ) |
|
return false; |
|
|
|
for ( int i = 0; i < 3; i++ ) |
|
{ |
|
Vector offset = RandomVector( -32, 32 ); |
|
offset.z = 16; |
|
CreateSpellSpawnZombie( pThrower, GetAbsOrigin(), 0 ); |
|
} |
|
|
|
if ( TFGameRules() ) |
|
{ |
|
TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_PLAYER_SPELL_SKELETON_HORDE, ( pThrower->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED ); |
|
} |
|
|
|
return true; |
|
} |
|
virtual void ExplodeEffectOnTarget( CTFPlayer *pThrower, CTFPlayer *pTarget, CBaseCombatCharacter *pBaseTarget ) { } |
|
virtual const char *GetExplodeEffectParticle() const { return GetTeamNumber() == TF_TEAM_RED ? "spell_pumpkin_mirv_goop_red" : "spell_pumpkin_mirv_goop_blue"; } |
|
virtual const char *GetExplodeEffectSound() const { return "Halloween.spell_skeleton_horde_rise"; } |
|
#endif |
|
|
|
#ifdef CLIENT_DLL |
|
virtual const char* GetTrailParticleName( void ) { return "unusual_bubbles_green_fumes"; } |
|
#endif |
|
}; |
|
|
|
// Spawn Horde of Skels |
|
IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SpellSpawnHorde, DT_TFProjectile_SpellSpawnHorde ) |
|
BEGIN_NETWORK_TABLE( CTFProjectile_SpellSpawnHorde, DT_TFProjectile_SpellSpawnHorde ) |
|
END_NETWORK_TABLE() |
|
|
|
LINK_ENTITY_TO_CLASS( tf_projectile_spellspawnhorde, CTFProjectile_SpellSpawnHorde ); |
|
PRECACHE_WEAPON_REGISTER( tf_projectile_spellspawnhorde); |
|
|
|
|
|
// ************************************************************************************************************************* |
|
class CTFProjectile_SpellPumpkin : public CTFProjectile_SpellBats |
|
{ |
|
public: |
|
DECLARE_CLASS( CTFProjectile_SpellPumpkin, CTFProjectile_SpellBats ); |
|
DECLARE_NETWORKCLASS(); |
|
|
|
CTFProjectile_SpellPumpkin () |
|
{ |
|
#ifdef GAME_DLL |
|
m_flImpactTime = gpGlobals->curtime + 1.0f; |
|
#endif |
|
} |
|
virtual float GetDamageRadius() const { return 1.0f; } |
|
virtual void SetCustomPipebombModel() { SetModel( "models/weapons/w_models/w_cannonball.mdl" ); } |
|
virtual float GetModelScale() const { return 0.75f; } |
|
virtual int GetCustomDamageType() const OVERRIDE { return TF_DMG_CUSTOM_SPELL_MIRV; } |
|
#ifdef GAME_DLL |
|
// ignore collisions early in its lifetime |
|
virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) |
|
{ |
|
if ( gpGlobals->curtime < m_flImpactTime ) |
|
return; |
|
BaseClass::VPhysicsCollision( index, pEvent ); |
|
} |
|
virtual void PipebombTouch( CBaseEntity *pOther ) |
|
{ |
|
if ( gpGlobals->curtime < m_flImpactTime ) |
|
return; |
|
BaseClass::PipebombTouch( pOther ); |
|
} |
|
|
|
virtual void ApplyBlastDamage ( CTFPlayer *pThrower, Vector vecOrigin ) { } |
|
|
|
virtual bool InitialExplodeEffects( CTFPlayer *pThrower, const trace_t *pTrace ) OVERRIDE |
|
{ |
|
// Spawn a pumkin bomb here |
|
// Set the angles to what I want |
|
QAngle angle(0, RandomFloat( 0, 360 ) ,0); |
|
CTFPumpkinBomb *pGrenade = static_cast<CTFPumpkinBomb*>( CBaseEntity::CreateNoSpawn( "tf_pumpkin_bomb", GetAbsOrigin(), angle, NULL ) ); |
|
if ( pGrenade ) |
|
{ |
|
pGrenade->SetInitParams( 0.60, 80.0f, 200.0f, GetTeamNumber(), 40.0f + RandomFloat(0 , 1.0f) ); |
|
DispatchSpawn( pGrenade ); |
|
pGrenade->SetSpell( true ); |
|
} |
|
|
|
if ( TFGameRules() ) |
|
{ |
|
TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_PLAYER_SPELL_MIRV, ( pThrower->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED ); |
|
} |
|
|
|
return true; |
|
} |
|
virtual void ExplodeEffectOnTarget( CTFPlayer *pThrower, CTFPlayer *pTarget, CBaseCombatCharacter *pBaseTarget ) { } |
|
virtual const char *GetExplodeEffectParticle() const { return GetTeamNumber() == TF_TEAM_RED ? "spell_pumpkin_mirv_goop_red" : "spell_pumpkin_mirv_goop_blue"; } |
|
virtual const char *GetExplodeEffectSound() const { return "Halloween.spell_mirv_explode_secondary"; } |
|
|
|
float m_flImpactTime; |
|
#endif |
|
|
|
#ifdef CLIENT_DLL |
|
virtual const char* GetTrailParticleName( void ) { return "unusual_bubbles_green_fumes"; } |
|
#endif |
|
}; |
|
|
|
IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SpellPumpkin, DT_TFProjectile_SpellPumpkin ) |
|
BEGIN_NETWORK_TABLE( CTFProjectile_SpellPumpkin, DT_TFProjectile_SpellPumpkin) |
|
END_NETWORK_TABLE() |
|
|
|
LINK_ENTITY_TO_CLASS( tf_projectile_spellpumpkin, CTFProjectile_SpellPumpkin ); |
|
PRECACHE_WEAPON_REGISTER( tf_projectile_spellpumpkin); |
|
|
|
|
|
// ************************************************************************************************************************* |
|
class CTFProjectile_SpellMirv : public CTFProjectile_SpellBats |
|
{ |
|
public: |
|
DECLARE_CLASS( CTFProjectile_SpellMirv, CTFProjectile_SpellBats ); |
|
DECLARE_NETWORKCLASS(); |
|
|
|
virtual float GetDamageRadius() const { return 1.0f; } |
|
virtual void SetCustomPipebombModel() { SetModel( "models/weapons/w_models/w_cannonball.mdl" ); } |
|
virtual float GetModelScale() const { return 0.9f; } |
|
virtual int GetCustomDamageType() const OVERRIDE { return TF_DMG_CUSTOM_SPELL_MIRV; } |
|
|
|
#ifdef GAME_DLL |
|
//virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) { } |
|
//virtual void PipebombTouch( CBaseEntity *pOther ) { } |
|
|
|
virtual bool InitialExplodeEffects( CTFPlayer *pThrower, const trace_t *pTrace ) OVERRIDE |
|
{ |
|
// Spawn a tonne of extra grenades (mirv style) |
|
CTFSpellBook *pSpellBook = dynamic_cast< CTFSpellBook* >( pThrower->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) ); |
|
if ( !pSpellBook ) |
|
return false; |
|
|
|
// Create bomblets |
|
Vector offset = Vector( 0, -100, 400 ); |
|
|
|
for ( int i = 0; i < 6; i++ ) |
|
{ |
|
AngularImpulse angVelocity = AngularImpulse( 0, 0, RandomFloat( 100, 300) ); |
|
|
|
switch ( i ) |
|
{ |
|
case 0: offset = Vector( 75, 110, 400 ); break; |
|
case 1: offset = Vector( 75, -110, 400 ); break; |
|
case 2: offset = Vector( -75, 110, 400 ); break; |
|
case 3: offset = Vector( -75, -110, 400 ); break; |
|
case 4: offset = Vector( 135, 0, 400 ); break; |
|
case 5: offset = Vector( -135, 0, 400 ); break; |
|
} |
|
|
|
CTFProjectile_SpellPumpkin *pGrenade = static_cast<CTFProjectile_SpellPumpkin*>( CBaseEntity::CreateNoSpawn( "tf_projectile_spellpumpkin", GetAbsOrigin(), pThrower->EyeAngles(), pThrower ) ); |
|
if ( pGrenade ) |
|
{ |
|
// Set the pipebomb mode before calling spawn, so the model & associated vphysics get setup properly. |
|
pGrenade->SetPipebombMode(); |
|
DispatchSpawn( pGrenade ); |
|
pGrenade->InitGrenade( offset, angVelocity, pThrower, pSpellBook->GetTFWpnData() ); |
|
pGrenade->m_flFullDamage = 0; |
|
pGrenade->ApplyLocalAngularVelocityImpulse( angVelocity ); |
|
pGrenade->SetDetonateTimerLength( 2.0f + 0.05f * i ); |
|
} |
|
} |
|
|
|
if ( TFGameRules() ) |
|
{ |
|
TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_PLAYER_SPELL_MIRV, ( pThrower->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED ); |
|
} |
|
|
|
return true; |
|
} |
|
virtual void ExplodeEffectOnTarget( CTFPlayer *pThrower, CTFPlayer *pTarget, CBaseCombatCharacter *pBaseTarget ) { } |
|
virtual const char *GetExplodeEffectParticle() const { return GetTeamNumber() == TF_TEAM_RED ? "spell_pumpkin_mirv_goop_red" : "spell_pumpkin_mirv_goop_blue"; } |
|
virtual const char *GetExplodeEffectSound() const { return "Halloween.spell_mirv_explode_primary"; } |
|
#endif |
|
|
|
#ifdef CLIENT_DLL |
|
virtual const char* GetTrailParticleName( void ) { return "unusual_bubbles_green_fumes"; } |
|
#endif |
|
}; |
|
|
|
IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SpellMirv, DT_TFProjectile_SpellMirv ) |
|
BEGIN_NETWORK_TABLE( CTFProjectile_SpellMirv, DT_TFProjectile_SpellMirv) |
|
END_NETWORK_TABLE() |
|
|
|
LINK_ENTITY_TO_CLASS( tf_projectile_spellmirv, CTFProjectile_SpellMirv ); |
|
PRECACHE_WEAPON_REGISTER( tf_projectile_spellmirv); |
|
|
|
// ************************************************************************************************************************* |
|
class CTFProjectile_SpellSpawnBoss : public CTFProjectile_SpellBats |
|
{ |
|
public: |
|
DECLARE_CLASS( CTFProjectile_SpellSpawnBoss, CTFProjectile_SpellBats ); |
|
DECLARE_NETWORKCLASS(); |
|
|
|
virtual float GetDamageRadius() const { return 1.0f; } |
|
virtual void SetCustomPipebombModel() { SetModel( "models/props_mvm/mvm_human_skull_collide.mdl" ); } |
|
virtual float GetModelScale() const { return 1.5f; } |
|
virtual int GetCustomDamageType() const OVERRIDE { return TF_DMG_CUSTOM_SPELL_MONOCULUS; } |
|
|
|
#ifdef GAME_DLL |
|
|
|
//virtual void Explode( trace_t *pTrace, int bitsDamageType ); |
|
virtual bool InitialExplodeEffects( CTFPlayer *pThrower, const trace_t *pTrace ) OVERRIDE |
|
{ |
|
const Vector &vContactPoint = pTrace->endpos; |
|
CHalloweenBaseBoss *pBoss = CHalloweenBaseBoss::SpawnBossAtPos( HALLOWEEN_BOSS_MONOCULUS, vContactPoint, pThrower->GetTeamNumber(), pThrower ); |
|
if ( pBoss ) |
|
{ |
|
float flDesiredHeight = tf_eyeball_boss_hover_height.GetFloat(); |
|
|
|
const Vector &vMins = pBoss->WorldAlignMins(); |
|
const Vector &vMaxs = pBoss->WorldAlignMaxs(); |
|
Vector vSize = vMaxs - vMins; |
|
|
|
float flBossHeight = vSize.z; |
|
float flBossHalfX = 0.5f * vSize.x; |
|
float flBossHalfY = 0.5f * vSize.y; |
|
|
|
static Vector vTest[] = |
|
{ |
|
Vector( 0, 0, flBossHeight ), |
|
Vector( flBossHalfX, flBossHalfY, flBossHeight ), |
|
Vector( -flBossHalfX, -flBossHalfY, flBossHeight ), |
|
Vector( flBossHalfX, -flBossHalfY, flBossHeight ), |
|
Vector( -flBossHalfX, flBossHalfY, flBossHeight ) |
|
}; |
|
|
|
bool bFoundValidSpawnPos = false; |
|
for ( int i=0; i<ARRAYSIZE( vTest ); ++i ) |
|
{ |
|
trace_t result; |
|
float bloat = 5.0f; |
|
Vector vStart = vContactPoint + vTest[i] + 30.f * pTrace->plane.normal; |
|
Vector vEnd = vStart + Vector( 0, 0, flDesiredHeight ); |
|
|
|
CTraceFilterNoNPCsOrPlayer filter( pBoss, COLLISION_GROUP_NONE ); |
|
UTIL_TraceHull( vStart, vEnd, vMins - Vector( bloat, bloat, 0 ), vMaxs + Vector( bloat, bloat, bloat ), MASK_SOLID | CONTENTS_PLAYERCLIP, &filter, &result ); |
|
if ( !result.startsolid ) |
|
{ |
|
pBoss->SetAbsOrigin( result.endpos ); |
|
bFoundValidSpawnPos = true; |
|
//NDebugOverlay::SweptBox( vStart, vEnd, vMins - Vector( bloat, bloat, 0 ), vMaxs + Vector( bloat, bloat, bloat ), vec3_angle, 0, 255, 0, 0, 5.f ); |
|
//NDebugOverlay::Sphere( result.endpos, 10.f, 0, 255, 0, true, 5.f ); |
|
|
|
break; |
|
} |
|
else |
|
{ |
|
// Maybe we should play fail sound here? |
|
//NDebugOverlay::SweptBox( vStart, vEnd, vMins - Vector( bloat, bloat, 0 ), vMaxs + Vector( bloat, bloat, bloat ), vec3_angle, 255, 0, 0, 0, 5.f ); |
|
//NDebugOverlay::Sphere( result.endpos, 10.f, 255, 0, 0, true, 5.f ); |
|
} |
|
} |
|
|
|
// couldn't find any valid position |
|
if ( !bFoundValidSpawnPos ) |
|
{ |
|
UTIL_Remove( pBoss ); |
|
pBoss = NULL; |
|
} |
|
} |
|
|
|
// refund the player the spell |
|
if ( !pBoss ) |
|
{ |
|
CTFSpellBook *pSpellBook = dynamic_cast< CTFSpellBook* >( pThrower->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) ); |
|
if ( pSpellBook ) |
|
{ |
|
pSpellBook->SetSelectedSpell( GetSpellIndexFromContext( MP_CONCEPT_PLAYER_SPELL_MONOCULOUS ) ); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
if ( TFGameRules() ) |
|
{ |
|
TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_PLAYER_SPELL_MONOCULOUS, ( pThrower->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED ); |
|
} |
|
|
|
return true; |
|
} |
|
virtual void ExplodeEffectOnTarget( CTFPlayer *pThrower, CTFPlayer *pTarget, CBaseCombatCharacter *pBaseTarget ) { } |
|
virtual const char *GetExplodeEffectParticle() const { return "eyeboss_death"; } |
|
virtual const char *GetExplodeEffectSound() const { return "Halloween.spell_spawn_boss"; } |
|
#endif |
|
|
|
#ifdef CLIENT_DLL |
|
virtual const char* GetTrailParticleName( void ) { return "unusual_bubbles_green_fumes"; } |
|
#endif |
|
}; |
|
|
|
// Spawn Boss |
|
IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SpellSpawnBoss, DT_TFProjectile_SpellSpawnBoss ) |
|
BEGIN_NETWORK_TABLE( CTFProjectile_SpellSpawnBoss, DT_TFProjectile_SpellSpawnBoss ) |
|
END_NETWORK_TABLE() |
|
|
|
LINK_ENTITY_TO_CLASS( tf_projectile_spellspawnboss, CTFProjectile_SpellSpawnBoss ); |
|
PRECACHE_WEAPON_REGISTER( tf_projectile_spellspawnboss ); |
|
|
|
|
|
// ************************************************************************************************************************* |
|
#ifdef GAME_DLL |
|
class CTFSpell_MeteorShowerSpawner : public CBaseEntity |
|
{ |
|
public: |
|
DECLARE_DATADESC(); |
|
DECLARE_CLASS( CTFSpell_MeteorShowerSpawner, CBaseEntity ); |
|
|
|
virtual void Spawn() OVERRIDE |
|
{ |
|
m_flFinishTime = gpGlobals->curtime + 4.f; |
|
SetContextThink( &CTFSpell_MeteorShowerSpawner::MeteorShowerThink, gpGlobals->curtime, "MeteorShowerThink" ); |
|
} |
|
|
|
void MeteorShowerThink( void ) |
|
{ |
|
CTFPlayer *pPlayer = ToTFPlayer( GetOwnerEntity() ); |
|
if ( pPlayer && m_flFinishTime > gpGlobals->curtime ) |
|
{ |
|
// the owner changed team? remove this |
|
if ( pPlayer->GetTeamNumber() != GetTeamNumber() ) |
|
{ |
|
UTIL_Remove( this ); |
|
return; |
|
} |
|
|
|
// Determine our "height" offset range based on surface normal |
|
Vector vecDir = Vector( 0.f, 0.f, 1.f ); |
|
float flOffsetMin = 400.f; |
|
float flOffsetMax = 500.f; |
|
if ( m_vecImpactNormal.z <= -0.6f ) // Ceiling? |
|
{ |
|
flOffsetMin = 45.f; |
|
flOffsetMax = 60.f; |
|
vecDir.z = -1.f; |
|
} |
|
const float flRange = 200.f; |
|
const float flRandomAngleOffset = 75.f; |
|
|
|
const int nNumToSpawn = random->RandomInt( 1, 2 ); |
|
for ( int i = 0; i < nNumToSpawn; ++i ) |
|
{ |
|
// Vary start point away from surface center |
|
Vector vecOnPlane = Vector( RandomFloat( -flRange, flRange ), RandomFloat( -flRange, flRange ), 0.f ).Normalized(); |
|
Vector vecPointOnPlane = GetAbsOrigin() + random->RandomFloat( -flRange, flRange ) * vecOnPlane; |
|
const float flOffsetFromPlane = random->RandomFloat( flOffsetMin, flOffsetMax ); |
|
Vector vecEmit = vecPointOnPlane + flOffsetFromPlane * vecDir; |
|
|
|
// debugoverlay->AddLineOverlay( GetAbsOrigin(), vecEmit, 255, 0, 0, false, 10 ); |
|
|
|
Vector vecVelocity = Vector( RandomFloat( -flRandomAngleOffset, flRandomAngleOffset ), RandomFloat( -flRandomAngleOffset, flRandomAngleOffset ), -700.f ); |
|
|
|
// Check for a spot |
|
trace_t trace; |
|
UTIL_TraceLine( GetAbsOrigin(), vecEmit, ( MASK_SHOT & ~( CONTENTS_HITBOX ) ), NULL, COLLISION_GROUP_NONE, &trace ); |
|
if ( !trace.DidHit() ) |
|
{ |
|
SpawnMeteor( pPlayer, trace.endpos, vec3_angle, vecVelocity ); |
|
} |
|
else |
|
{ |
|
// Pull back and try again |
|
vecEmit = trace.endpos + ( trace.plane.normal * 1.0f ); |
|
SpawnMeteor( pPlayer, vecEmit, vec3_angle, vecVelocity ); |
|
} |
|
} |
|
|
|
SetContextThink( &CTFSpell_MeteorShowerSpawner::MeteorShowerThink, gpGlobals->curtime + 0.2f, "MeteorShowerThink" ); |
|
return; |
|
} |
|
|
|
UTIL_Remove( this ); |
|
} |
|
|
|
void SpawnMeteor( CTFPlayer *pOwner, const Vector &origin, const QAngle &angles, const Vector &velocity ) |
|
{ |
|
CTFProjectile_SpellFireball *pRocket = static_cast< CTFProjectile_SpellFireball* >( CBaseEntity::CreateNoSpawn( "tf_projectile_spellfireball", origin, angles, pOwner ) ); |
|
if ( pRocket ) |
|
{ |
|
pRocket->SetOwnerEntity( pOwner ); |
|
pRocket->SetLauncher( pOwner ); |
|
pRocket->SetAbsVelocity( velocity ); |
|
pRocket->SetDamage( 50.f ); |
|
pRocket->SetMeteor( true ); |
|
pRocket->ChangeTeam( GetTeamNumber() ); |
|
const char *pszParticle = GetTeamNumber() == TF_TEAM_BLUE ? "spell_fireball_tendril_parent_blue" : "spell_fireball_tendril_parent_red"; |
|
pRocket->SetExplodeParticleName( pszParticle ); |
|
|
|
IPhysicsObject *pPhysicsObject = pRocket->VPhysicsGetObject(); |
|
if ( pPhysicsObject ) |
|
{ |
|
pPhysicsObject->AddVelocity( &velocity, NULL ); |
|
} |
|
|
|
DispatchSpawn( pRocket ); |
|
} |
|
} |
|
|
|
void SetImpaceNormal( Vector &vecNormal ) { m_vecImpactNormal = vecNormal; } |
|
|
|
private: |
|
float m_flFinishTime; |
|
Vector m_vecImpactNormal; |
|
}; |
|
|
|
// Meteor Shower |
|
LINK_ENTITY_TO_CLASS( tf_spell_meteorshowerspawner, CTFSpell_MeteorShowerSpawner ); |
|
|
|
BEGIN_DATADESC( CTFSpell_MeteorShowerSpawner ) |
|
END_DATADESC() |
|
#endif // GAME_DLL |
|
|
|
// ************************************************************************************************************************* |
|
class CTFProjectile_SpellMeteorShower : public CTFProjectile_SpellBats |
|
{ |
|
public: |
|
DECLARE_CLASS( CTFProjectile_SpellMeteorShower, CTFProjectile_SpellBats ); |
|
DECLARE_NETWORKCLASS(); |
|
|
|
virtual float GetModelScale() const { return 0.01f; } |
|
|
|
#ifdef GAME_DLL |
|
virtual void Explode( trace_t *pTrace, int bitsDamageType ) OVERRIDE |
|
{ |
|
m_takedamage = DAMAGE_NO; |
|
|
|
// Pull out of the wall a bit. |
|
if ( pTrace->fraction != 1.0 ) |
|
{ |
|
SetAbsOrigin( pTrace->endpos + ( pTrace->plane.normal * 1.0f ) ); |
|
} |
|
|
|
Vector vecOrigin = GetAbsOrigin(); |
|
|
|
// Particle |
|
if ( GetExplodeEffectParticle() ) |
|
{ |
|
CPVSFilter filter( vecOrigin ); |
|
TE_TFParticleEffect( filter, 0.0, GetExplodeEffectParticle(), vecOrigin, vec3_angle ); |
|
} |
|
|
|
// Sounds |
|
if ( GetExplodeEffectSound() ) |
|
{ |
|
EmitSound( GetExplodeEffectSound() ); |
|
} |
|
|
|
CTFSpell_MeteorShowerSpawner *pSpawner = static_cast< CTFSpell_MeteorShowerSpawner* >( CBaseEntity::CreateNoSpawn( "tf_spell_meteorshowerspawner", vecOrigin, vec3_angle, GetThrower() ) ); |
|
if ( pSpawner ) |
|
{ |
|
pSpawner->SetImpaceNormal( pTrace->plane.normal ); |
|
pSpawner->ChangeTeam( GetTeamNumber() ); |
|
DispatchSpawn( pSpawner ); |
|
} |
|
|
|
if ( TFGameRules() ) |
|
{ |
|
CTFPlayer *pThrower = ToTFPlayer( GetOwnerEntity() ); |
|
if ( pThrower ) |
|
{ |
|
TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_PLAYER_SPELL_METEOR_SWARM, ( pThrower->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED ); |
|
} |
|
} |
|
|
|
SetModelName( NULL_STRING ); |
|
AddSolidFlags( FSOLID_NOT_SOLID ); |
|
SetTouch( NULL ); |
|
AddEffects( EF_NODRAW ); |
|
SetAbsVelocity( vec3_origin ); |
|
|
|
SetContextThink( &CBaseGrenade::SUB_Remove, gpGlobals->curtime, "RemoveThink" ); |
|
} |
|
|
|
virtual const char *GetExplodeEffectParticle() const { return "bomibomicon_ring"; } |
|
virtual const char *GetExplodeEffectSound() const { return "Halloween.spell_meteor_impact"; } |
|
#endif // GAME_DLL |
|
|
|
#ifdef CLIENT_DLL |
|
virtual const char *GetTrailParticleName( void ) |
|
{ |
|
return GetTeamNumber() == TF_TEAM_BLUE ? "spell_fireball_small_blue" : "spell_fireball_small_red"; |
|
} |
|
#endif |
|
}; |
|
|
|
// Meteor Shower |
|
IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SpellMeteorShower, DT_TFProjectile_SpellMeteorShower ) |
|
BEGIN_NETWORK_TABLE( CTFProjectile_SpellMeteorShower, DT_TFProjectile_SpellMeteorShower) |
|
END_NETWORK_TABLE() |
|
|
|
LINK_ENTITY_TO_CLASS( tf_projectile_spellmeteorshower, CTFProjectile_SpellMeteorShower ); |
|
PRECACHE_WEAPON_REGISTER( tf_projectile_spellmeteorshower ); |
|
|
|
|
|
// ************************************************************************************************************************* |
|
class CTFProjectile_SpellTransposeTeleport : public CTFProjectile_SpellBats |
|
{ |
|
public: |
|
DECLARE_CLASS( CTFProjectile_SpellTransposeTeleport, CTFProjectile_SpellBats ); |
|
DECLARE_NETWORKCLASS(); |
|
|
|
virtual void Spawn( void ) |
|
{ |
|
SetModelScale( 0.01f ); |
|
BaseClass::Spawn(); |
|
SetCollisionGroup( COLLISION_GROUP_PLAYER_MOVEMENT ); |
|
#ifdef GAME_DLL |
|
SetContextThink( &CTFProjectile_SpellTransposeTeleport::RecordPosThink, gpGlobals->curtime + 0.05f, "RecordThink" ); |
|
#endif |
|
} |
|
|
|
// FIX |
|
virtual int GetWeaponID( void ) const { return TF_PROJECTILE_SPELL; } |
|
virtual float GetDamageRadius() const { return 5.0f; } |
|
virtual int GetCustomDamageType() const OVERRIDE { return TF_DMG_CUSTOM_SPELL_TELEPORT; } |
|
|
|
virtual unsigned int PhysicsSolidMaskForEntity( void ) const |
|
{ |
|
return BaseClass::PhysicsSolidMaskForEntity() | CONTENTS_PLAYERCLIP; |
|
} |
|
|
|
#ifdef GAME_DLL |
|
|
|
void RecordPosThink( void ) |
|
{ |
|
m_vecTrailingPos.AddToTail( GetAbsOrigin() ); |
|
|
|
// Only retain 5 positions |
|
if ( m_vecTrailingPos.Count() > 5 ) |
|
{ |
|
m_vecTrailingPos.Remove( 0 ); |
|
} |
|
|
|
SetContextThink( &CTFProjectile_SpellTransposeTeleport::RecordPosThink, gpGlobals->curtime + 0.05f, "RecordThink" ); |
|
} |
|
|
|
virtual bool InitialExplodeEffects( CTFPlayer *pThrower, const trace_t *pTrace ) OVERRIDE |
|
{ |
|
if ( !pThrower->IsAlive() ) |
|
return false; |
|
|
|
// Grant a small amount of health |
|
pThrower->TakeHealth( 30, DMG_GENERIC ); |
|
|
|
trace_t result; |
|
CTraceFilterIgnoreTeammates traceFilter( this, COLLISION_GROUP_PLAYER_MOVEMENT, GetTeamNumber() ); |
|
unsigned int nMask = pThrower->GetTeamNumber() == TF_TEAM_RED ? CONTENTS_BLUETEAM : CONTENTS_REDTEAM; |
|
nMask |= MASK_PLAYERSOLID; |
|
|
|
m_vecTrailingPos.AddToTail( pTrace->endpos + ( pTrace->plane.normal * 50.f ) ); |
|
|
|
// Try a few spots |
|
FOR_EACH_VEC_BACK( m_vecTrailingPos, i ) |
|
{ |
|
// Try positions starting with the current, and moving back in time a bit |
|
Vector vecStart = m_vecTrailingPos[i]; |
|
UTIL_TraceHull( vecStart, vecStart, VEC_HULL_MIN, VEC_HULL_MAX, nMask, &traceFilter, &result ); |
|
|
|
if( !result.DidHit() ) |
|
{ |
|
// Place a teleport effect where they came from |
|
const Vector& vecOrigin = pThrower->GetAbsOrigin(); |
|
CPVSFilter pvsFilter( vecOrigin ); |
|
TE_TFParticleEffect( pvsFilter, 0.0, GetExplodeEffectParticle(), vecOrigin, vec3_angle ); |
|
|
|
// Move 'em! |
|
pThrower->Teleport( &vecStart, &pThrower->GetAbsAngles(), NULL ); |
|
|
|
// Do a zoom effect |
|
pThrower->SetFOV( pThrower, 0, 0.3f, 120 ); |
|
|
|
// Screen flash |
|
color32 fadeColor = {255,255,255,100}; |
|
UTIL_ScreenFade( pThrower, fadeColor, 0.25, 0.4, FFADE_IN ); |
|
|
|
if ( TFGameRules() ) |
|
{ |
|
TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_PLAYER_SPELL_TELEPORT, ( pThrower->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED ); |
|
} |
|
|
|
// Success! |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
virtual void ExplodeEffectOnTarget( CTFPlayer *pThrower, CTFPlayer *pTarget, CBaseCombatCharacter *pBaseTarget ) |
|
{ |
|
// ... |
|
} |
|
virtual const char *GetExplodeEffectParticle() const { return "eyeboss_tp_player"; } |
|
virtual const char *GetExplodeEffectSound() const { return "Building_Teleporter.Ready"; } |
|
#endif |
|
|
|
#ifdef CLIENT_DLL |
|
virtual const char* GetTrailParticleName( void ) { return GetTeamNumber() == TF_TEAM_RED ? "spell_teleport_red" : "spell_teleport_blue"; } |
|
#endif |
|
|
|
private: |
|
#ifdef GAME_DLL |
|
CUtlVector< Vector > m_vecTrailingPos; |
|
#endif |
|
}; |
|
|
|
// Spawn Boss |
|
IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SpellTransposeTeleport, SpellTransposeTeleport ) |
|
BEGIN_NETWORK_TABLE( CTFProjectile_SpellTransposeTeleport, SpellTransposeTeleport ) |
|
END_NETWORK_TABLE() |
|
|
|
LINK_ENTITY_TO_CLASS( tf_projectile_spelltransposeteleport, CTFProjectile_SpellTransposeTeleport ); |
|
PRECACHE_WEAPON_REGISTER( tf_projectile_spelltransposeteleport ); |
|
|
|
#ifdef GAME_DLL |
|
void RemoveAll2013HalloweenTeleportSpellsInMidFlight( void ) |
|
{ |
|
CBaseEntity *pTeleport = NULL; |
|
while ( ( pTeleport = gEntList.FindEntityByClassname( pTeleport, "tf_projectile_spelltransposeteleport" ) ) != NULL ) |
|
{ |
|
UTIL_Remove( pTeleport ); |
|
} |
|
} |
|
#endif |
|
|
|
// ************************************************************************************************************************* |
|
class CTFProjectile_SpellLightningOrb : public CTFProjectile_SpellFireball |
|
{ |
|
public: |
|
DECLARE_CLASS( CTFProjectile_SpellLightningOrb, CTFProjectile_SpellFireball ); |
|
DECLARE_NETWORKCLASS(); |
|
|
|
~CTFProjectile_SpellLightningOrb() |
|
{ |
|
#ifdef CLIENT_DLL |
|
if ( m_pTrailParticle ) |
|
{ |
|
ParticleProp()->StopEmissionAndDestroyImmediately( m_pTrailParticle ); |
|
m_pTrailParticle = NULL; |
|
} |
|
#endif // CLIENT_DLL |
|
} |
|
|
|
#ifdef GAME_DLL |
|
|
|
virtual void Spawn() OVERRIDE |
|
{ |
|
BaseClass::Spawn(); |
|
|
|
// We dont want to collide with anything but the world |
|
SetSolid( SOLID_NONE ); |
|
|
|
SetExplodeParticleName( GetTeamNumber() == TF_TEAM_BLUE ? "drg_cow_explosioncore_charged_blue" : "drg_cow_explosioncore_charged" ); |
|
SetContextThink( &CTFProjectile_SpellLightningOrb::ZapThink, gpGlobals->curtime + 0.25f, "ZapThink" ); |
|
SetContextThink( &CTFProjectile_SpellLightningOrb::VortexThink, gpGlobals->curtime + 0.2f, "VortexThink" ); |
|
SetContextThink( &CTFProjectile_SpellLightningOrb::ExplodeAndRemove, gpGlobals->curtime + 5.f, "ExplodeAndRemoveThink" ); |
|
SetDamage( 20.f ); |
|
} |
|
|
|
virtual const char *GetProjectileModelName( void ) { return ""; } // We dont have a model by default, and that's OK |
|
|
|
virtual float GetDamageRadius() const { return 200.f; } |
|
virtual int GetCustomDamageType() const OVERRIDE { return TF_DMG_CUSTOM_SPELL_LIGHTNING; } |
|
|
|
virtual void RocketTouch( CBaseEntity *pOther ) OVERRIDE |
|
{ |
|
Assert( pOther ); |
|
if ( !pOther ) |
|
return; |
|
|
|
if ( !pOther->IsSolid() || pOther->IsSolidFlagSet( FSOLID_VOLUME_CONTENTS ) ) |
|
return; |
|
|
|
const trace_t *pTrace = &CBaseEntity::GetTouchTrace(); |
|
// Bounce off the world |
|
if ( pOther->IsWorld() ) |
|
{ |
|
Vector vIntoSurface = pTrace->plane.normal * pTrace->plane.normal.Dot( GetAbsVelocity() ); |
|
SetAbsVelocity( GetAbsVelocity() + ( -1.5f * vIntoSurface ) ); |
|
return; |
|
} |
|
|
|
// Handle hitting skybox (disappear). |
|
if ( pTrace->surface.flags & SURF_SKY ) |
|
{ |
|
UTIL_Remove( this ); |
|
return; |
|
} |
|
|
|
// pass through ladders |
|
if( pTrace->surface.flags & CONTENTS_LADDER ) |
|
return; |
|
|
|
if ( pOther->IsPlayer() ) |
|
return; |
|
|
|
// Spell ends when we run into something |
|
ExplodeAndRemove(); |
|
return; |
|
} |
|
|
|
virtual bool InitialExplodeEffects( CTFPlayer *pThrower, const trace_t *pTrace ) OVERRIDE |
|
{ |
|
Zap( 16 ); |
|
|
|
return true; |
|
} |
|
|
|
virtual const char *GetExplodeEffectSound() const { return "Halloween.spell_lightning_impact"; } |
|
virtual void ExplodeEffectOnTarget( CTFPlayer *pThrower, CTFPlayer *pTarget, CBaseCombatCharacter *pBaseTarget ) OVERRIDE |
|
{} |
|
|
|
void ExplodeAndRemove() |
|
{ |
|
// Particle |
|
if ( GetExplodeEffectParticle() ) |
|
{ |
|
CPVSFilter filter( GetAbsOrigin() ); |
|
TE_TFParticleEffect( filter, 0.0, GetExplodeEffectParticle(), GetAbsOrigin(), vec3_angle ); |
|
|
|
EmitSound( filter, entindex(), GetExplodeEffectSound() ); |
|
} |
|
|
|
// Go out with a bang |
|
Zap( 16 ); |
|
|
|
SetContextThink( &CBaseGrenade::SUB_Remove, gpGlobals->curtime, "RemoveThink" ); |
|
return; |
|
} |
|
|
|
void ZapThink() |
|
{ |
|
Zap( 2 ); |
|
SetContextThink( &CTFProjectile_SpellLightningOrb::ZapThink, gpGlobals->curtime + RandomFloat( 0.25f, 0.35f ), "ZapThink" ); |
|
} |
|
|
|
void Zap( int nNumToZap ) |
|
{ |
|
CBaseEntity *pOwner = GetOwnerEntity(); |
|
|
|
if ( !pOwner ) |
|
return; |
|
|
|
CTakeDamageInfo info; |
|
info.SetAttacker( pOwner ); |
|
info.SetInflictor( this ); |
|
info.SetWeapon( GetLauncher() ); |
|
info.SetDamage( GetDamage() ); |
|
info.SetDamageCustom( GetCustomDamageType() ); |
|
info.SetDamagePosition( GetAbsOrigin() ); |
|
info.SetDamageType( DMG_BURN ); |
|
|
|
CBaseEntity *pListOfEntities[5]; |
|
int iEntities = UTIL_EntitiesInSphere( pListOfEntities, 5, GetAbsOrigin(), GetDamageRadius(), FL_CLIENT | FL_FAKECLIENT | FL_NPC ); |
|
|
|
// Shuffle the list |
|
for( int i = iEntities - 1; i > 0; --i ) |
|
{ |
|
V_swap( pListOfEntities[i], pListOfEntities[ RandomInt( 0, i ) ] ); |
|
} |
|
|
|
// Zap as many targets as we're told to, if we can |
|
int nHits = 0; |
|
for ( int i = 0; i < iEntities && nHits < nNumToZap; ++i ) |
|
{ |
|
CBaseEntity* pTarget = pListOfEntities[i]; |
|
|
|
if ( !pTarget ) |
|
continue; |
|
|
|
if ( !pTarget->IsAlive() ) |
|
continue; |
|
|
|
if ( pOwner->InSameTeam( pTarget ) ) |
|
continue; |
|
|
|
if ( !FVisible( pTarget, MASK_OPAQUE ) ) |
|
continue; |
|
|
|
CTFPlayer *pTFPlayer = ToTFPlayer( pTarget ); |
|
if ( pTFPlayer ) |
|
{ |
|
if ( pTFPlayer->m_Shared.InCond( TF_COND_PHASE ) || pTFPlayer->m_Shared.InCond( TF_COND_PASSTIME_INTERCEPTION ) ) |
|
continue; |
|
|
|
if ( pTFPlayer->m_Shared.IsInvulnerable() ) |
|
continue; |
|
} |
|
|
|
CTraceFilterIgnoreTeammates tracefilter( this, COLLISION_GROUP_NONE, GetTeamNumber() ); |
|
trace_t trace; |
|
UTIL_TraceLine( GetAbsOrigin(), pTarget->GetAbsOrigin(), ( MASK_SHOT & ~( CONTENTS_HITBOX ) ), &tracefilter, &trace ); |
|
if ( trace.DidHitWorld() ) |
|
continue; |
|
|
|
// Shoot a beam at them |
|
CPVSFilter filter( pTarget->WorldSpaceCenter() ); |
|
Vector vStart = WorldSpaceCenter(); |
|
Vector vEnd = pTarget->EyePosition(); |
|
const char *pszHitEffect = GetTeamNumber() == TF_TEAM_BLUE ? "spell_lightningball_hit_blue" : "spell_lightningball_hit_red"; |
|
te_tf_particle_effects_control_point_t controlPoint = { PATTACH_ABSORIGIN, vEnd }; |
|
TE_TFParticleEffectComplex( filter, 0.0f, pszHitEffect, vStart, QAngle( 0, 0, 0 ), NULL, &controlPoint, pTFPlayer, PATTACH_CUSTOMORIGIN ); |
|
|
|
// Hurt 'em. |
|
Vector dir; |
|
AngleVectors( GetAbsAngles(), &dir ); |
|
pTarget->DispatchTraceAttack( info, dir, &trace ); |
|
ApplyMultiDamage(); |
|
|
|
++nHits; |
|
} |
|
|
|
// We zapped someone. Play a sound |
|
if ( nHits > 0 ) |
|
{ |
|
pOwner->EmitSound( "TFPlayer.MedicChargedDeath" ); |
|
|
|
if ( TFGameRules() ) |
|
{ |
|
CTFPlayer *pThrower = ToTFPlayer( GetOwnerEntity() ); |
|
if ( pThrower ) |
|
{ |
|
TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_PLAYER_SPELL_LIGHTNING_BALL, ( pThrower->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
void VortexThink( void ) |
|
{ |
|
const int nMaxEnts = 32; |
|
|
|
Vector vecPos = GetAbsOrigin(); |
|
CBaseEntity *pObjects[ nMaxEnts ]; |
|
int nCount = UTIL_EntitiesInSphere( pObjects, nMaxEnts, vecPos, GetDamageRadius(), FL_CLIENT | FL_NPC ); |
|
|
|
// NDebugOverlay::Sphere( vecPos, GetDamageRadius(), 0, 255, 0, false, 0.35f ); |
|
|
|
// Iterate through sphere's contents |
|
for ( int i = 0; i < nCount; i++ ) |
|
{ |
|
CBaseCombatCharacter *pEntity = pObjects[i]->MyCombatCharacterPointer(); |
|
if ( !pEntity ) |
|
continue; |
|
|
|
if ( InSameTeam( pEntity ) ) |
|
continue; |
|
|
|
if ( !FVisible( pEntity, MASK_OPAQUE ) ) |
|
continue; |
|
|
|
// Draw player toward us |
|
Vector vecSourcePos = pEntity->GetAbsOrigin(); |
|
Vector vecTargetPos = GetAbsOrigin(); |
|
Vector vecVelocity = ( vecTargetPos - vecSourcePos ) * 2.f; |
|
vecVelocity.z += 50.f; |
|
|
|
if ( pEntity->GetFlags() & FL_ONGROUND ) |
|
{ |
|
vecVelocity.z += 150.f; |
|
pEntity->SetGroundEntity( NULL ); |
|
pEntity->SetGroundChangeTime( gpGlobals->curtime + 0.5f ); |
|
} |
|
|
|
pEntity->Teleport( NULL, NULL, &vecVelocity ); |
|
} |
|
|
|
SetContextThink( &CTFProjectile_SpellLightningOrb::VortexThink, gpGlobals->curtime + 0.2f, "VortexThink" ); |
|
return; |
|
} |
|
#endif |
|
|
|
#ifdef CLIENT_DLL |
|
virtual const char *GetTrailParticleName( void ) { return NULL; } // CRUTGUN! |
|
virtual void CreateTrails( void ) |
|
{ |
|
BaseClass::CreateTrails(); |
|
|
|
if ( !m_pTrailParticle ) |
|
{ |
|
m_pTrailParticle = ParticleProp()->Create( ( GetTeamNumber() == TF_TEAM_BLUE ? "spell_lightningball_parent_blue" : "spell_lightningball_parent_red" ), PATTACH_ABSORIGIN_FOLLOW ); |
|
} |
|
} |
|
|
|
private: |
|
CNewParticleEffect *m_pTrailParticle; |
|
#endif // CLIENT_DLL |
|
}; |
|
|
|
// Lightning ball |
|
IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SpellLightningOrb, DT_TFProjectile_SpellLightningOrb ) |
|
BEGIN_NETWORK_TABLE( CTFProjectile_SpellLightningOrb, DT_TFProjectile_SpellLightningOrb ) |
|
END_NETWORK_TABLE() |
|
|
|
LINK_ENTITY_TO_CLASS( tf_projectile_lightningorb, CTFProjectile_SpellLightningOrb ); |
|
PRECACHE_WEAPON_REGISTER( tf_projectile_lightningorb); |
|
|
|
|
|
#ifdef CLIENT_DLL |
|
#define CTFHellZap C_TFHellZap |
|
#endif |
|
|
|
#ifdef GAME_DLL |
|
#include "tf_obj_dispenser.h" |
|
#include "particle_parse.h" |
|
#include "tf_fx.h" |
|
#endif |
|
|
|
class CTFHellZap : public CBaseEntity |
|
{ |
|
DECLARE_CLASS( CTFHellZap, CBaseEntity ) |
|
DECLARE_NETWORKCLASS(); |
|
DECLARE_DATADESC(); |
|
public: |
|
CTFHellZap() |
|
#ifdef GAME_DLL |
|
: m_eType( ZAP_ON_TOUCH ) |
|
#endif |
|
{} |
|
#ifdef GAME_DLL |
|
|
|
enum EZapperType |
|
{ |
|
ZAP_ON_TOUCH, |
|
ZAP_ON_TEST, |
|
}; |
|
|
|
virtual void Spawn() |
|
{ |
|
m_bEnabled = true; |
|
|
|
if ( m_iszCustomTouchTrigger != NULL_STRING ) |
|
{ |
|
m_hTouchTrigger = dynamic_cast<CDispenserTouchTrigger *> ( gEntList.FindEntityByName( NULL, m_iszCustomTouchTrigger ) ); |
|
|
|
if ( m_hTouchTrigger.Get() != NULL ) |
|
{ |
|
Assert( m_hTouchTrigger->GetOwnerEntity() == NULL ); |
|
m_hTouchTrigger->SetOwnerEntity( this ); //owned |
|
} |
|
} |
|
} |
|
|
|
void ZapAllTouching() |
|
{ |
|
FOR_EACH_VEC_BACK( m_vecZapTargets, i ) |
|
{ |
|
CBaseEntity* pZapTarget = m_vecZapTargets[i].Get(); |
|
// Remove targets that have disappeared |
|
if ( !pZapTarget ) |
|
{ |
|
m_vecZapTargets.Remove( i ); |
|
continue; |
|
} |
|
|
|
// Shoot a beam at them |
|
CPVSFilter filter( pZapTarget->WorldSpaceCenter() ); |
|
Vector vStart = WorldSpaceCenter(); |
|
Vector vEnd = pZapTarget->WorldSpaceCenter(); |
|
te_tf_particle_effects_control_point_t controlPoint = { PATTACH_CUSTOMORIGIN, vEnd }; |
|
TE_TFParticleEffectComplex( filter, 0.0f, m_iszParticleName.ToCStr(), vStart, QAngle( 0, 0, 0 ), NULL, &controlPoint, this, PATTACH_CUSTOMORIGIN ); |
|
} |
|
} |
|
|
|
void ZapThink() |
|
{ |
|
ZapAllTouching(); |
|
|
|
// Keep zapping if we have targets |
|
if ( m_vecZapTargets.Count() ) |
|
{ |
|
SetContextThink( &CTFHellZap::ZapThink, gpGlobals->curtime + 0.25f, "ZapThink" ); |
|
} |
|
} |
|
|
|
virtual void StartTouch( CBaseEntity *pEntity ) |
|
{ |
|
m_vecZapTargets.AddToTail( pEntity ); |
|
|
|
if ( m_eType == ZAP_ON_TOUCH ) |
|
{ |
|
SetContextThink( &CTFHellZap::ZapThink, gpGlobals->curtime, "ZapThink" ); |
|
} |
|
} |
|
|
|
virtual void EndTouch( CBaseEntity *pEntity ) |
|
{ |
|
int nIndex = m_vecZapTargets.Find( pEntity ); |
|
if( nIndex != m_vecZapTargets.InvalidIndex() ) |
|
{ |
|
m_vecZapTargets.Remove( nIndex ); |
|
} |
|
|
|
// No more targets. Stop thinking! |
|
if ( m_vecZapTargets.Count() == 0 && m_eType == ZAP_ON_TOUCH ) |
|
{ |
|
SetContextThink( NULL, 0, "ZapThink" ); |
|
} |
|
} |
|
|
|
void InputEnable( inputdata_t &inputdata ) |
|
{ |
|
m_bEnabled = true; |
|
|
|
if ( m_vecZapTargets.Count() > 0 && m_eType == ZAP_ON_TOUCH ) |
|
{ |
|
SetContextThink( &CTFHellZap::ZapThink, gpGlobals->curtime, "ZapThink" ); |
|
} |
|
} |
|
|
|
void InputDisable( inputdata_t &inputdata ) |
|
{ |
|
m_bEnabled = false; |
|
SetContextThink( NULL, 0, "ZapThink" ); |
|
} |
|
|
|
void InputZapAllTouching( inputdata_t &inputdata ) |
|
{ |
|
ZapAllTouching(); |
|
} |
|
private: |
|
|
|
EZapperType m_eType; |
|
bool m_bEnabled; |
|
EHANDLE m_hTouchTrigger; |
|
string_t m_iszCustomTouchTrigger; |
|
string_t m_iszParticleName; |
|
CUtlVector< EHANDLE > m_vecZapTargets; |
|
#endif |
|
}; |
|
|
|
|
|
BEGIN_DATADESC( CTFHellZap ) |
|
#ifdef GAME_DLL |
|
DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "ZapTouching", InputZapAllTouching ), |
|
|
|
DEFINE_KEYFIELD( m_iszCustomTouchTrigger, FIELD_STRING, "touch_trigger" ), |
|
DEFINE_KEYFIELD( m_iszParticleName, FIELD_STRING, "ParticleEffect" ), |
|
DEFINE_KEYFIELD( m_eType, FIELD_INTEGER, "ZapperType" ), |
|
#endif |
|
END_DATADESC() |
|
|
|
LINK_ENTITY_TO_CLASS( halloween_zapper, CTFHellZap ); |
|
IMPLEMENT_NETWORKCLASS_ALIASED( TFHellZap, DT_TFHellZap ) |
|
|
|
BEGIN_NETWORK_TABLE( CTFHellZap, DT_TFHellZap ) |
|
END_NETWORK_TABLE() |
|
|
|
|
|
//******************************************************************************************************************************************************* |
|
// Kart Spells |
|
//******************************************************************************************************************************************************* |
|
class CTFProjectile_SpellKartOrb: public CTFProjectile_SpellFireball |
|
{ |
|
public: |
|
DECLARE_CLASS( CTFProjectile_SpellKartOrb, CTFProjectile_SpellFireball ); |
|
DECLARE_NETWORKCLASS(); |
|
|
|
#ifdef GAME_DLL |
|
virtual void Spawn() OVERRIDE |
|
{ |
|
BaseClass::Spawn(); |
|
SetContextThink( &CTFProjectile_SpellKartOrb::ExplodeAndRemove, gpGlobals->curtime + tf_halloween_kart_rocketspell_lifetime.GetFloat(), "ExplodeAndRemoveThink" ); |
|
SetContextThink( &CTFProjectile_SpellKartOrb::MoveChecking, gpGlobals->curtime + 0.05f, "MoveCheckingThink" ); |
|
|
|
SetModel( SPELL_BOXING_GLOVE ); |
|
SetModelScale( 2.5f ); |
|
|
|
Vector mins( -20, -20, 0 ); |
|
Vector maxs( 20, 20, 20 ); |
|
UTIL_SetSize( this, mins, maxs ); |
|
} |
|
|
|
virtual void RocketTouch( CBaseEntity *pOther ) OVERRIDE |
|
{ |
|
Assert( pOther ); |
|
if ( !pOther ) |
|
return; |
|
|
|
if ( !pOther->IsSolid() || pOther->IsSolidFlagSet( FSOLID_VOLUME_CONTENTS ) ) |
|
return; |
|
|
|
// Handle hitting skybox (disappear). |
|
const trace_t *pTrace = &CBaseEntity::GetTouchTrace(); |
|
if ( pTrace->surface.flags & SURF_SKY ) |
|
{ |
|
UTIL_Remove( this ); |
|
return; |
|
} |
|
|
|
// Bounce off the world |
|
if ( pOther->IsWorld() ) |
|
{ |
|
Vector vOld = GetAbsVelocity(); |
|
//float flSpeed = vOld.Length(); |
|
Vector vNew = ( -2.0f * pTrace->plane.normal.Dot( vOld ) * pTrace->plane.normal + vOld ); |
|
vNew.NormalizeInPlace(); |
|
vNew *= tf_halloween_kart_rocketspell_speed.GetFloat(); |
|
SetAbsVelocity( vNew ); |
|
return; |
|
} |
|
|
|
// pass through ladders |
|
if ( pTrace->surface.flags & CONTENTS_LADDER ) |
|
return; |
|
|
|
if ( pOther->IsPlayer() ) |
|
ExplodeAndRemove(); |
|
|
|
// Spell ends when we run into something |
|
//ExplodeAndRemove(); |
|
return; |
|
} |
|
|
|
void MoveChecking () |
|
{ |
|
// do a short trace down, if nothing is there, add a bit of downward velocity |
|
trace_t pTrace; |
|
Vector vecSpot = GetAbsOrigin() ; |
|
UTIL_TraceLine( vecSpot, vecSpot - Vector(0, 0, 32), MASK_SOLID, this, COLLISION_GROUP_NONE, &pTrace ); |
|
|
|
if ( pTrace.fraction >= 1.0 ) |
|
{ |
|
// Start moving down |
|
SetAbsVelocity( GetAbsVelocity() - Vector( 0, 0, 128 ) ); |
|
} |
|
|
|
SetContextThink( &CTFProjectile_SpellKartOrb::MoveChecking, gpGlobals->curtime + 0.05f, "MoveCheckingThink" ); |
|
} |
|
|
|
void ExplodeAndRemove() |
|
{ |
|
// Handle hitting skybox (disappear). |
|
const trace_t *pTrace = &CBaseEntity::GetTouchTrace(); |
|
|
|
if ( pTrace->surface.flags & SURF_SKY ) |
|
{ |
|
UTIL_Remove( this ); |
|
return; |
|
} |
|
|
|
// pass through ladders |
|
if ( pTrace->surface.flags & CONTENTS_LADDER ) |
|
return; |
|
|
|
Explode( pTrace ); |
|
|
|
UTIL_Remove( this ); |
|
} |
|
|
|
// We dont deal actual damage, just Car damage |
|
virtual void Explode( const trace_t *pTrace ) |
|
{ |
|
SetModelName( NULL_STRING );//invisible |
|
AddSolidFlags( FSOLID_NOT_SOLID ); |
|
|
|
m_takedamage = DAMAGE_NO; |
|
|
|
// Pull out of the wall a bit. |
|
if ( pTrace->fraction != 1.0 ) |
|
{ |
|
SetAbsOrigin( pTrace->endpos + ( pTrace->plane.normal * 1.0f ) ); |
|
} |
|
|
|
CTFPlayer *pThrower = ToTFPlayer( GetOwnerEntity() ); |
|
if ( pThrower ) |
|
{ |
|
const Vector &vecOrigin = GetAbsOrigin(); |
|
|
|
// Any effects from the initial explosion |
|
if ( InitialExplodeEffects( pThrower, pTrace ) ) |
|
{ |
|
// Particle |
|
if ( GetExplodeEffectParticle() ) |
|
{ |
|
CPVSFilter filter( vecOrigin ); |
|
TE_TFParticleEffect( filter, 0.0, GetExplodeEffectParticle(), vecOrigin, vec3_angle ); |
|
} |
|
|
|
// Sounds |
|
if ( GetExplodeEffectSound() ) |
|
{ |
|
EmitSound( GetExplodeEffectSound() ); |
|
} |
|
|
|
// Treat this trace exactly like radius damage |
|
CTraceFilterIgnorePlayers traceFilter( pThrower, COLLISION_GROUP_PROJECTILE ); |
|
|
|
// Splash pee on everyone nearby. |
|
CBaseEntity *pListOfEntities[32]; |
|
int iEntities = UTIL_EntitiesInSphere( pListOfEntities, 32, vecOrigin, GetDamageRadius(), FL_CLIENT | FL_FAKECLIENT | FL_NPC ); |
|
for ( int i = 0; i < iEntities; ++i ) |
|
{ |
|
CBaseCombatCharacter *pBasePlayer = NULL; |
|
CTFPlayer *pPlayer = ToTFPlayer( pListOfEntities[i] ); |
|
if ( !pPlayer ) |
|
{ |
|
pBasePlayer = dynamic_cast<CBaseCombatCharacter*>( pListOfEntities[i] ); |
|
} |
|
else |
|
{ |
|
pBasePlayer = pPlayer; |
|
} |
|
|
|
if ( !pBasePlayer || !pPlayer || !pPlayer->IsAlive() || InSameTeam(pPlayer) ) |
|
continue; |
|
|
|
// Do a quick trace to see if there's any geometry in the way. |
|
trace_t trace; |
|
UTIL_TraceLine( vecOrigin, pPlayer->WorldSpaceCenter(), ( MASK_SHOT & ~( CONTENTS_HITBOX ) ), &traceFilter, &trace ); |
|
//debugoverlay->AddLineOverlay( vecOrigin, pPlayer->WorldSpaceCenter(), 255, 0, 0, false, 10 ); |
|
if ( trace.DidHitWorld() ) |
|
continue; |
|
|
|
// Effects on the individual players |
|
//ExplodeEffectOnTarget( pThrower, pPlayer, pBasePlayer ); |
|
|
|
// Apply Car Damage and a force |
|
Vector vecDir = pPlayer->WorldSpaceCenter() - GetAbsOrigin(); |
|
vecDir.NormalizeInPlace(); |
|
Vector vecForward, vecRight, vecUp; |
|
AngleVectors( pPlayer->GetAnimRenderAngles(), &vecForward, &vecRight, &vecUp ); |
|
vecDir += ( vecUp * 0.5f ); |
|
pPlayer->AddHalloweenKartPushEvent( pThrower, this, pThrower->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ), vecDir * tf_halloween_kart_rocketspell_force.GetFloat(), 50.0f ); |
|
} |
|
} |
|
else |
|
{ |
|
pThrower->EmitSound( "Player.DenyWeaponSelection" ); |
|
} |
|
} |
|
} |
|
|
|
virtual const char *GetExplodeEffectParticle() const { return "ExplosionCore_MidAir"; } |
|
#endif |
|
|
|
#ifdef CLIENT_DLL |
|
virtual const char *GetTrailParticleName( void ) |
|
{ |
|
return "halloween_rockettrail"; |
|
} |
|
#endif |
|
}; |
|
|
|
// Kart Spell Orbs |
|
IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SpellKartOrb, DT_TFProjectile_SpellKartOrb ) |
|
BEGIN_NETWORK_TABLE( CTFProjectile_SpellKartOrb, DT_TFProjectile_SpellKartOrb ) |
|
END_NETWORK_TABLE() |
|
|
|
LINK_ENTITY_TO_CLASS( tf_projectile_spellkartorb, CTFProjectile_SpellKartOrb ); |
|
PRECACHE_WEAPON_REGISTER( tf_projectile_spellkartorb ); |
|
|
|
class CTFProjectile_SpellKartBats : public CTFProjectile_SpellBats |
|
{ |
|
public: |
|
DECLARE_CLASS( CTFProjectile_SpellKartBats, CTFProjectile_SpellBats ); |
|
DECLARE_NETWORKCLASS(); |
|
|
|
#ifdef GAME_DLL |
|
virtual void ApplyBlastDamage( CTFPlayer *pThrower, Vector vecOrigin ) |
|
{ |
|
|
|
} |
|
|
|
virtual void ExplodeEffectOnTarget( CTFPlayer *pThrower, CTFPlayer *pTarget, CBaseCombatCharacter *pBaseTarget ) |
|
{ |
|
if ( pBaseTarget->GetTeamNumber() == GetTeamNumber() ) |
|
return; |
|
|
|
if ( !pTarget ) |
|
return; |
|
|
|
if ( pTarget->m_Shared.IsInvulnerable() ) |
|
return; |
|
|
|
if ( pTarget->m_Shared.InCond( TF_COND_PHASE ) || pTarget->m_Shared.InCond( TF_COND_PASSTIME_INTERCEPTION ) ) |
|
return; |
|
|
|
// Stun the target |
|
pTarget->m_Shared.StunPlayer( 0.5, 0.5, TF_STUN_MOVEMENT, pThrower ); |
|
|
|
Vector vecDir = pBaseTarget->WorldSpaceCenter() - GetAbsOrigin(); |
|
VectorNormalize( vecDir ); |
|
Vector vecForward, vecRight, vecUp; |
|
AngleVectors( pTarget->GetAnimRenderAngles(), &vecForward, &vecRight, &vecUp ); |
|
vecDir += ( vecUp * 0.5f ); |
|
|
|
if ( pTarget ) |
|
{ |
|
pTarget->AddHalloweenKartPushEvent( pThrower, this, pThrower->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ), vecDir * tf_halloween_kart_normal_speed.GetFloat() * 1.10f, 45.0f ); |
|
|
|
const char* pszEffectName = GetTeamNumber() == TF_TEAM_RED ? "spell_batball_red" : "spell_batball_blue"; |
|
DispatchParticleEffect( pszEffectName, PATTACH_ABSORIGIN_FOLLOW, pTarget ); |
|
} |
|
} |
|
#endif |
|
}; |
|
|
|
|
|
IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SpellKartBats, DT_TFProjectile_SpellKartBats ) |
|
BEGIN_NETWORK_TABLE( CTFProjectile_SpellKartBats, DT_TFProjectile_SpellKartBats ) |
|
END_NETWORK_TABLE() |
|
|
|
LINK_ENTITY_TO_CLASS( tf_projectile_spellkartbats, CTFProjectile_SpellKartBats ); |
|
PRECACHE_WEAPON_REGISTER( tf_projectile_spellkartbats ); |
|
|
|
//// ************************************************************************************************************************* |
|
//class CTFProjectile_SpellKartPumpkin : public CTFProjectile_SpellPumpkin |
|
//{ |
|
//public: |
|
// DECLARE_CLASS( CTFProjectile_SpellKartPumpkin, CTFProjectile_SpellPumpkin ); |
|
// DECLARE_NETWORKCLASS(); |
|
// |
|
//#ifdef GAME_DLL |
|
// virtual bool InitialExplodeEffects( CTFPlayer *pThrower, const trace_t *pTrace ) OVERRIDE |
|
// { |
|
// // Spawn a pumkin bomb here |
|
// // Set the angles to what I want |
|
// QAngle angle( 0, RandomFloat( 0, 360 ), 0 ); |
|
// CTFPumpkinBomb *pGrenade = static_cast<CTFPumpkinBomb*>( CBaseEntity::CreateNoSpawn( "tf_pumpkin_bomb", GetAbsOrigin(), angle, NULL ) ); |
|
// if ( pGrenade ) |
|
// { |
|
// pGrenade->SetInitParams( 0.60, 80.0f, 200.0f, GetTeamNumber(), 40.0f + RandomFloat( 0, 1.0f ) ); |
|
// DispatchSpawn( pGrenade ); |
|
// pGrenade->SetSpell( true ); |
|
// pGrenade->TakeDamage( CTakeDamageInfo( pThrower, pThrower, 10.f, DMG_CRUSH ) ); |
|
// } |
|
// |
|
// if ( TFGameRules() ) |
|
// { |
|
// TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_PLAYER_SPELL_MIRV, ( pThrower->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED ); |
|
// } |
|
// |
|
// return true; |
|
// } |
|
//#endif |
|
//}; |
|
// |
|
//IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SpellKartPumpkin, DT_TFProjectile_SpellKartPumpkin ) |
|
//BEGIN_NETWORK_TABLE( CTFProjectile_SpellKartPumpkin, DT_TFProjectile_SpellKartPumpkin ) |
|
//END_NETWORK_TABLE() |
|
// |
|
//LINK_ENTITY_TO_CLASS( tf_projectile_spellkartpumpkin, CTFProjectile_SpellKartPumpkin ); |
|
//PRECACHE_WEAPON_REGISTER( tf_projectile_spellkartpumpkin ); |
|
// |
|
////************* |
|
//// ************************************************************************************************************************* |
|
//class CTFProjectile_SpellKartMirv : public CTFProjectile_SpellMirv |
|
//{ |
|
//public: |
|
// DECLARE_CLASS( CTFProjectile_SpellKartMirv, CTFProjectile_SpellMirv ); |
|
// DECLARE_NETWORKCLASS(); |
|
// |
|
//#ifdef GAME_DLL |
|
// |
|
// virtual bool InitialExplodeEffects( CTFPlayer *pThrower, const trace_t *pTrace ) OVERRIDE |
|
// { |
|
// // Spawn a tonne of extra grenades (mirv style) |
|
// CTFSpellBook *pSpellBook = dynamic_cast<CTFSpellBook*>( pThrower->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) ); |
|
// if ( !pSpellBook ) |
|
// return false; |
|
// |
|
// // Create bomblets |
|
// Vector offset = Vector( 0, -100, 400 ); |
|
// |
|
// for ( int i = 0; i < 6; i++ ) |
|
// { |
|
// AngularImpulse angVelocity = AngularImpulse( 0, 0, RandomFloat( 100, 300 ) ); |
|
// |
|
// switch ( i ) |
|
// { |
|
// case 0: offset = Vector( 75, 110, 400 ); break; |
|
// case 1: offset = Vector( 75, -110, 400 ); break; |
|
// case 2: offset = Vector( -75, 110, 400 ); break; |
|
// case 3: offset = Vector( -75, -110, 400 ); break; |
|
// case 4: offset = Vector( 135, 0, 400 ); break; |
|
// case 5: offset = Vector( -135, 0, 400 ); break; |
|
// } |
|
// |
|
// CTFProjectile_SpellPumpkin *pGrenade = static_cast<CTFProjectile_SpellPumpkin*>( CBaseEntity::CreateNoSpawn( "tf_projectile_spellkartpumpkin", GetAbsOrigin(), pThrower->EyeAngles(), pThrower ) ); |
|
// if ( pGrenade ) |
|
// { |
|
// // Set the pipebomb mode before calling spawn, so the model & associated vphysics get setup properly. |
|
// pGrenade->SetPipebombMode(); |
|
// DispatchSpawn( pGrenade ); |
|
// pGrenade->InitGrenade( offset, angVelocity, pThrower, pSpellBook->GetTFWpnData() ); |
|
// pGrenade->m_flFullDamage = 0; |
|
// pGrenade->ApplyLocalAngularVelocityImpulse( angVelocity ); |
|
// pGrenade->SetDetonateTimerLength( 2.0f + 0.05f * i ); |
|
// } |
|
// } |
|
// |
|
// if ( TFGameRules() ) |
|
// { |
|
// TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_PLAYER_SPELL_MIRV, ( pThrower->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED ); |
|
// } |
|
// |
|
// return true; |
|
// } |
|
//#endif |
|
//}; |
|
// |
|
//IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SpellKartMirv, DT_TFProjectile_SpellKartMirv ) |
|
//BEGIN_NETWORK_TABLE( CTFProjectile_SpellKartMirv, DT_TFProjectile_SpellKartMirv ) |
|
//END_NETWORK_TABLE() |
|
// |
|
//LINK_ENTITY_TO_CLASS( tf_projectile_spellkartmirv, CTFProjectile_SpellKartMirv ); |
|
//PRECACHE_WEAPON_REGISTER( tf_projectile_spellkartmirv );
|