//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //============================================================================= #include "cbase.h" #include "tf_weapon_sword.h" // Client specific. #ifdef CLIENT_DLL #include "c_tf_player.h" #include "econ_entity.h" // Server specific. #else #include "tf_player.h" #endif //============================================================================= // // Weapon Sword tables. // // CTFSword IMPLEMENT_NETWORKCLASS_ALIASED( TFSword, DT_TFWeaponSword ) BEGIN_NETWORK_TABLE( CTFSword, DT_TFWeaponSword ) END_NETWORK_TABLE() BEGIN_PREDICTION_DATA( CTFSword ) END_PREDICTION_DATA() LINK_ENTITY_TO_CLASS( tf_weapon_sword, CTFSword ); PRECACHE_WEAPON_REGISTER( tf_weapon_sword ); // CTFKatana IMPLEMENT_NETWORKCLASS_ALIASED( TFKatana, DT_TFWeaponKatana ) BEGIN_NETWORK_TABLE( CTFKatana, DT_TFWeaponKatana ) #ifdef CLIENT_DLL RecvPropBool( RECVINFO( m_bIsBloody ) ), #else SendPropBool( SENDINFO( m_bIsBloody ) ), #endif END_NETWORK_TABLE() BEGIN_PREDICTION_DATA( CTFKatana ) END_PREDICTION_DATA() LINK_ENTITY_TO_CLASS( tf_weapon_katana, CTFKatana ); PRECACHE_WEAPON_REGISTER( tf_weapon_katana ); //============================================================================= // // Decapitation melee weapon base implementation. // //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFDecapitationMeleeWeaponBase::CTFDecapitationMeleeWeaponBase() : m_bHolstering( false ) { } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFDecapitationMeleeWeaponBase::Precache() { BaseClass::Precache(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CTFDecapitationMeleeWeaponBase::GetMeleeDamage( CBaseEntity *pTarget, int* piDamageType, int* piCustomDamage ) { float flBaseDamage = BaseClass::GetMeleeDamage( pTarget, piDamageType, piCustomDamage ); *piCustomDamage = TF_DMG_CUSTOM_DECAPITATION; return flBaseDamage; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- Activity CTFDecapitationMeleeWeaponBase::TranslateViewmodelHandActivityInternal( Activity actBase ) { CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( !pPlayer ) return BaseClass::TranslateViewmodelHandActivityInternal( actBase ); CEconItemView *pEconItemView = GetAttributeContainer()->GetItem(); if ( pEconItemView ) { if ( pEconItemView->GetAnimationSlot() == TF_WPN_TYPE_MELEE_ALLCLASS ) return BaseClass::TranslateViewmodelHandActivityInternal( actBase ); } // Alright, so we have some decapitation weapons (katana) that can be used // by both the soldier and the demoman, but the classes play totally different // animations using the same weapon. // // This logic is also responsible for playing the correct animations on the // demo when he's using non-shared weapons like the Eyelanders. if ( pPlayer->GetPlayerClass()->GetClassIndex() == TF_CLASS_DEMOMAN ) { switch ( actBase ) { case ACT_VM_DRAW: actBase = ACT_VM_DRAW_SPECIAL; break; case ACT_VM_HOLSTER: actBase = ACT_VM_HOLSTER_SPECIAL; break; case ACT_VM_IDLE: actBase = ACT_VM_IDLE_SPECIAL; break; case ACT_VM_PULLBACK: actBase = ACT_VM_PULLBACK_SPECIAL; break; case ACT_VM_PRIMARYATTACK: actBase = ACT_VM_PRIMARYATTACK_SPECIAL; break; case ACT_VM_SECONDARYATTACK: actBase = ACT_VM_PRIMARYATTACK_SPECIAL; break; case ACT_VM_HITCENTER: actBase = ACT_VM_HITCENTER_SPECIAL; break; case ACT_VM_SWINGHARD: actBase = ACT_VM_SWINGHARD_SPECIAL; break; case ACT_VM_IDLE_TO_LOWERED: actBase = ACT_VM_IDLE_TO_LOWERED_SPECIAL; break; case ACT_VM_IDLE_LOWERED: actBase = ACT_VM_IDLE_LOWERED_SPECIAL; break; case ACT_VM_LOWERED_TO_IDLE: actBase = ACT_VM_LOWERED_TO_IDLE_SPECIAL; break; default: break; } } return BaseClass::TranslateViewmodelHandActivityInternal( actBase ); } /* //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFDecapitationMeleeWeaponBase::SendWeaponAnim( int iActivity ) { }*/ //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFDecapitationMeleeWeaponBase::CanDecapitate( void ) { CEconItemView *pScriptItem = GetAttributeContainer()->GetItem(); if ( !pScriptItem ) return false; CEconItemDefinition *pStaticData = pScriptItem->GetStaticData(); if ( !pStaticData ) return false; int iDecapitateType = 0; CALL_ATTRIB_HOOK_INT( iDecapitateType, decapitate_type ); return iDecapitateType != 0; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFDecapitationMeleeWeaponBase::SetupGameEventListeners( void ) { ListenForGameEvent( "player_death" ); } #ifdef CLIENT_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFDecapitationMeleeWeaponBase::UpdateAttachmentModels( void ) { BaseClass::UpdateAttachmentModels(); CTFPlayer *pTFPlayer = GetTFPlayerOwner(); if ( !pTFPlayer ) return; if ( !pTFPlayer->IsLocalPlayer() ) return; if ( !pTFPlayer->GetViewModel() ) return; if ( !pTFPlayer->m_Shared.IsShieldEquipped() ) return; CTFWearableDemoShield* pMyShield = dynamic_cast( m_hShield.Get() ); if ( pMyShield ) { pMyShield->UpdateAttachmentModels(); } else { // Find a shield wearable... for ( int i=0; iGetNumWearables(); ++i ) { CEconWearable* pItem = pTFPlayer->GetWearable(i); if ( !pItem ) continue; pMyShield = dynamic_cast( pItem ); if ( !pMyShield ) continue; m_hShield.Set( pMyShield ); } if ( pMyShield ) { pMyShield->UpdateAttachmentModels(); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFDecapitationMeleeWeaponBase::DrawOverriddenViewmodel( C_BaseViewModel *pViewmodel, int flags ) { int iRes = BaseClass::DrawOverriddenViewmodel( pViewmodel, flags ); CTFWearableDemoShield* pMyShield = dynamic_cast( m_hShield.Get() ); if ( pMyShield ) { pMyShield->DrawOverriddenViewmodel( pViewmodel, flags ); } return iRes; } #endif // CLIENT_DLL //============================================================================= // // Weapon Sword functions. // //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFSword::WeaponReset( void ) { BaseClass::WeaponReset(); #ifdef CLIENT_DLL m_flNextIdleWavRoll = 0; m_iPrevWavDecap = 0; #endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFSword::GetSwingRange( void ) { CTFPlayer *pOwner = ToTFPlayer( GetOwner() ); if ( pOwner && pOwner->m_Shared.InCond( TF_COND_SHIELD_CHARGE ) ) { return 128; } else { //int iRange = 0; //CALL_ATTRIB_HOOK_INT( iRange, is_a_sword ) //return 72; return 72; } } //----------------------------------------------------------------------------- // Purpose: A speed mod that is applied even if the weapon isn't in hand. //----------------------------------------------------------------------------- float CTFSword::GetSwordSpeedMod( void ) { if ( m_bHolstering ) return 1.f; CTFPlayer *pOwner = ToTFPlayer( GetOwner() ); if ( !pOwner ) return 0; if ( !CanDecapitate() ) return 1.f; int iDecaps = MIN( MAX_DECAPITATIONS, pOwner->m_Shared.GetDecapitations() ); return 1.f + (iDecaps * 0.08f); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFSword::GetSwordHealthMod( void ) { CTFPlayer *pOwner = ToTFPlayer( GetOwner() ); if ( !pOwner ) return 0; if ( !CanDecapitate() ) return 0.f; int iDecaps = MIN( MAX_DECAPITATIONS, pOwner->m_Shared.GetDecapitations() ); return (iDecaps * 15); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFSword::OnDecapitation( CTFPlayer *pDeadPlayer ) { BaseClass::OnDecapitation( pDeadPlayer ); #ifdef GAME_DLL CTFPlayer *pOwner = ToTFPlayer( GetOwner() ); Assert( pOwner ); // The pyro's scythe is actually a "sword" as far as the gameplay code is concerned, // but we don't want the demo's head-collecting silliness to apply. if ( pOwner->GetPlayerClass()->GetClassIndex() == TF_CLASS_DEMOMAN ) { // We have chopped someone's bloody 'ead off! int iDecap = pOwner->m_Shared.GetDecapitations(); // transfer decapitations if ( pDeadPlayer ) { iDecap += pDeadPlayer->m_Shared.GetDecapitations(); } pOwner->m_Shared.SetDecapitations( ++iDecap ); pOwner->TeamFortress_SetSpeed(); if ( pOwner->m_Shared.GetBestOverhealDecayMult() == -1.f ) { pOwner->m_Shared.SetBestOverhealDecayMult( 0.25f ); } if ( pOwner->GetHealth() < pOwner->m_Shared.GetMaxBuffedHealth() ) { pOwner->TakeHealth( 15, DMG_IGNORE_MAXHEALTH ); } if ( !pOwner->m_Shared.InCond( TF_COND_DEMO_BUFF ) ) { pOwner->m_Shared.AddCond( TF_COND_DEMO_BUFF ); } } #endif // GAME_DLL } #ifdef GAME_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFDecapitationMeleeWeaponBase::Holster( CBaseCombatWeapon *pSwitchingTo ) { m_bHolstering = true; bool res = BaseClass::Holster( pSwitchingTo ); m_bHolstering = false; if ( res ) { StopListeningForAllEvents(); } return res; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFDecapitationMeleeWeaponBase::FireGameEvent( IGameEvent *event ) { const char *pszEventName = event->GetName(); if ( Q_strcmp( pszEventName, "player_death" ) != 0 ) return; CTFPlayer *pOwner = ToTFPlayer( GetOwner() ); if ( !pOwner ) return; int iAttackerID = event->GetInt( "attacker" ); if ( iAttackerID != pOwner->GetUserID() ) return; int iWeaponID = event->GetInt( "weaponid" ); int iCustomKill = event->GetInt( "customkill" ); if ( iWeaponID != TF_WEAPON_SWORD && iCustomKill != TF_DMG_CUSTOM_TAUNTATK_BARBARIAN_SWING ) return; // Off with their heads! if ( !CanDecapitate() ) return; OnDecapitation( ToTFPlayer( UTIL_PlayerByUserId( event->GetInt( "userid" ) ) ) ); } #endif //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFSword::Deploy( void ) { bool res = BaseClass::Deploy(); if ( !CanDecapitate() ) { CTFPlayer *pOwner = ToTFPlayer( GetOwner() ); if ( !pOwner ) return res; pOwner->m_Shared.SetDecapitations( 0 ); return res; } #ifdef GAME_DLL if ( res ) { SetupGameEventListeners(); } #else // When we go active, there's a chance we immediately thirst for heads. if ( RandomInt( 1,4 ) == 1 ) { m_flNextIdleWavRoll = gpGlobals->curtime + 3.0; } else { m_flNextIdleWavRoll = gpGlobals->curtime + RandomFloat( 5.0, 30.0 ); } #endif return res; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFSword::GetCount( void ) { CTFPlayer *pOwner = ToTFPlayer( GetOwner() ); if ( !pOwner ) return 0; if ( !CanDecapitate() ) return 0; return pOwner->m_Shared.GetDecapitations(); } #ifdef CLIENT_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFSword::WeaponIdle( void ) { BaseClass::WeaponIdle(); CTFPlayer *pOwner = ToTFPlayer( GetOwner() ); if ( !pOwner ) return; if ( !CanDecapitate() ) return; // If we've decapped someone recently, we roll shortly afterwards int iDecaps = pOwner->m_Shared.GetDecapitations(); if ( m_iPrevWavDecap < iDecaps ) { m_flNextIdleWavRoll = MIN( m_flNextIdleWavRoll, gpGlobals->curtime + RandomFloat(3,5) ); } m_iPrevWavDecap = iDecaps; // Randomly play sounds to the local player. The more decaps we have, the more chance it's a good wav. if ( m_flNextIdleWavRoll < gpGlobals->curtime ) { float flChance = RemapValClamped( iDecaps, 0, 10, 0.25, 0.9 ); if ( RandomFloat() <= flChance ) { // Chance to get the more powerful wav: float flChanceForGoodWav = RemapValClamped( iDecaps, 0, 10, 0.1, 0.75 ); if ( RandomFloat() <= flChanceForGoodWav ) { WeaponSound( SPECIAL2 ); } else { WeaponSound( SPECIAL1 ); } } m_flNextIdleWavRoll = gpGlobals->curtime + RandomFloat( 30.0, 60.0 ); } } #endif //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFKatana::CTFKatana() { m_bIsBloody = false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFKatana::Deploy( void ) { bool res = BaseClass::Deploy(); m_bIsBloody = false; if ( CanDecapitate() && res ) { #ifdef GAME_DLL SetupGameEventListeners(); #endif } return res; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CTFKatana::GetMeleeDamage( CBaseEntity *pTarget, int* piDamageType, int* piCustomDamage ) { // Start with our base damage. We use this to generate our custom damage flags, // if any. We may trash the damage amount. float fDamage = BaseClass::GetMeleeDamage( pTarget, piDamageType, piCustomDamage ); // The katana is a weapon of honor!!!! (Hitting someone wielding a katana with // your katana results in a massive damage boost, a one-hit kill.) if ( IsHonorBound() ) { CTFPlayer *pTFPlayerTarget = ToTFPlayer( pTarget ); if ( pTFPlayerTarget ) { // If our victim is wielding the weapon we're looking for, bump the damage way up. if ( pTFPlayerTarget->GetActiveTFWeapon() && pTFPlayerTarget->GetActiveTFWeapon()->IsHonorBound() ) { fDamage = MAX( fDamage, pTFPlayerTarget->GetHealth() * 3 ); *piDamageType |= DMG_DONT_COUNT_DAMAGE_TOWARDS_CRIT_RATE; } } } return fDamage; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFKatana::GetActivityWeaponRole() const { CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( pPlayer && pPlayer->GetPlayerClass()->GetClassIndex() == TF_CLASS_DEMOMAN ) { // demo should use act table item1 return TF_WPN_TYPE_ITEM1; } return BaseClass::GetActivityWeaponRole(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFKatana::OnDecapitation( CTFPlayer *pDeadPlayer ) { BaseClass::OnDecapitation( pDeadPlayer ); #ifndef CLIENT_DLL m_bIsBloody = true; #endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFKatana::GetSkinOverride() const { if ( m_bIsBloody && !UTIL_IsLowViolence() ) { CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( pPlayer ) { return ( ( pPlayer->GetTeamNumber() == TF_TEAM_RED ) ? 2 : 3 ); } } return BaseClass::GetSkinOverride(); }