//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: TF2's player object. // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include #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 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(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( 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( m_hLastBoostEntity.Get() ); if ( pObject ) { CBasePlayer *pBuilder = pObject->GetBuilder(); if ( pBuilder && pBuilder != this ) return pBuilder; } } } return NULL; }