diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/tf2/tf_player.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/server/tf2/tf_player.cpp')
| -rw-r--r-- | game/server/tf2/tf_player.cpp | 3720 |
1 files changed, 3720 insertions, 0 deletions
diff --git a/game/server/tf2/tf_player.cpp b/game/server/tf2/tf_player.cpp new file mode 100644 index 0000000..6631454 --- /dev/null +++ b/game/server/tf2/tf_player.cpp @@ -0,0 +1,3720 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: TF2's player object. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include <stdarg.h> +#include "player.h" +#include "tf_player.h" +#include "gamerules.h" +#include "trains.h" +#include "entitylist.h" +#include "menu_base.h" +#include "basecombatweapon.h" +#include "controlzone.h" +#include "tf_shareddefs.h" +#include "AmmoDef.h" +#include "techtree.h" +#include "in_buttons.h" +#include "tf_team.h" +#include "client.h" +#include "baseviewmodel.h" +#include "tf_gamerules.h" +#include "tf_obj.h" +#include "weapon_builder.h" +#include "orders.h" +#include "decals.h" +#include "tf_func_resource.h" +#include "resource_chunk.h" +#include "team_messages.h" +#include "tier0/dbg.h" +#include "tf_obj_respawn_station.h" +#include "tf_obj_resourcepump.h" +#include "tf_class_commando.h" +#include "tf_class_defender.h" +#include "tf_class_escort.h" +#include "tf_class_infiltrator.h" +#include "tf_class_medic.h" +#include "tf_class_recon.h" +#include "tf_class_sniper.h" +#include "tf_class_support.h" +#include "tf_class_sapper.h" +#include "sendproxy.h" +#include "ragdoll_shadow.h" +#include "vstdlib/random.h" +#include "engine/IEngineSound.h" +#include "bone_setup.h" +#include "weapon_combatshield.h" +#include "weapon_twohandedcontainer.h" +#include "NDebugOverlay.h" +#include "tier1/strtools.h" +#include "IEffects.h" +#include "info_act.h" +#include "ai_basehumanoid.h" +#include "tf_stats.h" +#include "iservervehicle.h" +#include "tf_vehicle_teleport_station.h" +#include "globals.h" + +#define MAX_EXPLOSIVE_VELOCITY 600.0f + +extern ConVar tf_knockdowntime; + +extern ConVar inv_demo; + +ConVar tf_autoteam( "tf_autoteam", "1", 0, "Automatically place players on the team with the least players." ); +ConVar tf_destroyobjects( "tf_destroyobjects", "1", FCVAR_CHEAT, "Destroy objects when players change class or team." ); + +IMPLEMENT_SERVERCLASS_ST(CBaseTFPlayer, DT_BaseTFPlayer) + SendPropDataTable(SENDINFO_DT(m_TFLocal), &REFERENCE_SEND_TABLE(DT_TFLocal), SendProxy_SendLocalDataTable), + + SendPropInt(SENDINFO(m_iPlayerClass), 4, SPROP_UNSIGNED), + + // Class Data Tables + SendPropDataTable( SENDINFO_DT( m_PlayerClasses ), &REFERENCE_SEND_TABLE( DT_AllPlayerClasses ), SendProxy_SendLocalDataTable ), + + SendPropEHandle( SENDINFO( m_hSelectedMCV ) ), + SendPropInt( SENDINFO(m_iCurrentZoneState ), 3 ), + SendPropInt( SENDINFO(m_iMaxHealth ), 8, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(m_TFPlayerFlags), TF_PLAYER_NUMFLAGS, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_bUnderAttack ), 1, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_bIsBlocking ), 1, SPROP_UNSIGNED ), + + // Sniper - will get moved to a class data table + SendPropVector( SENDINFO(m_vecDeployedAngles), -1, SPROP_COORD ), + SendPropInt( SENDINFO( m_bDeployed ), 1, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_bDeploying ), 1, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_bUnDeploying ), 1, SPROP_UNSIGNED ), + + // Infiltrator - will get moved to a class data table + SendPropFloat( SENDINFO( m_flCamouflageAmount ), 7, SPROP_ROUNDDOWN, 0.0f, 100.0f ), + + SendPropEHandle(SENDINFO(m_hSpawnPoint)), + + SendPropExclude( "DT_BaseAnimating" , "m_flPoseParameter" ), + SendPropExclude( "DT_BaseAnimating" , "m_flPlaybackRate" ), + +END_SEND_TABLE() + +LINK_ENTITY_TO_CLASS( player, CBaseTFPlayer ); +PRECACHE_REGISTER(player); + +BEGIN_DATADESC( CBaseTFPlayer ) + + DEFINE_INPUTFUNC( FIELD_VOID, "Respawn", InputRespawn ), + + // Function Pointers + DEFINE_THINKFUNC( TFPlayerDeathThink ), + +END_DATADESC() + + +BEGIN_PREDICTION_DATA_NO_BASE( CTFPlayerLocalData ) +END_PREDICTION_DATA() + +BEGIN_PREDICTION_DATA( CBaseTFPlayer ) +END_PREDICTION_DATA() + + +bool IsSpawnPointValid( CBaseEntity *pPlayer, CBaseEntity *pSpot ); +void respawn( CBaseEntity *pEdict, bool fCopyCorpse ); +int TrainSpeed(int iSpeed, int iMax); +void BulletWizz( Vector vecSrc, Vector vecEndPos, edict_t *pShooter, bool isTracer ); + +extern float g_flNextReinforcementTime; +extern short g_sModelIndexFireball; +extern CBaseEntity *g_pLastSpawn; + +//----------------------------------------------------------------------------- +// Purpose: Don't do anything for now +// Input : *pFormat - +// ... - +// Output : static void +//----------------------------------------------------------------------------- +void StatusPrintf( bool clear, int destination, char *pFormat, ... ) +{ + return; + + /* + va_list marker; + char msg[8192]; + + va_start(marker, pFormat); + Q_vsnprintf(msg, sizeof( msg ), pFormat, marker); + va_end(marker); + + Msg( msg ); + */ +} + +#pragma warning( disable : 4355 ) + +//===================================================================== +// PLAYER HANDLING +//===================================================================== +CBaseTFPlayer::CBaseTFPlayer() : + m_PlayerClasses( this ), m_PlayerAnimState( this ) +{ + // HACK because player's have pev set in baseclass constructor + // which triggers an assert that we want to keep. + { + edict_t *savepev = edict(); + NetworkProp()->SetEdict( NULL ); + UseClientSideAnimation(); + NetworkProp()->SetEdict( savepev ); + } + + m_bWasMoving = false; + + m_iLastSecondsToGo = -1; + m_TFLocal.m_nInTacticalView = 0; + m_TFLocal.m_pPlayer = this; + m_bSwitchingView = false; + ClearActiveWeapon(); + + m_iPlayerClass = TFCLASS_UNDECIDED; + SetPlayerClass( TFCLASS_UNDECIDED ); + m_pCurrentMenu = NULL; + m_TFPlayerFlags = 0; + m_bDeploying = false; + m_bDeployed = false; + m_bUnDeploying = false; + m_flFinishedDeploying = 0; + SetOrder( NULL ); + + m_nPreferredTechnology = -1; + m_nMedicDamageBoosts = 0; + + m_hSpawnPoint = NULL; + m_flLastTimeDamagedByEnemy = -1000; + + int i; + for ( i = 0; i < MOMENTUM_MAXSIZE; i++ ) + { + m_aMomentum[ i ] = 1.0f; + } +} + +void CBaseTFPlayer::UpdateOnRemove( void ) +{ + if ( m_hSelectedOrder ) + { + GetTFTeam()->RemoveOrdersToPlayer( this ); + Assert( !m_hSelectedOrder.Get() ); + } + + ClearPlayerClass(); + + ClearClientRagdoll( false ); + + // Chain at end to mimic destructor unwind order + BaseClass::UpdateOnRemove(); +} + +CBaseTFPlayer::~CBaseTFPlayer() +{ + SetPlayerClass( (TFClass)-1 ); +} + +bool CBaseTFPlayer::IsHidden() const +{ + return (m_TFPlayerFlags & TF_PLAYER_HIDDEN) != 0; +} + +void CBaseTFPlayer::SetHidden( bool bHidden ) +{ + if ( bHidden ) + m_TFPlayerFlags |= TF_PLAYER_HIDDEN; + else + m_TFPlayerFlags &= ~TF_PLAYER_HIDDEN; +} + +//----------------------------------------------------------------------------- +// Purpose: Called everytime the player's respawned +//----------------------------------------------------------------------------- +void CBaseTFPlayer::Spawn( void ) +{ + m_bUnderAttack = false; + m_pCurrentZone = NULL; + ClearClientRagdoll( false ); + + g_pNotify->ReportNamedEvent( this, "PlayerSpawned" ); + + DeactivateMovementConstraint(); + + if ( IsInAVehicle() ) + { + LeaveVehicle(); + } + + // If the player doesn't have a spawn station set, find one + if ( m_hSpawnPoint == NULL || !InSameTeam( m_hSpawnPoint ) ) + { + m_hSpawnPoint = GetInitialSpawnPoint(); + } + + if ( inv_demo.GetBool() ) + { + if ( !GetPlayerClass() ) + { + ChangeClass( TFCLASS_MEDIC ); + m_Local.m_iHideHUD |= HIDEHUD_MISCSTATUS; + engine->ServerCommand("r_DispEnableLOD 0\n"); + } + } + + // Must be done before baseclass spawn, so it's correct for when we find a spawnpoint + if ( GetPlayerClass() ) + { + GetPlayerClass()->SetPlayerHull(); + } + + // Use human commando model until we know our class + SetModel( "models/player/human_commando.mdl" ); + + BaseClass::Spawn(); + + m_flFractionalBoost = 0.0f; + + // Create second view model ( for support/commando, etc ) + CreateViewModel( 1 ); + + // Tell the PlayerClass that this player's just respawned + if ( GetPlayerClass() ) + { + RemoveFlag( FL_NOTARGET ); + RemoveSolidFlags( FSOLID_NOT_SOLID ); + + GetPlayerClass()->RespawnClass(); + if ( GetActiveWeapon() ) + { + // Holster weapon immediately, to allow it to cleanup +// GetActiveWeapon()->Holster( ); // NJS: test + + if (GetActiveWeapon()->HasAnyAmmo()) + { + Weapon_Switch( GetActiveWeapon() ); + } + else + { + SwitchToNextBestWeapon( GetActiveWeapon() ); + } + } + else + { + SwitchToNextBestWeapon( NULL ); + } + + SetPlayerModel(); + + // Make sure they're not deployed + FinishUnDeploying(); + + // Remove my personal orders + if ( GetTFTeam() ) + { + GetTFTeam()->RemoveOrdersToPlayer( this ); + } + + RemoveAllDecals(); + } + else + { + // No class? can't target this dude + AddFlag( FL_NOTARGET ); + + // Remove everything + RemoveAllItems( false ); + + // Set/unset m_bHidden instead to hide the tf player + SetHidden( true ); + + AddSolidFlags( FSOLID_NOT_SOLID ); + SetMoveType( MOVETYPE_NONE ); + + SetModel( "models/player/human_commando.mdl" ); + + // If they're not in a team, bring up the Team Menu + if ( !IsInAnyTeam() ) + { + if ( tf_autoteam.GetFloat() ) + { + // Autoteam the player + PlacePlayerInTeam(); + ForceRespawn(); + } + else + { + // Let players choose their team + m_pCurrentMenu = gMenus[MENU_TEAM]; + } + } + else // Bring up the Class Menu + { + m_pCurrentMenu = gMenus[MENU_CLASS]; + } + + m_MenuRefreshTime = gpGlobals->curtime; + + m_nPreferredTechnology = -1; + } + + SetCantMove( false ); + + + m_TFLocal.m_nInTacticalView = 0; + m_flLastTimeDamagedByEnemy = -1000; + + // Purge resource chunks + for ( int i=0; i < m_TFLocal.m_iResourceAmmo.Count(); i++ ) + m_TFLocal.m_iResourceAmmo.Set( i, 0 ); + + ResetKnockdown(); + SetGagged( false ); + SetUsingThermalVision( false ); + ClearCamouflage(); + SetIDEnt( NULL ); + m_iPowerups = 0; + + // MUST set the right player hull before placing the player somewhere. + if ( GetPlayerClass() ) + GetPlayerClass()->SetPlayerHull(); + + g_pGameRules->GetPlayerSpawnSpot( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::CleanupOnActStart( void ) +{ + // Tell all our weapons + for ( int i = 0; i < WeaponCount(); i++ ) + { + if ( GetWeapon(i) ) + { + ((CBaseTFCombatWeapon*)GetWeapon(i))->CleanupOnActStart(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::RecalculateSpeed( void ) +{ + if ( GetPlayerClass() ) + { + GetPlayerClass()->SetMaxSpeed( GetPlayerClass()->GetMaxSpeed() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: I just killed another player +//----------------------------------------------------------------------------- +void CBaseTFPlayer::KilledPlayer( CBaseTFPlayer *pVictim ) +{ + TFStats()->IncrementPlayerStat( this, TF_PLAYER_STAT_KILL_COUNT, 1 ); + TFStats()->IncrementTeamStat( GetTeamNumber(), TF_TEAM_STAT_KILL_COUNT, 1 ); + + // Am I in a rampage? + if ( HasPowerup( POWERUP_RUSH ) && IsInRampage() ) + { + // Extend my rush + AttemptToPowerup( POWERUP_RUSH, ADRENALIN_RAMPAGE_EXTEND ); + + // Let 'em know + EmitSound( "BaseTFPlayer.BloodSportKiller" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called only the first time a player's placed in the map +//----------------------------------------------------------------------------- +void CBaseTFPlayer::InitialSpawn( void ) +{ + BaseClass::InitialSpawn(); + SetWeaponBuilder( NULL ); + + m_bFirstTeamSpawn = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::Precache( void ) +{ + //!! hack for radar + BaseClass::Precache(); + + PrecacheScriptSound( "BaseTFPlayer.BloodSportKiller" ); + PrecacheScriptSound( "Humans.Death" ); + PrecacheScriptSound( "AlienCommando.Death" ); + PrecacheScriptSound( "AlienMedic.Death" ); + PrecacheScriptSound( "AlienDefender.Death" ); + PrecacheScriptSound( "AlienEscort.Death" ); + PrecacheScriptSound( "BaseTFPlayer.StartDeploying" ); + PrecacheScriptSound( "BaseTFPlayer.StartUnDeploying" ); + PrecacheScriptSound( "BaseTFPlayer.KnockedDown" ); + PrecacheScriptSound( "BaseTFPlayer.ThermalOn" ); + PrecacheScriptSound( "BaseTFPlayer.ThermalOff" ); + PrecacheScriptSound( "BaseTFPlayer.PickupResources" ); + PrecacheScriptSound( "BaseTFPlayer.DonateResources" ); + + // Class specific sounds + PrecacheScriptSound( "Commando.BootHit" ); + PrecacheScriptSound( "Commando.BootSwing" ); + PrecacheScriptSound( "Commando.BullRushScream" ); + PrecacheScriptSound( "Commando.BullRushFlesh" ); + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::UpdateClientData( void ) +{ + CTeam *pTeam = GetTeam(); + if ( pTeam ) + pTeam->UpdateClientData( this ); + + BaseClass::UpdateClientData(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::ForceClientDllUpdate( void ) +{ + BaseClass::ForceClientDllUpdate(); + + // Force any active menu to be reset + m_MenuRefreshTime = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Forces an immediate respawn of the player +//----------------------------------------------------------------------------- +void CBaseTFPlayer::ForceRespawn( void ) +{ + Spawn(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Input handler that forces a respawn of the player. +//----------------------------------------------------------------------------- +void CBaseTFPlayer::InputRespawn( inputdata_t &inputdata ) +{ + ForceRespawn(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::InitHUD( void ) +{ + CSingleUserRecipientFilter user( this ); + user.MakeReliable(); + + // If we're in an act, tell it to update the client + if ( g_hCurrentAct ) + { + g_hCurrentAct->UpdateClient( this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Player has just tried to switch to a new weapon +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SelectItem( const char *pstr, int iSubType ) +{ + // can't change weapon while deployed + if ( IsPlayerLockedInPlace() || IsDeployed() || IsDeploying() ) + return; + + // Pass through to CBaseCombatWeapon code + BaseClass::SelectItem( pstr, iSubType ); +} + +//----------------------------------------------------------------------------- +// Purpose: Put the player in the specified team +//----------------------------------------------------------------------------- +void CBaseTFPlayer::ChangeTeam( int iTeamNum ) +{ + // If we're changing team, clear my order + if ( iTeamNum != GetTeamNumber() ) + { + SetOrder(NULL); + if ( tf_destroyobjects.GetFloat() ) + { + RemoveAllObjects( false ); + } + } + + // Force full tech tree update + for ( int i = 0 ; i < MAX_TECHNOLOGIES; i++ ) + { + m_rgClientTechAvail[ i ].m_nAvailable = -1; + } + + BaseClass::ChangeTeam( iTeamNum ); + + // Now handle resources: + // - If it's the first spawn ever, give the player the team's currently calculated resource amount + // - If the player has more resources than the team's joining amount, drop his resources to that amount. Otherwise, he can keep his current. + if ( GetGlobalTFTeam( iTeamNum ) ) + { + float flJoiningResources = GetGlobalTFTeam( iTeamNum )->GetJoiningPlayerResources(); + if ( m_bFirstTeamSpawn ) + { + m_bFirstTeamSpawn = false; + SetBankResources( flJoiningResources ); + } + else + { + if ( flJoiningResources < GetBankResources() ) + { + SetBankResources( flJoiningResources ); + } + } + } + + // Clear the client ragdoll, when changing teams. + ClearClientRagdoll( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: Automatically place the player in the most appropriate team +//----------------------------------------------------------------------------- +void CBaseTFPlayer::PlacePlayerInTeam( void ) +{ + CTFTeam *pTargetTeam = NULL; + + // Find the team with the least players in it + for ( int i = 0; i < MAX_TF_TEAMS; i++ ) + { + CTFTeam *pTeam = GetGlobalTFTeam(i); + + if ( pTargetTeam ) + { + if ( pTeam->GetNumPlayers() < pTargetTeam->GetNumPlayers() ) + pTargetTeam = pTeam; + } + else + { + pTargetTeam = pTeam; + } + } + + ChangeTeam( pTargetTeam->GetTeamNumber() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if the specified class is available to this player +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::IsClassAvailable( TFClass iClass ) +{ + char str[128]; + Q_snprintf( str, sizeof( str ), "class_%s", GetTFClassInfo( iClass )->m_pClassName ); + return HasNamedTechnology( str ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::ChangeClass( TFClass iClass ) +{ + // If they've got a playerclass, kill it + if ( GetPlayerClass() ) + { + if ( tf_destroyobjects.GetFloat() ) + { + RemoveAllObjects( false, iClass ); + } + + ClearPlayerClass(); + } + + // can't change class if we have no team + if ( !IsInAnyTeam() ) + return; + + // Make sure client .dll can find out about it. + SetPlayerClass( iClass ); + + // Clear out current vote.... + CTFTeam *pTFTeam = GetTFTeam(); + SetPreferredTechnology( pTFTeam->m_pTechnologyTree, -1 ); + + // Force a respawn if they're alive + if ( IsAlive() ) + { + ForceRespawn(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Reset player class +//----------------------------------------------------------------------------- +void CBaseTFPlayer::ClearPlayerClass( void ) +{ + // Remove all weapons & items + if ( GetPlayerClass() ) + { + RemoveAllItems( false ); + m_hWeaponCombatShield = NULL; + } + + m_iPowerups = 0; + SetPlayerClass( TFCLASS_UNDECIDED ); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the player's model to the correct one, taking into account +// class, gender, team, and disguise. +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetPlayerModel( void ) +{ + if (!GetPlayerClass()) + { + SetHidden( true ); + return; + } + + string_t sModel = GetPlayerClass()->GetClassModel( GetTeamNumber() ); + + // If they don't have a model, make the player invisible + if ( !sModel ) + { + SetHidden( true ); + return; + } + + // Make the player visible + SetHidden( false ); + + // Set the model + SetModel( STRING( sModel ) ); + + if ( GetFlags() & FL_DUCKING ) + UTIL_SetSize(this, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); + else + UTIL_SetSize(this, VEC_HULL_MIN, VEC_HULL_MAX); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::PlayerRespawn( void ) +{ + m_nButtons = 0; + m_iRespawnFrames = 0; + + // don't copy a corpse if we're in deathcam. + respawn( this, !IsObserver() ); + SetThink( NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Play a sound when we die +//----------------------------------------------------------------------------- +void CBaseTFPlayer::DeathSound( const CTakeDamageInfo &info ) +{ + if ( GetTeamNumber() == TEAM_HUMANS ) + { + EmitSound( "Humans.Death" ); + } + else if ( GetTeamNumber() == TEAM_ALIENS ) + { + switch( PlayerClass() ) + { + case TFCLASS_COMMANDO: + EmitSound( "AlienCommando.Death" ); + break; + + case TFCLASS_MEDIC: + EmitSound( "AlienMedic.Death" ); + break; + + case TFCLASS_DEFENDER: + EmitSound( "AlienDefender.Death" ); + break; + + case TFCLASS_ESCORT: + EmitSound( "AlienEscort.Death" ); + break; + + default: + break; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::ItemPostFrame() +{ + // Don't process items while in a vehicle. + if ( IsInAVehicle() ) + { + IServerVehicle *pVehicle = GetVehicle(); + Assert( pVehicle ); + + // NOTE: We *have* to do this before ItemPostFrame because ItemPostFrame + // may dump us out of the vehicle + int nRole = pVehicle->GetPassengerRole( this ); + bool bUsingStandardWeapons = pVehicle->IsPassengerUsingStandardWeapons( nRole ); + + pVehicle->ItemPostFrame( this ); + + // Fall through and check weapons, etc. if we're using them + if (!bUsingStandardWeapons || !IsInAVehicle()) + return; + } + + // If we're attaching a sapper, handle player use only + if ( m_TFLocal.m_bAttachingSapper ) + { + PlayerUse(); + return; + } + + BaseClass::ItemPostFrame(); + + if ( GetPlayerClass() ) + { + GetPlayerClass()->ItemPostFrame(); // Let the player class handle it. + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::Jump( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::PreThink(void) +{ + CheckBuffs(); + + // Riding a vehicle? + if ( IsInAVehicle() ) + { + BaseClass::PreThink(); + return; + } + + CheckDeployFinish(); + CheckKnockdown(); + CheckCamouflage(); + CheckSapperAttaching(); + + // Update reinforcement state + if (m_lifeState >= LIFE_DYING) + { + // After 3 seconds, move them to the Tactical Map + if ( (gpGlobals->curtime - m_flTimeOfDeath) > 3.0 ) + { + if ( m_TFLocal.m_nInTacticalView == false ) + { + ShowTacticalView( 1 ); + } + } + + // ROBIN: Maps will define whether or not teams reinforce + /* + // Aliens respawn in waves + if ( GetTeamNumber() == TEAM_ALIENS ) + { + int iSecondsToGo = (int)(g_flNextReinforcementTime - gpGlobals->curtime); + if ( iSecondsToGo != m_iLastSecondsToGo && iSecondsToGo >= 1 ) + { + m_iLastSecondsToGo = iSecondsToGo; + ClientPrint( this, HUD_PRINTCENTER, UTIL_VarArgs("\nReinforcing in %d %s\n", iSecondsToGo, iSecondsToGo > 1 ? "seconds" : "second" ) ); + } + } + */ + + TFPlayerDeathThink(); + } + + // Update zone state + if ( m_pCurrentZone ) + { + m_iCurrentZoneState = m_pCurrentZone->GetControllingTeam(); + if ( m_iCurrentZoneState != ZONE_CONTESTED ) + { + // Set the Zone state to the correct one + if ( m_iCurrentZoneState == GetTeamNumber() ) + m_iCurrentZoneState = ZONE_FRIENDLY; + else + m_iCurrentZoneState = ZONE_ENEMY; + } + } + else + { + m_iCurrentZoneState = 0; + } + + BaseClass::PreThink(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::PostThink() +{ + BaseClass::PostThink(); + + // Make sure we have a valid MCV id. + CVehicleTeleportStation *pMCV = GetSelectedMCV(); + if ( !pMCV || !pMCV->IsDeployed() ) + { + m_hSelectedMCV = CVehicleTeleportStation::GetFirstDeployedMCV( GetTeamNumber() ); + } + + // Tell the client if our damage is boosted so it can do a smurfy effect on the weapon. + if ( GetAttackDamageScale( NULL ) == 1 ) + m_TFPlayerFlags &= ~TF_PLAYER_DAMAGE_BOOST; + else + m_TFPlayerFlags |= TF_PLAYER_DAMAGE_BOOST; + + m_PlayerAnimState.Update(); +// SetLocalAngles( m_PlayerAnimState.GetRenderAngles() ); + + float flTimeSinceAttacked = gpGlobals->curtime - LastTimeDamagedByEnemy(); + m_bUnderAttack = ((flTimeSinceAttacked >= 0.0f) && (flTimeSinceAttacked < 1.0f)); + + // TODO: This collision hull is set in the base class PostThink (so this is + // redundant), but I don't wanna re-write the whole thing at this point. + // We will just have to deal with a little redundancy for now. + if ( GetPlayerClass() ) + { + GetPlayerClass()->SetPlayerHull(); + } + + // Menus + MenuDisplay(); + + // Player class Think + if (GetPlayerClass()) + { + GetPlayerClass()->ClassThink(); + } + + if ( m_bSwitchingView ) + { + m_bSwitchingView = false; + SetMoveType( m_TFLocal.m_nInTacticalView ? MOVETYPE_ISOMETRIC : MOVETYPE_WALK ); + } + + FollowClientRagdoll(); +} + +//----------------------------------------------------------------------------- +// Purpose: selects a valid point that the player can spawn at +// Output : edict_t - the point in the world to spawn at +//----------------------------------------------------------------------------- +CBaseEntity *CBaseTFPlayer::EntSelectSpawnPoint( void ) +{ + // If we're in a team, ask the team for a spawnpoint + if ( GetTeam() ) + { + CBaseEntity *entity = NULL; + if ( GetPlayerClass() ) + { + // Let individual player classes override the respawn point + entity = GetPlayerClass()->SelectSpawnPoint(); + if ( entity ) + { + return entity; + } + + // Do we have a selected spawn point (from a respawn station)? + entity = m_hSpawnPoint; + if (entity && (entity->GetTeam() == GetTeam())) + { + PlayRespawnEffect( entity ); + return entity; + } + } + + entity = GetTeam()->SpawnPlayer( this ); + if ( entity ) + return entity; + } + + // If we're not in a team, or the team didn't have a spawnpoint for us, + // fall back to the basic spawnpoint code. + return BaseClass::EntSelectSpawnPoint(); +} + +void CBaseTFPlayer::RemoveShieldOverlays( void ) +{ + RemoveGesture( ACT_OVERLAY_SHIELD_UP ); + RemoveGesture( ACT_OVERLAY_SHIELD_DOWN ); + RemoveGesture( ACT_OVERLAY_SHIELD_UP_IDLE ); + RemoveGesture( ACT_OVERLAY_SHIELD_ATTACK ); + RemoveGesture( ACT_OVERLAY_SHIELD_KNOCKBACK ); +} + +static bool IsShieldOverlay( Activity activity ) +{ + switch ( activity ) + { + default: + return false; + case ACT_OVERLAY_SHIELD_UP: + case ACT_OVERLAY_SHIELD_DOWN: + case ACT_OVERLAY_SHIELD_UP_IDLE: + case ACT_OVERLAY_SHIELD_ATTACK: + case ACT_OVERLAY_SHIELD_KNOCKBACK: + return true; + } + return false; +} + +int CBaseTFPlayer::RemoveShieldOverlaysExcept( Activity activity, bool addifnotpresent /*= true */ ) +{ + int skip = FindGestureLayer( activity ); + + int i; + for ( i = 0; i < CBaseAnimatingOverlay::MAX_OVERLAYS; i++ ) + { + if ( i == skip ) + continue; + + if ( IsShieldOverlay( GetLayerActivity( i ) ) ) + { + RemoveLayer( i, 0.0, 0.0f ); + } + } + + // Add it in if it's not present already + if ( addifnotpresent && ( skip == -1 ) ) + { + return AddGesture( activity ); + } + else + { + return skip; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : activity - i/o : can be changed to a new activity +// overlayindex - o: if an overlay is picked, this gets changed +// Rest of paramters are input only +// moving - is player moving +// ducked - is player ducking +// overlay - animation choices for this state (either full body crouch/stand, or overlay on top of base crouch/stand ) +// crouch - +// normal - +// Overlay parameters +// autokill - if false, overlay will loop indefinitely +// blendin - amount of time over which to blend in (0.0f for snap) +// blendout - same but for blending out instead +//----------------------------------------------------------------------------- +void CBaseTFPlayer::PickShieldAnimation( Activity& activity, int& overlayindex, bool moving, bool ducked, + Activity overlay, Activity crouch, Activity normal, + bool autokill /*=true*/, float blendin /*=0.0f*/, float blendout /*=0.0f*/ ) +{ + if ( moving ) + { + overlayindex = RemoveShieldOverlaysExcept( overlay ); + if ( overlayindex != -1 ) + { + if ( blendin > 0.0f ) + { + SetLayerBlendIn( overlayindex, blendin ); + } + + if ( blendout > 0.0f ) + { + SetLayerBlendOut( overlayindex, blendout ); + } + + if ( !autokill ) + { + SetLayerAutokill( overlayindex, false ); + } + } + } + else + { + activity = ducked ? crouch : normal; + } +} + +Activity CBaseTFPlayer::ShieldTranslateActivity( Activity activity ) +{ + CWeaponTwoHandedContainer *container = dynamic_cast< CWeaponTwoHandedContainer * >( GetActiveWeapon() ); + if ( !container ) + return activity; + + CWeaponCombatShield *pShield = dynamic_cast< CWeaponCombatShield * >( container->GetLeftWeapon() ); + if ( !pShield ) + { + pShield = dynamic_cast< CWeaponCombatShield * >( container->GetRightWeapon() ); + if ( !pShield ) + { + return activity; + } + } + + float speed = GetAbsVelocity().Length2D(); + bool isMoving = speed != 0 ? true : false; + //bool isRunning = speed > 75 ? true : false; + bool isDucked = ( GetFlags() & FL_DUCKING ) ? true : false; + + int shieldState = pShield->GetShieldState(); + + float startframe = 0.0f; + + bool movechanged = isMoving ^ m_bWasMoving; + if ( movechanged) + { + // Grab frame from overlay + if ( !isMoving ) + { + for ( int i = 0; i < MAX_OVERLAYS; i++ ) + { + if ( IsShieldOverlay( GetLayerActivity( i ) ) ) + { + startframe = GetLayerCycle( i ); + } + } + + RemoveShieldOverlays(); + } + else + { + switch ( GetActivity() ) + { + case ACT_SHIELD_UP: + case ACT_SHIELD_DOWN: + case ACT_SHIELD_UP_IDLE: + case ACT_SHIELD_ATTACK: + //case ACT_SHIELD_KNOCKBACK: + case ACT_CROUCHING_SHIELD_UP: + case ACT_CROUCHING_SHIELD_DOWN: + case ACT_CROUCHING_SHIELD_UP_IDLE: + case ACT_CROUCHING_SHIELD_ATTACK: + //case ACT_CROUCHING_SHIELD_KNOCKBACK: + startframe = GetCycle(); + break; + default: + break; + } + } + } + + // Asume we should fix up animation based on move/stationary state change + bool fixup = true; + // Assume no overlay + int idx = -1; + + switch ( shieldState ) + { + default: + case SS_DOWN: + case SS_UNAVAILABLE: + RemoveShieldOverlays(); + // By default, remove shield overlays and don't do fixup + fixup = false; + break; + case SS_LOWERING: + { + PickShieldAnimation( activity, idx, isMoving, isDucked, + ACT_OVERLAY_SHIELD_DOWN, ACT_CROUCHING_SHIELD_DOWN, ACT_SHIELD_DOWN, + true, 0.0f, 0.2f ); + } + break; + case SS_RAISING: + { + PickShieldAnimation( activity, idx, isMoving, isDucked, + ACT_OVERLAY_SHIELD_UP, ACT_CROUCHING_SHIELD_UP, ACT_SHIELD_UP, + true, 0.2f, 0.0f ); + } + break; + case SS_UP: + { + PickShieldAnimation( activity, idx, isMoving, isDucked, + ACT_OVERLAY_SHIELD_UP_IDLE, ACT_CROUCHING_SHIELD_UP_IDLE, ACT_SHIELD_UP_IDLE, + false ); + } + break; + case SS_PARRYING: + { + PickShieldAnimation( activity, idx, isMoving, isDucked, + ACT_OVERLAY_SHIELD_ATTACK, ACT_CROUCHING_SHIELD_ATTACK, ACT_SHIELD_ATTACK, + true, 0.1f, 0.1f ); + } + break; + } + + // If started or stopped moving and still using shield, match the cycle to/from the overlay/base animation + // being used beforehand + if ( movechanged && fixup ) + { + // Fixup overlay frame + if ( idx != -1 ) + { + SetLayerCycle( idx, startframe ); + } + else + { + // Force animation blend + ResetSequenceInfo(); + + // Match start frame + SetCycle( startframe ); + } + } + + // Remember previous state + m_bWasMoving = isMoving; + + // Return translated activity + return activity; +} + +void CBaseTFPlayer::StoreCycle( void ) +{ + m_flStoredCycle = GetCycle(); // !!!!! +} + +float CBaseTFPlayer::RetrieveCycle( void ) +{ + return m_flStoredCycle; +} + +//----------------------------------------------------------------------------- +// Purpose: Certain activities have matched cycles +// Input : newActivity - +// currentActivity - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::ShouldMatchCycles( Activity newActivity, Activity currentActivity ) +{ + if ( ( newActivity == ACT_WALK || newActivity == ACT_RUN ) && + ( currentActivity == ACT_WALK || currentActivity == ACT_RUN ) ) + { + // Don't blend either + IncrementInterpolationFrame(); + return true; + } + return false; +} + +#define ARBITRARY_RUN_SPEED 75.0f + +//----------------------------------------------------------------------------- +// Purpose: Set the activity based on an event or current state +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetAnimation( PLAYER_ANIM playerAnim ) +{ + // Assume no change + Activity idealActivity = GetActivity(); + int animDesired = GetSequence(); + + float speed = GetAbsVelocity().Length2D(); + + bool isMoving = ( speed != 0.0f ) ? true : false; + bool isRunning = false; + + if ( GetPlayerClass() ) + { +// FIXME: TF2 makes no distinction between walking and running for now, +// use the run animation always + if ( speed > 10.0f ) + { + isRunning = true; + } + } + else + { + if ( speed > ARBITRARY_RUN_SPEED ) + { + isRunning = true; + } + } + + bool isDucked = ( GetFlags() & FL_DUCKING ) ? true : false; + bool isStillJumping = !( GetFlags() & FL_ONGROUND ) && ( GetActivity() == ACT_HOP ); + + StoreCycle(); + + // Decide upon an animation activity based upon the desired Player animation + switch ( playerAnim ) + { + default: + case PLAYER_RELOAD: + case PLAYER_ATTACK1: + case PLAYER_IDLE: + case PLAYER_WALK: + // Are we still jumping? + // If so, keep playing the jump animation. + if ( !isStillJumping ) + { + idealActivity = ACT_WALK; + + if ( isDucked ) + { + idealActivity = !isMoving ? ACT_CROUCHIDLE : ACT_CROUCH; + } + else + { + + if ( isRunning ) + { + idealActivity = ACT_RUN; + } + else + { + idealActivity = isMoving ? ACT_WALK : ACT_IDLE; + } + } + + // Allow shield to override + idealActivity = ShieldTranslateActivity( idealActivity ); + // Allow body yaw to override for standing and turning in place + idealActivity = m_PlayerAnimState.BodyYawTranslateActivity( idealActivity ); + } + break; + + case PLAYER_IN_VEHICLE: + // For now, use manned gun pose for all vehicles + idealActivity = ACT_RIDE_MANNED_GUN; + break; + + case PLAYER_JUMP: + idealActivity = ACT_HOP; + break; + + case PLAYER_DIE: + // Uses Ragdoll now??? + idealActivity = ACT_DIESIMPLE; + break; + + // FIXME: Use overlays for reload, start/leave aiming, attacking + case PLAYER_START_AIMING: + case PLAYER_LEAVE_AIMING: + idealActivity = ACT_WALK; + break; + } + + // No change requested? + if ( ( GetActivity() == idealActivity ) && ( GetSequence() != -1 ) ) + return; + + bool useStoredCycle = ShouldMatchCycles( idealActivity, GetActivity() ); + + animDesired = SelectWeightedSequence( idealActivity ); + + SetActivity( idealActivity ); + + // Already using the desired animation? + if ( GetSequence() == animDesired ) + return; + + ResetSequence( animDesired ); + + // Reset to first frame of desired animation or match previous animation if activities are + // meant to synchronize + SetCycle( useStoredCycle ? RetrieveCycle() : 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::CheatImpulseCommands( int iImpulse ) +{ + switch(iImpulse) + { + case 101: + if ( GetPlayerClass() ) + { + GetPlayerClass()->ResupplyAmmo( 1.0f, RESUPPLY_ALL_FROM_STATION ); + } + break; + + case 150: + if ( GetTFTeam() ) + GetTFTeam()->PostMessage( TEAMMSG_REINFORCEMENTS_ARRIVED ); + break; + + default: + BaseClass::CheatImpulseCommands(iImpulse); + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetRespawnStation( CBaseEntity* pRespawnStation ) +{ + // This can happen because the object may get killed and its index reused + // between time the message was sent and the + if( !pRespawnStation || !FClassnameIs( pRespawnStation, "obj_respawn_station" ) ) + return; + + // Team could have changed (stolen object) + if ( GetTeam() != pRespawnStation->GetTeam() ) + return; + + // If the respawn station is the same one, then unselect! + if ( pRespawnStation != m_hSpawnPoint ) + { + // Make sure the respawn station is a respawn station; it could be some + m_hSpawnPoint = pRespawnStation; + } + else + { + m_hSpawnPoint = 0; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Find a starting respawn station +//----------------------------------------------------------------------------- +CBaseEntity *CBaseTFPlayer::GetInitialSpawnPoint( void ) +{ + if ( !GetTFTeam() ) + return NULL; + + CBaseEntity *pFirstStation = NULL; + + // Cycle through all the respawn stations on my team + for ( int i = 0; i < GetTFTeam()->GetNumObjects(); i++ ) + { + CBaseObject *pObject = GetTFTeam()->GetObject(i); + if ( pObject->GetType() == OBJ_RESPAWN_STATION ) + { + // Store off the first station we find + if ( !pFirstStation ) + { + pFirstStation = pObject; + } + + // Map specified initial spawnpoint? + if ( ((CObjectRespawnStation*)pObject)->IsInitialSpawnPoint() ) + return pObject; + } + } + + return pFirstStation; +} + + + +CBaseEntity *FindEntityForward( CBasePlayer *pMe, bool fHull ); +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::ClientCommand( const CCommand &args ) +{ + if( HasClass() ) + { + if ( GetPlayerClass()->ClientCommand( args ) ) + return true; + + const char *cmd = args[0]; + if ( FStrEq( cmd, "emp" ) ) + { + Msg( "Self-inflicted EMP: testing\n" ); + float flTime = 10; + if ( args.ArgC() == 2 ) + { + flTime = atof( args[ 1 ] ); + } + + AttemptToPowerup( POWERUP_EMP, flTime ); + return true; + } + + if ( FStrEq( cmd, "emp_target" ) ) + { + CBaseEntity *pEntity = FindEntityForward( this, true ); + if ( pEntity && pEntity->CanBePoweredUp() ) + { + float flTime = 10; + if ( args.ArgC() == 2 ) + { + flTime = atof( args[ 1 ] ); + } + pEntity->AttemptToPowerup( POWERUP_EMP, flTime ); + } + return true; + } + + if ( FStrEq( cmd, "dmg_target" ) ) + { + CBaseEntity *pEntity = FindEntityForward( this, true ); + if ( pEntity && pEntity->m_takedamage ) + { + float flDamage = 1; + if ( args.ArgC() == 2 ) + { + flDamage = atof( args[ 1 ] ); + } + CBaseEntity *world = CBaseEntity::Instance( engine->PEntityOfEntIndex( 0 ) ); + if ( world ) + { + pEntity->OnTakeDamage( CTakeDamageInfo( world, world, flDamage, DMG_GENERIC ) ); + } + } + return true; + } + } + + if ( !stricmp( cmd, "kd" ) ) + { + Vector force( 0, 0, 0 ); + if ( args.ArgC() == 1 ) + { + force.x = random->RandomFloat( 0.5, 1.0 ); + force.y = random->RandomFloat( 0.5, 1.0 ); + + if ( random->RandomFloat( 0, 1 ) > 0.5 ) + { + force.x *= -1.0f; + } + if ( random->RandomFloat( 0, 1 ) > 0.5 ) + { + force.y *= -1.0f; + } + force.z = random->RandomFloat( 0.5, 1.0 ); + } + else + { + Vector fwd; + Vector right; + AngleVectors( GetAbsAngles(), &fwd, &right, NULL ); + + if ( !stricmp( args[ 1 ], "f" ) ) + { + force = fwd * -1.0f; + } + else if ( !stricmp( args[ 1 ], "b" ) ) + { + force = fwd; + } + else if ( !stricmp( args[ 1 ], "r" ) ) + { + force = right * -1.0f; + } + else if ( !stricmp( args[ 1 ], "l" ) ) + { + force = right; + } + else if ( !stricmp( args[ 1 ], "fr" ) ) + { + force = fwd * -1.0f; + force += right * -1.0f; + } + else if ( !stricmp( args[ 1 ], "br" ) ) + { + force = fwd; + force += right * -1.0f; + } + else if ( !stricmp( args[ 1 ], "fl" ) ) + { + force = fwd * -1.0f; + force += right; + } + else if ( !stricmp( args[ 1 ], "bl" ) ) + { + force = fwd; + force += right; + } + + force.z = 0.8f; + VectorNormalize( force ); + } + + KnockDownPlayer( force, 500.0f, 3.0f ); + return true; + } + + if ( FStrEq( cmd, "veryweak" ) ) + { + int ouch = m_iHealth - 1; + + CBaseEntity *world = CBaseEntity::Instance( engine->PEntityOfEntIndex( 0 ) ); + if ( world ) + { + OnTakeDamage( CTakeDamageInfo( world, world, (float)ouch, DMG_GENERIC ) ); + } + + return true; + } + + if ( FStrEq( cmd, "ragdoll" ) ) + { + bool on = true; + + if ( args.ArgC() >= 2 ) + { + on = atoi( args[ 1 ] ) ? true : false; + } + + if ( on ) + { + Vector force = RandomVector( -500, 500 ); + force.z = fabs( force.z ); + force.z = MIN( 200.0f, force.z ); + + BecomeRagdollOnClient( force ); + } + else + { + ClearClientRagdoll( true ); + } + return true; + } + + if ( FStrEq( cmd, "hbset" ) ) + { + if ( args.ArgC() >= 2 ) + { + SetHitboxSet( atoi( args[ 1 ] ) ); + Msg( "Hitboxset forced to %i %s\n", GetHitboxSet(), GetHitboxSetName() ); + } + + return true; + } + + return BaseClass::ClientCommand( args ); +} + + +//========================================================= +// Purpose: Override base TraceAttack +//========================================================= +void CBaseTFPlayer::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr ) +{ + if ( m_takedamage ) + { + // Prevent team damage here so blood doesn't appear + if ( info.GetAttacker() ) + { + // Take damage from myself + if ( InSameTeam( info.GetAttacker() ) && info.GetAttacker() != this ) + return; + } + + // If we hit our shield, ignore the damage + float flDamage = info.GetDamage(); + if ( IsHittingShield( vecDir, &flDamage )) + return; + + // Shield may have blocked some + CTakeDamageInfo subInfo = info; + subInfo.SetDamage( flDamage ); + + SetLastHitGroup( ptr->hitgroup ); + + // Hit groups aren't evaluated here, like base TraceAttack. + // Weapons factor hit location into flDamage before it gets here +/* //SpawnBlood( ptr->endpos - (vecDir * 5), BloodColor(), subInfo.GetDamage() ); + //TraceBleed( subInfo.GetDamage(), vecDir, ptr, subInfo.GetDamageType() ); + + // Show the personal shield effect. + // What we do here is collide the trace line with an ellipse that is slightly larger + // than the player and put the effect there. + + // Translate the line so the player's (and the ellipse's) center is at the origin. + Vector vCenter = Center(); + Vector vStart = ptr->startpos - vCenter; + Vector vEnd = ptr->endpos - vCenter; + + // Figure out the ellipse dimensions. + Vector vDims = (WorldAlignMaxs() - WorldAlignMins()) * 0.5f; + Vector vEllipse = vDims * 1.5; + + // Squash the line we're testing so we're testing against a sphere of radius 1 at the origin. + vStart /= vEllipse; + vEnd /= vEllipse; + + // See where the line hits the sphere. + Vector vLineDir = vEnd - vStart; + float f1, f2; + if ( IntersectInfiniteRayWithSphere( vStart, vLineDir, vec3_origin, 1, &f1, &f2 ) ) + { + // Use the closest hit point on the sphere. + float fMin = MIN( f1, f2 ); + Vector vPos = vStart + vLineDir * fMin; + + // Unsquash back to the ellipse's dimensions. + vPos *= vEllipse; + + ShowPersonalShieldEffect( vPos, vecDir, subInfo.GetDamage() ); + } +*/ + + AddMultiDamage( subInfo, this ); + } +} + + +//----------------------------------------------------------------------------- +// Applies a force on the player when he takes damage +//----------------------------------------------------------------------------- +void CBaseTFPlayer::ApplyDamageForce( const CTakeDamageInfo &info, int nDamageToDo ) +{ + if (nDamageToDo <= 0) + return; + + if ( (info.GetDamageType() & (DMG_ENERGYBEAM | DMG_BLAST)) == 0 ) + return; + + if ( !info.GetInflictor() || (info.GetInflictor() == this) || info.GetAttacker()->IsSolidFlagSet(FSOLID_TRIGGER) ) + return; + + // Don't blow ragdolls around + if ( IsClientRagdoll() ) + return; + + // Don't bother with crouched players, or classes that have other rules about it + if ( GetFlags() & FL_DUCKING ) + return; + + if (!GetPlayerClass() || !GetPlayerClass()->ShouldApplyDamageForce( info )) + return; + + Vector vecDir; + // If the inflictor isn't moving, use the delta between it & me. If it's moving, use it's velocity. + Vector vecInflictorVelocity; + info.GetInflictor()->GetVelocity( &vecInflictorVelocity, NULL ); + + // Explosives never use the velocity of the inflictor + if ( !(info.GetDamageType() & DMG_BLAST) && vecInflictorVelocity != vec3_origin ) + { + vecDir = vecInflictorVelocity; + } + else + { + vecDir = WorldSpaceCenter( ); + vecDir -= info.GetInflictor()->WorldSpaceCenter( ); + } + VectorNormalize( vecDir ); + + float flForce = (nDamageToDo * 2) + 20; + if (flForce > 1000.0) + flForce = 1000.0; + + // Escorts get knocked half as far + if ( PlayerClass() == TFCLASS_ESCORT ) + { + flForce *= 0.5; + } + + vecDir *= flForce; + + if ( (GetMoveType() != MOVETYPE_FLY) && (GetMoveType() != MOVETYPE_FLYGRAVITY) && ((GetFlags() & FL_ONGROUND) != 0) ) + { + // Need large x-y component to overcome walking friction + vecDir.x *= 3; + vecDir.y *= 3; + } + + Vector vecNewVelocity = GetAbsVelocity(); + vecNewVelocity += vecDir; + + Vector vecTestVel = vecNewVelocity; + float flLen = VectorNormalize( vecTestVel ); + if (flLen > MAX_EXPLOSIVE_VELOCITY) + VectorMultiply( vecTestVel, MAX_EXPLOSIVE_VELOCITY, vecNewVelocity ); + + SetAbsVelocity( vecNewVelocity ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Deal damage to the player +//----------------------------------------------------------------------------- +int CBaseTFPlayer::OnTakeDamage( const CTakeDamageInfo &info ) +{ + if ( !IsAlive() ) + return 0; + + //if ( GetFlags() & FL_GODMODE ) + //return 0; + + // Generate a global order event. + COrderEvent_PlayerDamaged event; + event.m_pPlayerDamaged = this; + event.m_TakeDamageInfo = info; + GlobalOrderEvent( &event ); + + // Don't do damage if the player's in a vehicle, in a non-damagable spot. + if ( IsInAVehicle() && m_hVehicle.Get() ) + { + IServerVehicle* pVehicle = m_hVehicle.Get()->GetServerVehicle(); + Assert( pVehicle ); + int nRole = pVehicle->GetPassengerRole(this); + + if( ( nRole < 0 ) + || !pVehicle->IsPassengerVisible(nRole) + || !pVehicle->IsPassengerDamagable(nRole) ) + { + return 0; + } + } + + // Check teams + CBaseTFPlayer *pPlayer = (CBaseTFPlayer*)info.GetAttacker(); + if ( pPlayer ) + { + // Take damage from myself + if ( pPlayer != this ) + { + if ( InSameTeam(pPlayer) ) + { + return 0; + } + else + { + // Store off the last time we were damaged by an enemy so commandos can + // get orders to assist. + m_flLastTimeDamagedByEnemy = gpGlobals->curtime; + } + } + } + + CTakeDamageInfo subInfo = info; + + // Let the playerclass at it + if ( GetPlayerClass() ) + { + subInfo.SetDamage( GetPlayerClass()->OnTakeDamage( subInfo ) ); + } + + if ( !subInfo.GetDamage() ) + return 0; + + //Msg( "Weapon did: %f\n", flDamage ); + + int iDamageToDo = Ceil2Int( subInfo.GetDamage() ); + + if ( !(GetFlags() & FL_GODMODE) ) + { + // Only certain damage types knock players around + ApplyDamageForce( info, iDamageToDo ); + + m_iHealth = MAX(0, m_iHealth - iDamageToDo); + } + + //Msg( "m_iHealth: %d\n\n", m_iHealth ); + + // Dead? + if ( m_iHealth < 1 ) + { + Event_Killed( subInfo ); + } + + // Let the client know + // Try and figure out where the damage is coming from + Vector vecDamageOrigin = info.GetReportedPosition(); + // If we didn't get an origin to use, try using the attacker's origin + if ( vecDamageOrigin == vec3_origin && info.GetAttacker() ) + { + vecDamageOrigin = info.GetAttacker()->GetAbsOrigin(); + } + + CSingleUserRecipientFilter user( this ); + UserMessageBegin( user, "Damage" ); + WRITE_BYTE( clamp( iDamageToDo, 0, 255 ) ); + WRITE_FLOAT( vecDamageOrigin.x ); // BUG: Should be fixed point (to hud) not floats + WRITE_FLOAT( vecDamageOrigin.y ); // BUG: However, the HUD does _not_ implement bitfield messages (yet) + WRITE_FLOAT( vecDamageOrigin.z ); // BUG: We use WRITE_VEC3COORD for everything else + MessageEnd(); + + // Do special explosion damage effect + if ( info.GetDamageType() & DMG_BLAST ) + { + OnDamagedByExplosion( info ); + } + + return iDamageToDo; +} + + +void CBaseTFPlayer::ShowPersonalShieldEffect( + const Vector &vOffsetFromEnt, + const Vector &vIncomingDirection, + float flDamage ) +{ + Vector vNormalized = vIncomingDirection; + VectorNormalize( vNormalized ); + + EntityMessageBegin( this ); + WRITE_BYTE( PLAYER_MSG_PERSONAL_SHIELD ); + WRITE_VEC3COORD( vOffsetFromEnt ); + WRITE_VEC3NORMAL( vNormalized ); + WRITE_SHORT( (short)flDamage ); + MessageEnd(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Player is being healed +//----------------------------------------------------------------------------- +int CBaseTFPlayer::TakeHealth( float flHealth, int bitsDamageType ) +{ + if ( m_iHealth == m_iMaxHealth ) + return 0; + + // Heal the location + float flAmountToHeal = flHealth; + if ( flAmountToHeal > (m_iMaxHealth - m_iHealth) ) + flAmountToHeal = (m_iMaxHealth - m_iHealth); + m_iHealth += flAmountToHeal; + + //Msg( "Health: %d\n", m_iHealth ); + + return flAmountToHeal; +} + + +//===================================================================== +// MENU HANDLING +//===================================================================== +void CBaseTFPlayer::MenuDisplay( void ) +{ + if ( !m_pCurrentMenu ) + { + m_MenuRefreshTime = 0; + return; + } + + if ( m_MenuRefreshTime > gpGlobals->curtime ) + { + // guard against sudden clock changes + m_MenuRefreshTime = MIN( m_MenuRefreshTime, gpGlobals->curtime + MENU_UPDATETIME ); + return; + } + + m_MenuRefreshTime = gpGlobals->curtime + MENU_UPDATETIME; + + if ( m_pCurrentMenu ) + m_pCurrentMenu->Display( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::MenuInput( int iInput ) +{ + if ( m_pCurrentMenu ) + { + return m_pCurrentMenu->Input( this, iInput ); + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::MenuReset( void ) +{ + CSingleUserRecipientFilter user( this ); + user.MakeReliable(); + + UserMessageBegin( user, "ShowMenu" ); + WRITE_SHORT( 0 ); + WRITE_CHAR( 0 ); // display time (-1 means unlimited) + WRITE_BYTE( false ); // is there more message to come? no + WRITE_STRING( "" ); + MessageEnd(); + + Q_strncpy( m_MenuStringBuffer, "" , sizeof(m_MenuStringBuffer) ); + m_MenuRefreshTime = m_MenuDisplayTime = 0; + + m_pCurrentMenu = NULL; +}; + +//----------------------------------------------------------------------------- +// Purpose: Enables/disables tactical/map view for the player +// Input : bTactical - true == enable it +//----------------------------------------------------------------------------- +void CBaseTFPlayer::ShowTacticalView( bool bTactical ) +{ + // TODO: Decide if we are going to keep the tactical view in TF2 + if ( !inv_demo.GetBool() ) + return; + + m_bSwitchingView = true; + m_TFLocal.m_nInTacticalView = bTactical ? 1 : 0; +} + +//----------------------------------------------------------------------------- +// returns true if we're in tactical view +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::IsInTacticalView( void ) const +{ + return m_TFLocal.m_nInTacticalView; +} + +int CBaseTFPlayer::UpdateTransmitState() +{ + return SetTransmitState( FL_EDICT_FULLCHECK ); +} + +//----------------------------------------------------------------------------- +// Purpose: Note, an entity can override the send table ( e.g., to send less data or to send minimal data for +// objects ( prob. players ) that are not in the pvs. +// Input : **ppSendTable - +// *recipient - +// *pvs - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +int CBaseTFPlayer::ShouldTransmit( const CCheckTransmitInfo *pInfo ) +{ + // Don't transmit if we have no team or class + if ((PlayerClass() == TFCLASS_UNDECIDED) || (GetTeamNumber() == 0)) + return FL_EDICT_DONTSEND; + + // Thermal vision in effect, if so, cull some players who are too far away + CBaseTFPlayer *pPlayer = ( ( CBaseTFPlayer * )CBaseEntity::Instance( pInfo->m_pClientEnt ) ); + if ( pPlayer ) + { + if ( pPlayer->IsUsingThermalVision() ) + { + // Do a radius check, and force sending of guys nearby (so we can see them through walls) + Vector dist = GetAbsOrigin() - pPlayer->GetAbsOrigin(); + if ( dist.Length() < THERMAL_VISION_RADIUS ) + return FL_EDICT_ALWAYS; + } + + // If the player we might see is camouflaged and not on our team, we can preclude based + // on distance + if ( IsCamouflaged() && !InSameTeam( pPlayer ) ) + { + Vector dist = GetAbsOrigin() - pPlayer->GetAbsOrigin(); + if ( dist.Length() > CAMO_OUTER_RADIUS ) + { + return FL_EDICT_ALWAYS; + } + } + } + + // Use default pvs etc. rules + return BaseClass::ShouldTransmit( pInfo ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTechnologyTree *CBaseTFPlayer::GetTechTree( void ) +{ + CTFTeam *pTeam = GetTFTeam(); + if ( pTeam ) + return pTeam->m_pTechnologyTree; + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CBaseTFPlayer::PlayerClass( void ) +{ + return m_iPlayerClass; +} + +CPlayerClass *CBaseTFPlayer::GetPlayerClass() +{ + return m_PlayerClasses.GetPlayerClass( PlayerClass() ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetPreferredTechnology( CTechnologyTree *pTechnologyTree, int iTechIndex ) +{ + Assert( pTechnologyTree ); + + // Have to be on a team to vote for tech + CTFTeam *pTeam = GetTFTeam(); + if ( !pTeam ) + return; + + if ( iTechIndex == -1 ) + { + m_nPreferredTechnology = -1; + } + else + { + if ( iTechIndex < 0 || iTechIndex >= pTechnologyTree->GetNumberTechnologies() ) + { + Msg( "%s tried to set voting preference to unknown technology index : %d\n", + GetPlayerName(), iTechIndex ); + return; + } + CBaseTechnology *tech = pTechnologyTree->GetTechnology( iTechIndex ); + if ( !tech ) + return; + + // Has the tech got incomplete dependancies? + if ( tech->HasInactiveDependencies() ) + return; + // Already have it? + if ( tech->GetAvailable() ) + return; + // Can't prefer a hidden tech + if ( tech->IsHidden() ) + return; + + m_nPreferredTechnology = iTechIndex; + } + + pTeam->RecomputePreferences(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CBaseTFPlayer::GetPreferredTechnology( void ) +{ + return m_nPreferredTechnology; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::HasNamedTechnology( const char *name ) +{ + if ( GetTFTeam() == NULL ) + return false; + + return GetTFTeam()->HasNamedTechnology( name ); +} + +//----------------------------------------------------------------------------- +// Purpose: Networking is about to update this player, let it override and specify it's own pvs +// Input : **pvs - +// **pas - +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetupVisibility( CBaseEntity *pViewEntity, unsigned char *pvs, int pvssize ) +{ + // Normal PVS + BaseClass::SetupVisibility( pViewEntity, pvs, pvssize ); + + // PVS has an additional origin + if ( m_vecAdditionalPVSOrigin != vec3_origin ) + { + // Add an additional origin to the pvs + engine->AddOriginToPVS( m_vecAdditionalPVSOrigin ); + } + + if ( m_vecCameraPVSOrigin != vec3_origin ) + { + engine->AddOriginToPVS( m_vecCameraPVSOrigin ); + } + + // If in tactical mode, merge in pvs from all of our teammates, too + // send all the others team info + if ( m_TFLocal.m_nInTacticalView ) + { + int i; + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseTFPlayer *plr = ToBaseTFPlayer( UTIL_PlayerByIndex(i) ); + if ( plr && + ( plr != this ) && + ( plr->TeamID() == TeamID() ) ) + { + Vector org; + org = plr->EyePosition(); + + engine->AddOriginToPVS( org ); + } + } + } +} + +//-------------------------------------------------------------------------------------------------------------- +// ORDERS +//----------------------------------------------------------------------------- +// Purpose: Assign the player to the specified order +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetOrder( COrder *pOrder ) +{ + if ( m_hSelectedOrder.Get() && m_hSelectedOrder != pOrder ) + { + m_hSelectedOrder->SetOwner( NULL ); + } + m_hSelectedOrder = pOrder; +} + + +int CBaseTFPlayer::GetNumResourceZoneOrders() +{ + return GetTFTeam()->CountOrders( COUNTORDERS_TYPE | COUNTORDERS_OWNER, ORDER_ATTACK, 0, this ) + + GetTFTeam()->CountOrders( COUNTORDERS_TYPE | COUNTORDERS_OWNER, ORDER_DEFEND, 0, this ) + + GetTFTeam()->CountOrders( COUNTORDERS_TYPE | COUNTORDERS_OWNER, ORDER_CAPTURE, 0, this ); +} + + +void CBaseTFPlayer::KillResourceZoneOrders() +{ + if( GetNumResourceZoneOrders() ) + GetTFTeam()->RemoveOrdersToPlayer( this ); +} + + +//-------------------------------------------------------------------------------------------------------------- +// DEPLOYMENT +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::StartDeploying( void ) +{ + if ( !GetPlayerClass() ) + return; + + m_bDeploying = true; + m_vecDeployedAngles = GetLocalAngles(); + + // No pitch or roll, though + m_vecDeployedAngles.SetX( 0 ); + m_vecDeployedAngles.SetZ( 0 ); + + SetCantMove( true ); + m_flFinishedDeploying = gpGlobals->curtime + GetPlayerClass()->GetDeployTime(); + + SetAnimation( PLAYER_START_AIMING ); + + EmitSound( "BaseTFPlayer.StartDeploying" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::StartUnDeploying( void ) +{ + if ( !GetPlayerClass() ) + return; + + m_bUnDeploying = true; + SetCantMove( true ); + m_flFinishedDeploying = gpGlobals->curtime + GetPlayerClass()->GetDeployTime(); + SetAnimation( PLAYER_LEAVE_AIMING ); + + EmitSound( "BaseTFPlayer.StartUnDeploying" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::CheckDeployFinish( void ) +{ + // Check to see if deployment has finished + if ( m_bDeploying ) + { + if ( gpGlobals->curtime > m_flFinishedDeploying ) + { + FinishDeploying(); + } + return; + } + + // Check to see if un-deployment has finished + if ( m_bUnDeploying ) + { + if ( gpGlobals->curtime > m_flFinishedDeploying ) + { + FinishUnDeploying(); + } + return; + } + + // Check to see if the player's trying to move while deployed + if ( IsAlive() && m_bDeployed ) + { + if ( m_nButtons & (IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT ) ) + { + StartUnDeploying(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::FinishDeploying( void ) +{ + m_bDeploying = false; + m_bDeployed = true; + SetCantMove( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::FinishUnDeploying( void ) +{ + m_bUnDeploying = false; + m_bDeployed = false; + SetCantMove( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::IsDeployed( void ) +{ + return m_bDeployed; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::IsDeploying( void ) +{ + return (m_bDeploying || m_bUnDeploying); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::IsUnDeploying( void ) +{ + return m_bUnDeploying; +} + +void CBaseTFPlayer::OnVehicleStart() +{ + // Do any class-specific stuff + if (GetPlayerClass()) + { + GetPlayerClass()->OnVehicleStart(); + } + + IServerVehicle *pVehicle = GetVehicle(); + CBaseCombatWeapon *weapon = GetActiveWeapon(); + if ( pVehicle && weapon ) + { + // Get Role for this player + int role = pVehicle->GetPassengerRole( this ); + bool allowweapons = pVehicle->IsPassengerUsingStandardWeapons( role ); + if ( !allowweapons ) + { + weapon->Holster(); + } + } +} + +void CBaseTFPlayer::OnVehicleEnd( Vector &playerDestPosition ) +{ + // Do any class-specific stuff + if (GetPlayerClass()) + { + GetPlayerClass()->OnVehicleEnd(); + } + + Vector vNewPos; + if ( !EntityPlacementTest( this, playerDestPosition, vNewPos, true) ) + { + Warning("Can't find valid place to exit vehicle.\n"); + return; + } + + // Move the player up a bit to be safe + playerDestPosition = vNewPos + Vector(0,0,16); + + CBaseCombatWeapon *weapon = GetActiveWeapon(); + if ( weapon ) + { + weapon->Deploy(); + } +} + +//-------------------------------------------------------------------------------------------------------------- +// Purpose: +//-------------------------------------------------------------------------------------------------------------- +bool CBaseTFPlayer::CanGetInVehicle( void ) +{ + // Class-specific? + if ( GetPlayerClass() ) + { + return GetPlayerClass()->CanGetInVehicle(); + } + + return true; +} + + +CVehicleTeleportStation* CBaseTFPlayer::GetSelectedMCV() const +{ + return dynamic_cast< CVehicleTeleportStation* >( m_hSelectedMCV.Get() ); +} + + +void CBaseTFPlayer::SetSelectedMCV( CVehicleTeleportStation *pMCV ) +{ + m_hSelectedMCV = pMCV; +} + + +//----------------------------------------------------------------------------- +// Purpose: Restore this player's ammo count to it's starting state +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::ResupplyAmmo( float flPercentage, ResupplyReason_t reason ) +{ + if ( !GetPlayerClass() ) + return false; + + return GetPlayerClass()->ResupplyAmmo(flPercentage, reason); +} + + +//----------------------------------------------------------------------------- +// Purpose: Medic has provided this player with a health boost +//----------------------------------------------------------------------------- +void CBaseTFPlayer::TakeHealthBoost( int iHealthBoost, int iTarget, int iDuration ) +{ + m_iHealth += iHealthBoost; + m_iHealthBoostTarget = iTarget; + m_flHealthBoostDecrement = ceil((m_iHealth - iTarget) / (float)iDuration); + + // Start the health ticking down + m_flHealthBoostTime = gpGlobals->curtime + 1.0; + + if ( iTarget >= m_iMaxHealth ) + { + m_bBuffHealthBoost = true; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Remove health buffs +//----------------------------------------------------------------------------- +void CBaseTFPlayer::RemoveHealthBoost( void ) +{ + m_bBuffHealthBoost = false; + m_iHealthBoostTarget = 0; + m_flHealthBoostTime = 0; + + if ( m_iHealth > m_iMaxHealth ) + { + m_iHealth = m_iMaxHealth; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Check the state of all buffs on this player +//----------------------------------------------------------------------------- +void CBaseTFPlayer::CheckBuffs( void ) +{ + // Health boost? + if ( m_bBuffHealthBoost ) + { + // Dropped below normal max health? + if ( m_iHealth <= m_iHealthBoostTarget ) + { + RemoveHealthBoost(); + } + else + { + if ( m_flHealthBoostTime < gpGlobals->curtime ) + { + // Ticking down from a boost? or suffering poison damage? + if ( m_iHealth > m_iMaxHealth ) + { + // Drop back to normal health in 20 seconds + m_iHealth -= m_flHealthBoostDecrement; + } + + m_flHealthBoostTime = gpGlobals->curtime + 1.0; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Powerup has just started +//----------------------------------------------------------------------------- +void CBaseTFPlayer::PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier ) +{ + Assert( iPowerup >= 0 && iPowerup < MAX_POWERUPS ); + + switch( iPowerup ) + { + case POWERUP_BOOST: + { + m_hLastBoostEntity = pAttacker; + + // Power up their shield + if ( GetCombatShield() ) + { + GetCombatShield()->AddShieldHealth( 0.06 ); + } + + // Let their playerclass know + GetPlayerClass()->PowerupStart( iPowerup, flAmount, pAttacker, pDamageModifier ); + } + break; + + case POWERUP_EMP: + { + // Let the playerclass know about it + GetPlayerClass()->PowerupStart( iPowerup, flAmount, pAttacker, pDamageModifier ); + } + break; + + case POWERUP_RUSH: + { + // Speed up + // We need to set this here so RecalculateSpeed() can check HasPowerup(POWERUP_RUSH) + m_iPowerups |= (1 << iPowerup); + RecalculateSpeed(); + } + break; + + default: + break; + } + + BaseClass::PowerupStart( iPowerup, flAmount, pAttacker, pDamageModifier ); +} + +//----------------------------------------------------------------------------- +// Purpose: Powerup has just started +//----------------------------------------------------------------------------- +void CBaseTFPlayer::PowerupEnd( int iPowerup ) +{ + switch( iPowerup ) + { + case POWERUP_EMP: + { + GetPlayerClass()->PowerupEnd(iPowerup); + } + break; + + case POWERUP_RUSH: + { + // Slow down + RecalculateSpeed(); + } + break; + + default: + break; + } + + BaseClass::PowerupEnd( iPowerup ); +} + +//-------------------------------------------------------------------------------------------------------------- +// OBJECTS +//----------------------------------------------------------------------------- +// Purpose: Return true if this player's allowed to build another one of the specified object +//----------------------------------------------------------------------------- +int CBaseTFPlayer::CanBuild( int iObjectType ) +{ + if ( iObjectType < 0 || iObjectType >= OBJ_LAST ) + return CB_NOT_RESEARCHED; + + if ( GetPlayerClass() ) + { + return GetPlayerClass()->CanBuild( iObjectType ); + } + + return CB_NOT_RESEARCHED; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the number of objects of the specified type that this player has +//----------------------------------------------------------------------------- +int CBaseTFPlayer::GetNumObjects( int iObjectType ) +{ + int iCount = 0; + for (int i = 0; i < GetObjectCount(); i++) + { + if ( !GetObject(i) ) + continue; + + if ( GetObject(i)->GetType() == iObjectType ) + { + iCount++; + } + } + + return iCount; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CBaseTFPlayer::GetObjectCount( void ) +{ + return m_TFLocal.m_aObjects.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBaseObject *CBaseTFPlayer::GetObject( int index ) +{ + return m_TFLocal.m_aObjects[index].Get(); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if this player is building something +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::IsBuilding( void ) +{ + CWeaponBuilder *pBuilder = GetWeaponBuilder(); + if ( pBuilder ) + return pBuilder->IsBuilding(); + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Object built by this player has been destroyed +//----------------------------------------------------------------------------- +void CBaseTFPlayer::OwnedObjectDestroyed( CBaseObject *pObject ) +{ + TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseTFPlayer::OwnedObjectDestroyed player %s object %p:%s\n", gpGlobals->curtime, + GetPlayerName(), + pObject, + pObject->GetClassname() ) ); + + if ( GetPlayerClass() ) + { + GetPlayerClass()->OwnedObjectDestroyed( pObject ); + } + + RemoveObject( pObject ); + + // Tell our builder weapon so it recalculates the state of the build icons + CWeaponBuilder *pBuilder = GetWeaponBuilder(); + if ( pBuilder ) + { + pBuilder->GainedNewTechnology( NULL ); + } +} + + +//----------------------------------------------------------------------------- +// Removes an object from the player +//----------------------------------------------------------------------------- +void CBaseTFPlayer::RemoveObject( CBaseObject *pObject ) +{ + TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseTFPlayer::RemoveObject %p:%s from player %s\n", gpGlobals->curtime, + pObject, + pObject->GetClassname(), + GetPlayerName() ) ); + + Assert( pObject ); + for (int i = m_TFLocal.m_aObjects.Count(); --i >= 0; ) + { + // Also, while we're at it, remove all other bogus ones too... + if ((!m_TFLocal.m_aObjects[i].Get()) || (m_TFLocal.m_aObjects[i] == pObject)) + { + m_TFLocal.m_aObjects.FastRemove(i); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pObject - +// *pNewOwner - +//----------------------------------------------------------------------------- +void CBaseTFPlayer::OwnedObjectChangeTeam( CBaseObject *pObject, CBaseTFPlayer *pNewOwner ) +{ + TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseTFPlayer::OwnedObjectChangeTeam player %s object %p:%s new player %s\n", gpGlobals->curtime, + GetPlayerName(), + pObject, + pObject->GetClassname(), + pNewOwner->GetPlayerName() ) ); + + if ( pNewOwner && pNewOwner->GetPlayerClass() ) + { + pNewOwner->GetPlayerClass()->OwnedObjectChangeFromTeam( pObject, this ); + } + + // Remove from my list of objects + RemoveObject( pObject ); + + // Add to new team + if ( pNewOwner ) + { + pNewOwner->AddObject( pObject ); + } + + if ( GetPlayerClass() ) + { + GetPlayerClass()->OwnedObjectChangeToTeam( pObject, pNewOwner ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pZone - +// Output : int +//----------------------------------------------------------------------------- +int CBaseTFPlayer::NumPumpsOnResourceZone( CResourceZone *pZone ) +{ + int ret = 0; + + for( int iObj=0; iObj < GetObjectCount(); iObj++ ) + { + CBaseObject *pObj = GetObject(iObj); + + if( pObj->GetType() == OBJ_RESOURCEPUMP ) + { + CObjectResourcePump *pPump = (CObjectResourcePump*)pObj; + + // Ok, this guy already has a pump here. + if( pPump->GetResourceZone() == pZone ) + ++ret; + } + } + + return ret; +} + + + +//----------------------------------------------------------------------------- +// Purpose: Remove all the player's objects +// If bForceAll is set, remove all of them immediately. +// Otherwise, make them all deteriorate over time. +// If iClass is passed in, don't remove any objects that can be built +// by that class. If bReturnResources is set, the cost of any destroyed +// objects will be returned to the player. +//----------------------------------------------------------------------------- +void CBaseTFPlayer::RemoveAllObjects( bool bForceAll, int iClass, bool bReturnResources ) +{ + // Remove all the player's objects + int iSize = GetObjectCount(); + for (int i = iSize-1; i >= 0; i--) + { + CBaseObject *obj = GetObject(i); + Assert( obj ); + if ( !obj ) + { + continue; + } + + if ( !bForceAll ) + { + if ( iClass ) + { + // Can our new class build this object? + if ( ClassCanBuild( iClass, obj->GetType() ) ) + continue; + } + + // Vehicles don't deteriorate when their owner changes teams/leaves. + // They'll deteriorate naturally if they're unused for a while. + if ( IsObjectAVehicle(obj->GetType()) ) + { + RemoveObject( obj ); + + // Just remove it from my list + obj->SetBuilder( NULL ); + continue; + } + } + + // Return the cost of the object? + if ( bReturnResources ) + { + GetPlayerClass()->PickupObject( obj ); + } + + // Remove or deteriorate? + if ( bForceAll ) + { + UTIL_Remove( obj ); + } + else + { + OwnedObjectDestroyed( obj ); + obj->StartDeteriorating(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::StopPlacement( void ) +{ + // Tell our builder weapon + CWeaponBuilder *pBuilder = GetWeaponBuilder(); + if ( pBuilder ) + { + pBuilder->StopPlacement(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Player has started building an object +//----------------------------------------------------------------------------- +int CBaseTFPlayer::StartedBuildingObject( int iObjectType ) +{ + // Tell our playerclass + if ( GetPlayerClass() ) + return GetPlayerClass()->StartedBuildingObject( iObjectType ); + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Player has aborted building something +//----------------------------------------------------------------------------- +void CBaseTFPlayer::StoppedBuilding( int iObjectType ) +{ + // Tell our playerclass + if ( GetPlayerClass() ) + { + GetPlayerClass()->StoppedBuilding( iObjectType ); + } + + // Tell our builder weapon + CWeaponBuilder *pBuilder = GetWeaponBuilder(); + if ( pBuilder ) + { + pBuilder->StoppedBuilding( iObjectType ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Object has been built by this player +//----------------------------------------------------------------------------- +void CBaseTFPlayer::FinishedObject( CBaseObject *pObject ) +{ + AddObject( pObject ); + + // Tell our playerclass + if ( GetPlayerClass() ) + { + GetPlayerClass()->FinishedObject( pObject ); + } + + // Tell our builder weapon + CWeaponBuilder *pBuilder = GetWeaponBuilder(); + if ( pBuilder ) + { + pBuilder->FinishedObject(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Add the specified object to this player's object list. +//----------------------------------------------------------------------------- +void CBaseTFPlayer::AddObject( CBaseObject *pObject ) +{ + TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseTFPlayer::AddObject adding object %p:%s to player %s\n", gpGlobals->curtime, pObject, pObject->GetClassname(), GetPlayerName() ) ); + + // Make a handle out of it + CHandle<CBaseObject> hObject; + hObject = pObject; + + // Make sure it's not in the list already + bool alreadyInList = (m_TFLocal.m_aObjects.Find( hObject ) != -1); + Assert( !alreadyInList ); + if ( !alreadyInList ) + { + m_TFLocal.m_aObjects.AddToTail( hObject ); + } + + // Stop it deterioating, if it is + pObject->StopDeteriorating(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetWeaponBuilder( CWeaponBuilder *pBuilder ) +{ + m_hWeaponBuilder = pBuilder; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CWeaponBuilder *CBaseTFPlayer::GetWeaponBuilder( void ) +{ + return m_hWeaponBuilder; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *attacker - +// sourceDir - +// duration - +//----------------------------------------------------------------------------- +void CBaseTFPlayer::KnockDownPlayer( const Vector& sourceDir, float magnitude, float duration ) +{ + // Already knocked down + if ( m_TFLocal.m_bKnockedDown ) + return; + // In a vehicle + if ( IsInAVehicle() ) + return; + + m_TFLocal.m_bKnockedDown = true; + + // Randomize it a bit + Vector jitter( 0, 0, 0 ); + //jitter.Random( -0.1, 0.1 ); + + Vector dir = sourceDir + jitter; + + VectorNormalize( dir ); + + Vector force = dir * magnitude; + ApplyAbsVelocityImpulse( force ); + + VectorAngles( dir, m_TFLocal.m_vecKnockDownDir.GetForModify() ); + + QAngle ang = GetAbsAngles(); + Vector forward, right; + AngleVectors( ang, &forward, &right, NULL ); + + float dotFwd = dir.Dot( forward ); + float dotRight = dir.Dot( right ); + + if ( dotFwd >= 0) + { + // if get hit from behind, pitch down a bit + m_TFLocal.m_vecKnockDownDir.SetX( dotFwd * 20.0f ); + // look in the direction you fell + m_TFLocal.m_vecKnockDownDir.SetZ( dotRight * 80.0f ); + } + else + { + //Invert knock yaw if hit from front, so you are looking straight up at the direction + // the hit cam efrom + m_TFLocal.m_vecKnockDownDir += QAngle( 0, 180, 0 ); + // Look up in the air + m_TFLocal.m_vecKnockDownDir.SetX( fabs( dotFwd ) * -60.0f ); + // And a bit to the side the hit came from + m_TFLocal.m_vecKnockDownDir.SetZ( dotRight * 20.0f ); + } + + m_flKnockdownEndTime = gpGlobals->curtime + duration; + + // Play some kind of knockdown sound + EmitSound( "BaseTFPlayer.KnockedDown" ); + + if ( BecomeRagdollOnClient( force ) ) + { + // We we are using ragdoll flight, then don't change underlying player + // velocity + ApplyAbsVelocityImpulse( -force ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::ResetKnockdown( void ) +{ + // Don't get up if I'm dead + if ( IsAlive() ) + { + if ( !ClearClientRagdoll( true ) ) + return; + } + + m_TFLocal.m_bKnockedDown = false; + m_TFLocal.m_vecKnockDownDir.Init(); + m_flKnockdownEndTime = 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::IsKnockedDown( void ) +{ + return m_TFLocal.m_bKnockedDown; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::CheckKnockdown( void ) +{ + if ( !m_TFLocal.m_bKnockedDown ) + return; + + if ( gpGlobals->curtime < m_flKnockdownEndTime ) + return; + + // Remove knockdown + ResetKnockdown(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::IsGagged( void ) +{ + return m_bGagged; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : gag - +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetGagged( bool gag ) +{ + m_bGagged = gag; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::CanSpeak( void ) +{ + return !IsGagged(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::IsUsingThermalVision( void ) +{ + return m_TFLocal.m_bThermalVision; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetIDEnt( CBaseEntity *pEntity ) +{ + if ( pEntity ) + m_TFLocal.m_iIDEntIndex = pEntity->entindex(); + else + m_TFLocal.m_iIDEntIndex = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : thermal - +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetUsingThermalVision( bool thermal ) +{ + // Play sounds if we're changing + if ( m_TFLocal.m_bThermalVision != thermal ) + { + if ( thermal ) + { + EmitSound( "BaseTFPlayer.ThermalOn" ); + } + else + { + EmitSound( "BaseTFPlayer.ThermalOff" ); + } + } + + m_TFLocal.m_bThermalVision = thermal; +} + + +//----------------------------------------------------------------------------- +// Purpose: Add the specified number of resource chunks to the player. Return true if he can carry it all. +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::AddResourceChunks( int iChunks, bool bProcessed ) +{ + // Am I allowed to carry any more chunks? + int iCurrentCount = GetTotalResourceChunks(); + // Somewhat hacky + int iIndex = GetAmmoDef()->Index("ResourceChunks"); + int iMax = GetAmmoDef()->MaxCarry( iIndex ); + if ( iCurrentCount >= iMax ) + { + bool bSwapped = false; + + // If this is a processed chunk, see if we can swap it for an unprocessed chunk + if ( bProcessed ) + { + if ( m_TFLocal.m_iResourceAmmo[ NORMAL_RESOURCES ] ) + { + // Drop this unprocessed chunk + Vector vecVelocity = Vector( random->RandomFloat( -250,250 ), random->RandomFloat( -250,250 ), random->RandomFloat( 200,450 ) ); + CResourceChunk::Create( false, GetAbsOrigin() + Vector(0,0,32), vecVelocity ); + RemoveResourceChunks( 1, false ); + bSwapped = true; + } + } + + if ( !bSwapped ) + return false; + } + + m_TFLocal.m_iResourceAmmo.Set( bProcessed, MIN( iMax, m_TFLocal.m_iResourceAmmo[ bProcessed ] + iChunks ) ); + SetAmmoCount( GetTotalResourceChunks(), iIndex ); + CPASAttenuationFilter filter( this,"BaseTFPlayer.PickupResources" ); + EmitSound( filter, entindex(),"BaseTFPlayer.PickupResources" ); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Remove the specified number of resources chunks from the player. +//----------------------------------------------------------------------------- +void CBaseTFPlayer::RemoveResourceChunks( int iChunks, bool bProcessed ) +{ + // Remove the amount + m_TFLocal.m_iResourceAmmo.Set( bProcessed, MAX( 0, m_TFLocal.m_iResourceAmmo[ bProcessed ] - iChunks ) ); + int iIndex = GetAmmoDef()->Index("ResourceChunks"); + SetAmmoCount( GetTotalResourceChunks(), iIndex ); +} + +//----------------------------------------------------------------------------- +// Purpose: Get the number of resource chunks of this type the player's carrying +//----------------------------------------------------------------------------- +int CBaseTFPlayer::GetResourceChunkCount( bool bProcessed ) +{ + return m_TFLocal.m_iResourceAmmo[ bProcessed ]; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the total number of resource chunks being carried by the player +//----------------------------------------------------------------------------- +int CBaseTFPlayer::GetTotalResourceChunks( void ) +{ + int iCurrentCount = 0; + for ( int i = 0; i < RESOURCE_TYPES; i++ ) + { + iCurrentCount += m_TFLocal.m_iResourceAmmo[i]; + } + + return iCurrentCount; +} + + +//----------------------------------------------------------------------------- +// Purpose: Drop some resource chunks +//----------------------------------------------------------------------------- +void CBaseTFPlayer::DropAllResourceChunks( void ) +{ + Vector vecOrigin = GetAbsOrigin() + Vector(0,0,32); + + TFStats()->IncrementTeamStat( GetTeamNumber(), TF_TEAM_STAT_RESOURCE_CHUNKS_DROPPED, resource_chunk_value.GetFloat() ); + + // Drop a resource chunk + Vector vecVelocity = Vector( random->RandomFloat( -250,250 ), random->RandomFloat( -250,250 ), random->RandomFloat( 200,450 ) ); + CResourceChunk *pChunk = CResourceChunk::Create( FALSE, vecOrigin, vecVelocity ); + pChunk->ChangeTeam( GetTeamNumber() ); +} + + +//------------------------------------------------------------------------------------------------------------------ +// RESOURCE BANK +//----------------------------------------------------------------------------- +// Purpose: Get the amount of a resource in this player's bank +//----------------------------------------------------------------------------- +int CBaseTFPlayer::GetBankResources( void ) +{ + return m_TFLocal.ResourceCount(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetBankResources( int iAmount ) +{ + int nOldAmount = m_TFLocal.ResourceCount(); + + TFStats()->IncrementPlayerStat( this, TF_PLAYER_STAT_RESOURCES_ACQUIRED, iAmount - nOldAmount ); + + m_TFLocal.SetResources( iAmount ); + + // Tell the player's builder weapon to update + CWeaponBuilder *pBuilder = GetWeaponBuilder(); + if ( pBuilder ) + { + pBuilder->GainedNewTechnology( NULL ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Add resources to this player's Bank +//----------------------------------------------------------------------------- +void CBaseTFPlayer::AddBankResources( int iAmount ) +{ + m_TFLocal.AddResources( iAmount ); + + // Tell the player's builder weapon to update + CWeaponBuilder *pBuilder = GetWeaponBuilder(); + if ( pBuilder ) + { + pBuilder->GainedNewTechnology( NULL ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Remove resources to this player's Bank +//----------------------------------------------------------------------------- +void CBaseTFPlayer::RemoveBankResources( int iAmount, bool bSpent ) +{ + m_TFLocal.RemoveResources( iAmount ); + + // Tell the player's builder weapon to update + CWeaponBuilder *pBuilder = GetWeaponBuilder(); + if ( pBuilder ) + { + pBuilder->GainedNewTechnology( NULL ); + } + + if (bSpent) + { + TFStats()->IncrementPlayerStat( this, TF_PLAYER_STAT_RESOURCES_SPENT, iAmount ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::IsCamouflaged( void ) +{ + return ( m_flCamouflageAmount > 0.0f ) ? true : false; +} + +//----------------------------------------------------------------------------- +// Purpose: Change state over time +//----------------------------------------------------------------------------- +void CBaseTFPlayer::CheckCamouflage( void ) +{ + if ( m_flCamouflageAmount == m_flGoalCamouflageAmount ) + return; + + float remaining = m_flGoalCamouflageAmount - m_flCamouflageAmount; + float maxstep = m_flGoalCamouflageChangeRate * gpGlobals->frametime; + + if ( remaining > 0.0f ) + { + m_flCamouflageAmount += MIN( remaining, maxstep ); + } + else + { + remaining = -remaining; + m_flCamouflageAmount -= MIN( remaining, maxstep ); + } + + m_flCamouflageAmount = MAX( 0.0f, m_flCamouflageAmount ); + m_flCamouflageAmount = MIN( 100.0f, m_flCamouflageAmount ); +} + +//----------------------------------------------------------------------------- +// Purpose: Goal % and rate in percent/second to achieve the goal +// Input : percentage - +// changerate - +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetCamouflaged( int percentage, float changerate ) +{ + m_flGoalCamouflageAmount = (float)percentage; + m_flGoalCamouflageChangeRate = changerate; +} + +//----------------------------------------------------------------------------- +// Purpose: Remove the player's camo +//----------------------------------------------------------------------------- +void CBaseTFPlayer::ClearCamouflage( void ) +{ + SetCamouflaged( 0, 1000 ); + + // Tell the playerclass + if ( GetPlayerClass() ) + { + GetPlayerClass()->ClearCamouflage(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Confirm powerup durations +//----------------------------------------------------------------------------- +float CBaseTFPlayer::PowerupDuration( int iPowerup, float flTime ) +{ + // Medics are never EMPed for long + if ( PlayerClass() == TFCLASS_MEDIC && iPowerup == POWERUP_EMP ) + return 0.2; + + return BaseClass::PowerupDuration( iPowerup, flTime ); +} + +//----------------------------------------------------------------------------- +// Purpose: Return the player's anim speed multiplier. Used for speeding up viewmodels while rushed. +//----------------------------------------------------------------------------- +float CBaseTFPlayer::GetDefaultAnimSpeed( void ) +{ + if ( HasPowerup( POWERUP_RUSH ) ) + return ADRENALIN_ANIM_SPEED; + + // Weapons may modify animation times + if ( GetActiveWeapon() ) + return GetActiveWeapon()->GetDefaultAnimSpeed(); + + return 1.0; +} + + +//----------------------------------------------------------------------------- +// Donate resources to a teammate +//----------------------------------------------------------------------------- +void CBaseTFPlayer::DonateResources( CBaseTFPlayer *pTarget, int pCount ) +{ + Assert( pTarget ); + + int nTotalCountDonated = 0; + int nDonationCount = GetBankResources(); + if ( pCount < nDonationCount ) + nDonationCount = pCount; + + if (nDonationCount) + { + RemoveBankResources( nDonationCount, false ); + pTarget->AddBankResources( nDonationCount ); + nTotalCountDonated += nDonationCount; + } + + if (nTotalCountDonated > 0) + { + char buf[1024]; + Q_snprintf( buf, sizeof( buf ), "%s has donated %d resources to you\n", + GetPlayerName(), nTotalCountDonated ); + ClientPrint( pTarget, HUD_PRINTCENTER, buf ); + + CSingleUserRecipientFilter filter( this ); + EmitSound( filter, entindex(), "BaseTFPlayer.DonateResources" ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Infilitrator's can +use a corpse to consume it +//----------------------------------------------------------------------------- +void CBaseTFPlayer::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pActivator->IsPlayer() ) + { + CBaseTFPlayer *pPlayer = static_cast<CBaseTFPlayer*>(pActivator); + + if ( InSameTeam( pActivator )) + { + // Resource donation + pPlayer->DonateResources( this, 25 ); + } + } + + BaseClass::Use( pActivator, pCaller, useType, value ); +} + + +//----------------------------------------------------------------------------- +// The player's usable... +//----------------------------------------------------------------------------- +int CBaseTFPlayer::ObjectCaps( void ) +{ + return ( (BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_IMPULSE_USE ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::CantMove( void ) +{ + return m_bCantMove; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetCantMove( bool bCantMove ) +{ + m_bCantMove = bCantMove; + RecalculateSpeed(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::ResetViewOffset( void ) +{ + if ( GetPlayerClass() ) + { + GetPlayerClass()->ResetViewOffset(); + } + else + { + SetViewOffset( VEC_VIEW_SCALED( this ) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: If ragdolling, move the player along the path that the ragdoll takes +//----------------------------------------------------------------------------- +void CBaseTFPlayer::FollowClientRagdoll( void ) +{ + if (( m_hRagdollShadow == NULL ) || ( GetPlayerClass() == NULL )) + return; + + Vector vecMin, vecMax; + GetPlayerClass()->GetPlayerHull( ( ( GetFlags() & FL_DUCKING ) == 1 ), vecMin, vecMax ); + + // Follow shadow object + trace_t tr; + + UTIL_TraceHull( + m_hRagdollShadow->GetAbsOrigin() + Vector(0,0,18), + m_hRagdollShadow->GetAbsOrigin(), + vecMin, + vecMax, + MASK_PLAYERSOLID, + m_hRagdollShadow, + COLLISION_GROUP_NONE, + &tr ); + + // Only move if we can find a valid spot under where shadow rolled + if ( !tr.allsolid ) + { + UTIL_SetOrigin( this, tr.endpos ); + VectorCopy( tr.endpos, m_vecLastGoodRagdollPos ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Stop being a ragdoll +// Input : moveplayertofinalspot - +// Output : return whether or not the ragdoll was cleared +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::ClearClientRagdoll( bool moveplayertofinalspot ) +{ + if ( m_hRagdollShadow ) + { + if ( GetContainingEntity( edict() ) ) + { + if ( moveplayertofinalspot ) + { + // Move player to resting spot of shadow object + FollowClientRagdoll(); + + // Check for a valid standing position. If an entity is blocking impart some + // velocity to them and check again. + trace_t trace; + if ( CheckRagdollToStand( trace ) ) + { + // Switch back to normal movement and kill off ragdoll bone setup on client + SetMoveType( MOVETYPE_WALK ); + m_nRenderFX = kRenderFxNone; + //RemoveSolidFlags( FSOLID_NOTSOLID ); + Assert( GetPlayerClass() != NULL ); + Vector vecMin, vecMax; + GetPlayerClass()->GetPlayerHull( ( ( GetFlags() & FL_DUCKING ) == 1 ), vecMin, vecMax ); + UTIL_SetSize( this, vecMin, vecMax ); + } + else + { + CBaseEntity *pEntity = trace.m_pEnt; + if ( pEntity != GetContainingEntity( INDEXENT( 0 ) ) ) + { + // Check for a physics object and apply force! + IPhysicsObject *pPhysObject = pEntity->VPhysicsGetObject(); + if ( pPhysObject ) + { + Vector vecDirection( random->RandomFloat( 0.0f, 1.0f ), + random->RandomFloat( 0.0f, 1.0f ), + random->RandomFloat( 0.0f, 1.0f ) ); + vecDirection *= 40000.0f; + pPhysObject->ApplyForceCenter( vecDirection ); + } + + return false; + } + else + { + UTIL_SetOrigin( this, Vector( m_vecLastGoodRagdollPos.x, m_vecLastGoodRagdollPos.y, m_vecLastGoodRagdollPos.z + 18.0f ) ); + return false; + } + } + } + } + // Kill the shadow object + UTIL_Remove( m_hRagdollShadow ); + m_hRagdollShadow = NULL; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::CheckRagdollToStand( trace_t &trace ) +{ + Assert( GetPlayerClass() != NULL ); + Vector vecMin, vecMax; + GetPlayerClass()->GetPlayerHull( ( ( GetFlags() & FL_DUCKING ) == 1 ), vecMin, vecMax ); + + // Write this better -- this is just a test to get things started. + UTIL_TraceHull( + m_vecLastGoodRagdollPos + Vector( 0, 0, 18 ), + m_vecLastGoodRagdollPos, + vecMin, + vecMax, + MASK_PLAYERSOLID, + m_hRagdollShadow, + COLLISION_GROUP_NONE, + &trace ); + + if ( !trace.allsolid ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Start being a ragdoll, creates client ragdoll object and server +// physics shadow object +// Input : &force - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::BecomeRagdollOnClient( const Vector &force ) +{ + // Defender doesn't support it yet + if ( PlayerClass() == TFCLASS_INFILTRATOR ) + return false; + + // Initialize the good ragdoll position. + VectorCopy( GetAbsOrigin(), m_vecLastGoodRagdollPos ); + + bool bret = BaseClass::BecomeRagdollOnClient( force ); + + // ROBIN: Disabled ragdoll shadows for now. + // We'll re-enable them if we need to know the end position again + // If we re-enable them, we need to fix the ragdoll shadow not having the correct mass + return bret; + + AddSolidFlags( FSOLID_NOT_SOLID ); + + // Clear any old shadow object ( should never occur ) + ClearClientRagdoll( false ); + + // Create new shadow object + m_hRagdollShadow = CRagdollShadow::Create( this, force ); + + return bret; +} + +//========================================================= +// AddGesture - add a gesture into the animation queue +//========================================================= +int CBaseTFPlayer::AddGesture( Activity activity, bool autokill /*= true*/ ) +{ + int layer = BaseClass::AddGesture( activity, autokill ); + SetLayerBlendIn( layer, 0.0 ); + SetLayerBlendOut( layer, 0.0 ); + return layer; +} + +//----------------------------------------------------------------------------- +// Purpose: Class specific touch functionality! +//----------------------------------------------------------------------------- +void CBaseTFPlayer::ClassTouch( CBaseEntity *pTouched ) +{ + if ( m_pfnClassTouch && HasClass() ) + { + (GetPlayerClass()->*m_pfnClassTouch)( pTouched ); + } +} + +const char* CBaseTFPlayer::GetClassModelString( int iClass, int iTeam ) +{ + return m_PlayerClasses.GetPlayerClass( iClass )->GetClassModelString( iTeam ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bRampage - +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetRampage( bool bRampage ) +{ + m_bRampage = bRampage; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseTFPlayer::IsInRampage( void ) +{ + return m_bRampage; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::SetPlayerClass( TFClass iClass ) +{ + if ( m_iPlayerClass != iClass ) + { + m_Timer.End(); + + if ( m_iPlayerClass >= 0 && m_iPlayerClass < TFCLASS_CLASS_COUNT ) + { + void AddPlayerClassTime( int classnum, float seconds ); + + AddPlayerClassTime( m_iPlayerClass, m_Timer.GetDuration().GetSeconds() ); + } + } + + if ( m_iPlayerClass >= 0 ) + { + if ( GetPlayerClass() ) + { + GetPlayerClass()->ClassDeactivate(); + } + } + + m_iPlayerClass = iClass; + + if ( m_iPlayerClass >= 0 ) + { + SetPlayerModel(); + + m_Timer.Start(); + + if ( GetPlayerClass() ) + { + GetPlayerClass()->ClassActivate(); + // Setup the class on initial spawn + GetPlayerClass()->CreateClass(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CBaseTFPlayer::ClassCostAdjustment( ResupplyBuyType_t nType ) +{ + if ( m_iPlayerClass >= 0 ) + { + return GetPlayerClass()->ClassCostAdjustment( nType ); + } + + return 0; +} + +//============================================================================= +// +// Player Physics Shadow Code +// +class CPhysicsTFPlayerCallback : public IPhysicsPlayerControllerEvent +{ +public: + int ShouldMoveTo( IPhysicsObject *pObject, const Vector &position ) + { + CBaseTFPlayer *pPlayer = ( CBaseTFPlayer* )pObject->GetGameData(); + if ( pPlayer->TouchedPhysics() ) + { + return 0; + } + return 1; + } +}; + +static CPhysicsTFPlayerCallback TFPlayerCallback; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseTFPlayer::InitVCollision( void ) +{ + if ( GetPlayerClass() ) + { + GetPlayerClass()->InitVCollision(); + } + + // Setup the HL2 specific callback. + if ( GetPhysicsController() ) + { + GetPhysicsController()->SetEventHandler( &TFPlayerCallback ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Return the entity that should receive the score +//----------------------------------------------------------------------------- +CBasePlayer *CBaseTFPlayer::GetScorer( void ) +{ + return this; +} + +//----------------------------------------------------------------------------- +// Purpose: Return the entity that should get assistance credit +//----------------------------------------------------------------------------- +CBasePlayer *CBaseTFPlayer::GetAssistant( void ) +{ + // If I'm in a vehicle, the builder gets credit + if ( IsInAVehicle() ) + { + CBaseObject *pObject = dynamic_cast<CBaseObject*>( GetVehicle() ); + if ( pObject ) + { + CBasePlayer *pBuilder = pObject->GetBuilder(); + if ( pBuilder && pBuilder != this ) + return pBuilder; + } + } + + // If I'm boosted, someone's getting the assist + if ( HasPowerup( POWERUP_BOOST ) && m_hLastBoostEntity.Get() ) + { + // I may have boosted myself + if ( m_hLastBoostEntity.Get() != this ) + { + if ( m_hLastBoostEntity->IsPlayer() ) + return (CBasePlayer*)m_hLastBoostEntity.Get(); + + // If it's an object, give the builder the assist (i.e. buff station) + CBaseObject *pObject = dynamic_cast<CBaseObject*>( m_hLastBoostEntity.Get() ); + if ( pObject ) + { + CBasePlayer *pBuilder = pObject->GetBuilder(); + if ( pBuilder && pBuilder != this ) + return pBuilder; + } + } + } + + return NULL; +} |