You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1115 lines
26 KiB
1115 lines
26 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: Player for HL1. |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#include "tfc_player.h" |
|
#include "tfc_gamerules.h" |
|
#include "KeyValues.h" |
|
#include "viewport_panel_names.h" |
|
#include "client.h" |
|
#include "team.h" |
|
#include "weapon_tfcbase.h" |
|
#include "tfc_client.h" |
|
#include "tfc_mapitems.h" |
|
#include "tfc_timer.h" |
|
#include "tfc_engineer.h" |
|
#include "tfc_team.h" |
|
|
|
|
|
#define TFC_PLAYER_MODEL "models/player/pyro.mdl" |
|
|
|
|
|
// -------------------------------------------------------------------------------- // |
|
// Player animation event. Sent to the client when a player fires, jumps, reloads, etc.. |
|
// -------------------------------------------------------------------------------- // |
|
|
|
class CTEPlayerAnimEvent : public CBaseTempEntity |
|
{ |
|
public: |
|
DECLARE_CLASS( CTEPlayerAnimEvent, CBaseTempEntity ); |
|
DECLARE_SERVERCLASS(); |
|
|
|
CTEPlayerAnimEvent( const char *name ) : CBaseTempEntity( name ) |
|
{ |
|
} |
|
|
|
CNetworkHandle( CBasePlayer, m_hPlayer ); |
|
CNetworkVar( int, m_iEvent ); |
|
CNetworkVar( int, m_nData ); |
|
}; |
|
|
|
IMPLEMENT_SERVERCLASS_ST_NOBASE( CTEPlayerAnimEvent, DT_TEPlayerAnimEvent ) |
|
SendPropEHandle( SENDINFO( m_hPlayer ) ), |
|
SendPropInt( SENDINFO( m_iEvent ), Q_log2( PLAYERANIMEVENT_COUNT ) + 1, SPROP_UNSIGNED ) |
|
SendPropInt( SENDINFO( m_nData ), 32 ) |
|
END_SEND_TABLE() |
|
|
|
static CTEPlayerAnimEvent g_TEPlayerAnimEvent( "PlayerAnimEvent" ); |
|
|
|
void TE_PlayerAnimEvent( CBasePlayer *pPlayer, PlayerAnimEvent_t event, int nData ) |
|
{ |
|
CPVSFilter filter( pPlayer->EyePosition() ); |
|
|
|
// The player himself doesn't need to be sent his animation events |
|
// unless cs_showanimstate wants to show them. |
|
if ( !ToolsEnabled() && ( cl_showanimstate.GetInt() == pPlayer->entindex() ) ) |
|
{ |
|
filter.RemoveRecipient( pPlayer ); |
|
} |
|
|
|
g_TEPlayerAnimEvent.m_hPlayer = pPlayer; |
|
g_TEPlayerAnimEvent.m_iEvent = event; |
|
g_TEPlayerAnimEvent.m_nData = nData; |
|
g_TEPlayerAnimEvent.Create( filter, 0 ); |
|
} |
|
|
|
|
|
// -------------------------------------------------------------------------------- // |
|
// Tables. |
|
// -------------------------------------------------------------------------------- // |
|
|
|
LINK_ENTITY_TO_CLASS( player, CTFCPlayer ); |
|
PRECACHE_REGISTER(player); |
|
|
|
IMPLEMENT_SERVERCLASS_ST( CTFCPlayer, DT_TFCPlayer ) |
|
SendPropExclude( "DT_BaseAnimating", "m_flPoseParameter" ), |
|
SendPropExclude( "DT_BaseAnimating", "m_flPlaybackRate" ), |
|
SendPropExclude( "DT_BaseAnimating", "m_nSequence" ), |
|
SendPropExclude( "DT_BaseEntity", "m_angRotation" ), |
|
SendPropExclude( "DT_BaseAnimatingOverlay", "overlay_vars" ), |
|
|
|
// cs_playeranimstate and clientside animation takes care of these on the client |
|
SendPropExclude( "DT_ServerAnimationData" , "m_flCycle" ), |
|
SendPropExclude( "DT_AnimTimeMustBeFirst" , "m_flAnimTime" ), |
|
|
|
SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 0), 11 ), |
|
SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 1), 11 ), |
|
|
|
SendPropDataTable( SENDINFO_DT( m_Shared ), &REFERENCE_SEND_TABLE( DT_TFCPlayerShared ) ) |
|
END_SEND_TABLE() |
|
|
|
|
|
// -------------------------------------------------------------------------------- // |
|
|
|
void cc_CreatePredictionError_f() |
|
{ |
|
CBaseEntity *pEnt = CBaseEntity::Instance( 1 ); |
|
pEnt->SetAbsOrigin( pEnt->GetAbsOrigin() + Vector( 63, 0, 0 ) ); |
|
} |
|
|
|
ConCommand cc_CreatePredictionError( "CreatePredictionError", cc_CreatePredictionError_f, "Create a prediction error", FCVAR_CHEAT ); |
|
|
|
|
|
CTFCPlayer::CTFCPlayer() |
|
{ |
|
m_PlayerAnimState = CreatePlayerAnimState( this ); |
|
item_list = 0; |
|
|
|
UseClientSideAnimation(); |
|
m_angEyeAngles.Init(); |
|
m_pCurStateInfo = NULL; |
|
m_lifeState = LIFE_DEAD; // Start "dead". |
|
|
|
SetViewOffset( TFC_PLAYER_VIEW_OFFSET ); |
|
|
|
SetContextThink( &CTFCPlayer::TFCPlayerThink, gpGlobals->curtime, "TFCPlayerThink" ); |
|
} |
|
|
|
|
|
void CTFCPlayer::TFCPlayerThink() |
|
{ |
|
if ( m_pCurStateInfo && m_pCurStateInfo->pfnThink ) |
|
(this->*m_pCurStateInfo->pfnThink)(); |
|
|
|
SetContextThink( &CTFCPlayer::TFCPlayerThink, gpGlobals->curtime, "TFCPlayerThink" ); |
|
} |
|
|
|
|
|
CTFCPlayer::~CTFCPlayer() |
|
{ |
|
m_PlayerAnimState->Release(); |
|
} |
|
|
|
|
|
CTFCPlayer *CTFCPlayer::CreatePlayer( const char *className, edict_t *ed ) |
|
{ |
|
CTFCPlayer::s_PlayerEdict = ed; |
|
return (CTFCPlayer*)CreateEntityByName( className ); |
|
} |
|
|
|
|
|
void CTFCPlayer::PostThink() |
|
{ |
|
BaseClass::PostThink(); |
|
|
|
QAngle angles = GetLocalAngles(); |
|
angles[PITCH] = 0; |
|
SetLocalAngles( angles ); |
|
|
|
// Store the eye angles pitch so the client can compute its animation state correctly. |
|
m_angEyeAngles = EyeAngles(); |
|
|
|
m_PlayerAnimState->Update( m_angEyeAngles[YAW], m_angEyeAngles[PITCH] ); |
|
} |
|
|
|
|
|
void CTFCPlayer::Precache() |
|
{ |
|
for ( int i=0; i < PC_LASTCLASS; i++ ) |
|
PrecacheModel( GetTFCClassInfo( i )->m_pModelName ); |
|
|
|
PrecacheScriptSound( "Player.Spawn" ); |
|
|
|
BaseClass::Precache(); |
|
} |
|
|
|
|
|
void CTFCPlayer::InitialSpawn( void ) |
|
{ |
|
BaseClass::InitialSpawn(); |
|
|
|
State_Enter( STATE_WELCOME ); |
|
} |
|
|
|
|
|
void CTFCPlayer::Spawn() |
|
{ |
|
SetModel( GetTFCClassInfo( m_Shared.GetPlayerClass() )->m_pModelName ); |
|
|
|
SetMoveType( MOVETYPE_WALK ); |
|
m_iLegDamage = 0; |
|
|
|
BaseClass::Spawn(); |
|
|
|
// Kind of lame, but CBasePlayer::Spawn resets a lot of the state that we initially want on. |
|
// So if we're in the welcome state, call its enter function to reset |
|
if ( m_Shared.State_Get() == STATE_WELCOME ) |
|
{ |
|
State_Enter_WELCOME(); |
|
} |
|
|
|
// If they were dead, then they're respawning. Put them in the active state. |
|
if ( m_Shared.State_Get() == STATE_DYING ) |
|
{ |
|
State_Transition( STATE_ACTIVE ); |
|
} |
|
|
|
// If they're spawning into the world as fresh meat, give them items and stuff. |
|
if ( m_Shared.State_Get() == STATE_ACTIVE ) |
|
{ |
|
EmitSound( "Player.Spawn" ); |
|
GiveDefaultItems(); |
|
} |
|
} |
|
|
|
|
|
void CTFCPlayer::ForceRespawn() |
|
{ |
|
//TFCTODO: goldsrc tfc has a big function for this.. doing what I'm doing here may not work. |
|
respawn( this, false ); |
|
} |
|
|
|
|
|
void CTFCPlayer::GiveDefaultItems() |
|
{ |
|
switch( m_Shared.GetPlayerClass() ) |
|
{ |
|
case PC_HWGUY: |
|
{ |
|
GiveNamedItem( "weapon_crowbar" ); |
|
|
|
GiveNamedItem( "weapon_minigun" ); |
|
GiveAmmo( 176, TFC_AMMO_SHELLS ); |
|
} |
|
break; |
|
|
|
case PC_PYRO: |
|
{ |
|
GiveNamedItem( "weapon_crowbar" ); |
|
} |
|
break; |
|
|
|
case PC_ENGINEER: |
|
{ |
|
GiveNamedItem( "weapon_spanner" ); |
|
GiveNamedItem( "weapon_super_shotgun" ); |
|
GiveAmmo( 20, TFC_AMMO_SHELLS ); |
|
} |
|
break; |
|
|
|
case PC_SCOUT: |
|
{ |
|
GiveNamedItem( "weapon_crowbar" ); |
|
GiveNamedItem( "weapon_shotgun" ); |
|
GiveNamedItem( "weapon_nailgun" ); |
|
GiveAmmo( 25, TFC_AMMO_SHELLS ); |
|
GiveAmmo( 100, TFC_AMMO_NAILS ); |
|
} |
|
break; |
|
|
|
case PC_SNIPER: |
|
{ |
|
GiveNamedItem( "weapon_crowbar" ); |
|
} |
|
break; |
|
|
|
case PC_SOLDIER: |
|
{ |
|
GiveNamedItem( "weapon_crowbar" ); |
|
} |
|
break; |
|
|
|
case PC_DEMOMAN: |
|
{ |
|
GiveNamedItem( "weapon_crowbar" ); |
|
} |
|
break; |
|
|
|
case PC_SPY: |
|
{ |
|
GiveNamedItem( "weapon_knife" ); |
|
} |
|
break; |
|
|
|
case PC_MEDIC: |
|
{ |
|
GiveNamedItem( "weapon_medikit" ); |
|
GiveNamedItem( "weapon_super_nailgun" ); |
|
GiveAmmo( 100, TFC_AMMO_NAILS ); |
|
} |
|
break; |
|
} |
|
} |
|
|
|
|
|
void CTFCPlayer::DoAnimationEvent( PlayerAnimEvent_t event, int nData ) |
|
{ |
|
m_PlayerAnimState->DoAnimationEvent( event, nData ); |
|
TE_PlayerAnimEvent( this, event, nData ); // Send to any clients who can see this guy. |
|
} |
|
|
|
|
|
void CTFCPlayer::State_Transition( TFCPlayerState newState ) |
|
{ |
|
State_Leave(); |
|
State_Enter( newState ); |
|
} |
|
|
|
|
|
void CTFCPlayer::State_Enter( TFCPlayerState newState ) |
|
{ |
|
m_Shared.m_iPlayerState = newState; |
|
m_pCurStateInfo = State_LookupInfo( newState ); |
|
|
|
// Initialize the new state. |
|
if ( m_pCurStateInfo && m_pCurStateInfo->pfnEnterState ) |
|
(this->*m_pCurStateInfo->pfnEnterState)(); |
|
} |
|
|
|
|
|
void CTFCPlayer::State_Leave() |
|
{ |
|
if ( m_pCurStateInfo && m_pCurStateInfo->pfnLeaveState ) |
|
{ |
|
(this->*m_pCurStateInfo->pfnLeaveState)(); |
|
} |
|
} |
|
|
|
|
|
CPlayerStateInfo* CTFCPlayer::State_LookupInfo( TFCPlayerState state ) |
|
{ |
|
// This table MUST match the |
|
static CPlayerStateInfo playerStateInfos[] = |
|
{ |
|
{ STATE_ACTIVE, "STATE_ACTIVE", &CTFCPlayer::State_Enter_ACTIVE, NULL, NULL }, |
|
{ STATE_WELCOME, "STATE_WELCOME", &CTFCPlayer::State_Enter_WELCOME, NULL, NULL }, |
|
{ STATE_PICKINGTEAM, "STATE_PICKINGTEAM", &CTFCPlayer::State_Enter_PICKINGTEAM, NULL, NULL }, |
|
{ STATE_PICKINGCLASS, "STATE_PICKINGCLASS", &CTFCPlayer::State_Enter_PICKINGCLASS, NULL, NULL }, |
|
{ STATE_OBSERVER_MODE, "STATE_OBSERVER_MODE", &CTFCPlayer::State_Enter_OBSERVER_MODE, NULL, NULL }, |
|
{ STATE_DYING, "STATE_DYING", &CTFCPlayer::State_Enter_DYING, NULL, NULL } |
|
}; |
|
|
|
for ( int i=0; i < ARRAYSIZE( playerStateInfos ); i++ ) |
|
{ |
|
if ( playerStateInfos[i].m_iPlayerState == state ) |
|
return &playerStateInfos[i]; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
|
|
void CTFCPlayer::State_Enter_WELCOME() |
|
{ |
|
SetMoveType( MOVETYPE_NONE ); |
|
AddEffects( EF_NODRAW ); |
|
AddSolidFlags( FSOLID_NOT_SOLID ); |
|
PhysObjectSleep(); |
|
|
|
// Show info panel (if it's not a simple demo map). |
|
KeyValues *data = new KeyValues("data"); |
|
data->SetString( "title", "Message of the Day" ); // info panel title |
|
data->SetString( "type", "3" ); // show a file |
|
data->SetString( "msg", "motd.txt" ); // this file |
|
data->SetString( "cmd", "joingame" ); // exec this command if panel closed |
|
|
|
ShowViewPortPanel( "info", true, data ); |
|
|
|
data->deleteThis(); |
|
} |
|
|
|
|
|
void CTFCPlayer::State_Enter_PICKINGTEAM() |
|
{ |
|
ShowViewPortPanel( PANEL_TEAM ); // show the team menu |
|
} |
|
|
|
|
|
void CTFCPlayer::State_Enter_PICKINGCLASS() |
|
{ |
|
// go to spec mode, if dying keep deathcam |
|
if ( GetObserverMode() == OBS_MODE_DEATHCAM ) |
|
{ |
|
StartObserverMode( OBS_MODE_DEATHCAM ); |
|
} |
|
else |
|
{ |
|
StartObserverMode( OBS_MODE_ROAMING ); |
|
} |
|
|
|
PhysObjectSleep(); |
|
|
|
// show the class menu: |
|
ShowViewPortPanel( PANEL_CLASS ); |
|
} |
|
|
|
|
|
void CTFCPlayer::State_Enter_OBSERVER_MODE() |
|
{ |
|
StartObserverMode( m_iObserverLastMode ); |
|
PhysObjectSleep(); |
|
} |
|
|
|
|
|
void CTFCPlayer::State_Enter_ACTIVE() |
|
{ |
|
SetMoveType( MOVETYPE_WALK ); |
|
RemoveEffects( EF_NODRAW ); |
|
RemoveSolidFlags( FSOLID_NOT_SOLID ); |
|
m_Local.m_iHideHUD = 0; |
|
PhysObjectWake(); |
|
} |
|
|
|
|
|
void CTFCPlayer::State_Enter_DYING() |
|
{ |
|
SetMoveType( MOVETYPE_NONE ); |
|
AddSolidFlags( FSOLID_NOT_SOLID ); |
|
} |
|
|
|
|
|
void CTFCPlayer::PhysObjectSleep() |
|
{ |
|
IPhysicsObject *pObj = VPhysicsGetObject(); |
|
if ( pObj ) |
|
pObj->Sleep(); |
|
} |
|
|
|
|
|
void CTFCPlayer::PhysObjectWake() |
|
{ |
|
IPhysicsObject *pObj = VPhysicsGetObject(); |
|
if ( pObj ) |
|
pObj->Wake(); |
|
} |
|
|
|
|
|
void CTFCPlayer::HandleCommand_JoinTeam( const char *pTeamName ) |
|
{ |
|
int iTeam = TEAM_RED; |
|
if ( stricmp( pTeamName, "auto" ) == 0 ) |
|
{ |
|
iTeam = RandomInt( 0, 1 ) ? TEAM_RED : TEAM_BLUE; |
|
} |
|
else if ( stricmp( pTeamName, "spectate" ) == 0 ) |
|
{ |
|
iTeam = TEAM_SPECTATOR; |
|
} |
|
else |
|
{ |
|
for ( int i=0; i < TEAM_MAXCOUNT; i++ ) |
|
{ |
|
if ( stricmp( pTeamName, teamnames[i] ) == 0 ) |
|
{ |
|
iTeam = i; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
if ( iTeam == TEAM_SPECTATOR ) |
|
{ |
|
// Prevent this is the cvar is set |
|
if ( !mp_allowspectators.GetInt() && !IsHLTV() ) |
|
{ |
|
ClientPrint( this, HUD_PRINTCENTER, "#Cannot_Be_Spectator" ); |
|
return; |
|
} |
|
|
|
if ( GetTeamNumber() != TEAM_UNASSIGNED && !IsDead() ) |
|
{ |
|
CommitSuicide(); |
|
|
|
// add 1 to frags to balance out the 1 subtracted for killing yourself |
|
IncrementFragCount( 1 ); |
|
} |
|
|
|
ChangeTeam( TEAM_SPECTATOR ); |
|
|
|
// do we have fadetoblack on? (need to fade their screen back in) |
|
if ( mp_fadetoblack.GetInt() ) |
|
{ |
|
color32_s clr = { 0,0,0,0 }; |
|
UTIL_ScreenFade( this, clr, 0.001, 0, FFADE_IN ); |
|
} |
|
} |
|
else |
|
{ |
|
ChangeTeam( iTeam ); |
|
State_Transition( STATE_PICKINGCLASS ); |
|
} |
|
} |
|
|
|
|
|
void CTFCPlayer::ChangeTeam( int iTeamNum ) |
|
{ |
|
if ( !GetGlobalTeam( iTeamNum ) ) |
|
{ |
|
Warning( "CCSPlayer::ChangeTeam( %d ) - invalid team index.\n", iTeamNum ); |
|
return; |
|
} |
|
|
|
int iOldTeam = GetTeamNumber(); |
|
|
|
// if this is our current team, just abort |
|
if ( iTeamNum == iOldTeam ) |
|
return; |
|
|
|
BaseClass::ChangeTeam( iTeamNum ); |
|
|
|
if ( iTeamNum == TEAM_UNASSIGNED ) |
|
{ |
|
State_Transition( STATE_OBSERVER_MODE ); |
|
} |
|
else if ( iTeamNum == TEAM_SPECTATOR ) |
|
{ |
|
State_Transition( STATE_OBSERVER_MODE ); |
|
} |
|
else // active player |
|
{ |
|
if ( iOldTeam == TEAM_SPECTATOR ) |
|
{ |
|
// If they're switching from being a spectator to ingame player |
|
GetIntoGame(); |
|
} |
|
|
|
if ( !IsDead() && iOldTeam != TEAM_UNASSIGNED ) |
|
{ |
|
// Kill player if switching teams while alive |
|
CommitSuicide(); |
|
} |
|
|
|
// Put up the class selection menu. |
|
State_Transition( STATE_PICKINGCLASS ); |
|
} |
|
} |
|
|
|
|
|
void CTFCPlayer::HandleCommand_JoinClass( const char *pClassName ) |
|
{ |
|
int iClass = RandomInt( 0, PC_LAST_NORMAL_CLASS ); |
|
if ( stricmp( pClassName, "random" ) != 0 ) |
|
{ |
|
for ( int i=0; i < PC_LASTCLASS; i++ ) |
|
{ |
|
if ( stricmp( pClassName, GetTFCClassInfo( i )->m_pClassName ) == 0 ) |
|
{ |
|
iClass = i; |
|
break; |
|
} |
|
} |
|
if ( i == PC_LAST_NORMAL_CLASS ) |
|
{ |
|
Warning( "HandleCommand_JoinClass( %s ) - invalid class name.\n", pClassName ); |
|
} |
|
} |
|
|
|
m_Shared.SetPlayerClass( iClass ); |
|
|
|
if ( !IsAlive() ) |
|
GetIntoGame(); |
|
} |
|
|
|
|
|
void CTFCPlayer::GetIntoGame() |
|
{ |
|
State_Transition( STATE_ACTIVE ); |
|
Spawn(); |
|
} |
|
|
|
|
|
bool CTFCPlayer::ClientCommand( const CCommand& args ) |
|
{ |
|
const char *pcmd = args[0]; |
|
if ( FStrEq( pcmd, "joingame" ) ) |
|
{ |
|
// player just closed MOTD dialog |
|
if ( m_Shared.m_iPlayerState == STATE_WELCOME ) |
|
{ |
|
State_Transition( STATE_PICKINGTEAM ); |
|
} |
|
return true; |
|
} |
|
else if ( FStrEq( pcmd, "jointeam" ) ) |
|
{ |
|
if ( args.ArgC() >= 2 ) |
|
{ |
|
HandleCommand_JoinTeam( args[1] ); |
|
return true; |
|
} |
|
} |
|
else if ( FStrEq( pcmd, "joinclass" ) ) |
|
{ |
|
if ( args.ArgC() < 2 ) |
|
{ |
|
Warning( "Player sent bad joinclass syntax\n" ); |
|
} |
|
|
|
HandleCommand_JoinClass( args[1] ); |
|
return true; |
|
} |
|
|
|
return BaseClass::ClientCommand( args ); |
|
} |
|
|
|
|
|
bool CTFCPlayer::IsAlly( CBaseEntity *pEnt ) const |
|
{ |
|
return pEnt->GetTeamNumber() == GetTeamNumber(); |
|
} |
|
|
|
|
|
void CTFCPlayer::TF_AddFrags( int nFrags ) |
|
{ |
|
// TFCTODO: implement frags |
|
} |
|
|
|
|
|
void CTFCPlayer::ResetMenu() |
|
{ |
|
current_menu = 0; |
|
} |
|
|
|
|
|
int CTFCPlayer::GetNumFlames() const |
|
{ |
|
// TFCTODO: implement flames |
|
return 0; |
|
} |
|
|
|
|
|
void CTFCPlayer::SetNumFlames( int nFlames ) |
|
{ |
|
// TFCTODO: implement frags |
|
Assert( 0 ); |
|
} |
|
|
|
int CTFCPlayer::TakeHealth( float flHealth, int bitsDamageType ) |
|
{ |
|
int bResult = false; |
|
|
|
// If the bit's set, ignore the monster's max health and add over it |
|
if ( bitsDamageType & DMG_IGNORE_MAXHEALTH ) |
|
{ |
|
int iDamage = g_pGameRules->Damage_GetTimeBased(); |
|
m_bitsDamageType &= ~(bitsDamageType & ~iDamage); |
|
m_iHealth += flHealth; |
|
bResult = true; |
|
} |
|
else |
|
{ |
|
bResult = BaseClass::TakeHealth( flHealth, bitsDamageType ); |
|
} |
|
|
|
// Leg Healing |
|
if (m_iLegDamage > 0) |
|
{ |
|
// Allow even at full health |
|
if ( GetHealth() >= (GetMaxHealth() - 5)) |
|
m_iLegDamage = 0; |
|
else |
|
m_iLegDamage -= (GetHealth() + flHealth) / 20; |
|
if (m_iLegDamage < 1) |
|
m_iLegDamage = 0; |
|
|
|
TeamFortress_SetSpeed(); |
|
bResult = true; |
|
} |
|
|
|
return bResult; |
|
} |
|
|
|
|
|
void CTFCPlayer::TeamFortress_SetSpeed() |
|
{ |
|
int playerclass = m_Shared.GetPlayerClass(); |
|
float maxfbspeed; |
|
|
|
// Spectators can move while in Classic Observer mode |
|
if ( IsObserver() ) |
|
{ |
|
if ( GetObserverMode() == OBS_MODE_ROAMING ) |
|
SetMaxSpeed( GetTFCClassInfo( PC_SCOUT )->m_flMaxSpeed ); |
|
else |
|
SetMaxSpeed( 0 ); |
|
|
|
return; |
|
} |
|
|
|
// Check for any reason why they can't move at all |
|
if ( (m_Shared.GetStateFlags() & TFSTATE_CANT_MOVE) || (playerclass == PC_UNDEFINED) ) |
|
{ |
|
SetAbsVelocity( vec3_origin ); |
|
SetMaxSpeed( 1 ); |
|
return; |
|
} |
|
|
|
// First, get their max class speed |
|
maxfbspeed = GetTFCClassInfo( playerclass )->m_flMaxSpeed; |
|
|
|
// 2nd, see if any GoalItems are slowing them down |
|
CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "item_tfgoal" ); |
|
while ( pEnt ) |
|
{ |
|
CTFGoal *pGoal = dynamic_cast<CTFGoal*>( pEnt ); |
|
if ( pGoal ) |
|
{ |
|
if ( pGoal->GetOwnerEntity() == this ) |
|
{ |
|
if (pGoal->goal_activation & TFGI_SLOW) |
|
{ |
|
maxfbspeed = maxfbspeed / 2; |
|
} |
|
else if (pGoal->speed_reduction) |
|
{ |
|
float flPercent = ((float)pGoal->speed_reduction) / 100.0; |
|
maxfbspeed = flPercent * maxfbspeed; |
|
} |
|
} |
|
} |
|
|
|
pEnt = gEntList.FindEntityByClassname( pEnt, "item_tfgoal" ); |
|
} |
|
|
|
// 3rd, See if they're tranquilised |
|
if (m_Shared.GetStateFlags() & TFSTATE_TRANQUILISED) |
|
{ |
|
maxfbspeed = maxfbspeed / 2; |
|
} |
|
|
|
// 4th, check for leg wounds |
|
if (m_iLegDamage) |
|
{ |
|
if (m_iLegDamage > 6) |
|
m_iLegDamage = 6; |
|
|
|
// reduce speed by 10% per leg wound |
|
maxfbspeed = (maxfbspeed * ((10 - m_iLegDamage) / 10)); |
|
} |
|
|
|
// 5th, if they're a sniper, and they're aiming, their speed must be 80 or less |
|
if (m_Shared.GetStateFlags() & TFSTATE_AIMING) |
|
{ |
|
if (maxfbspeed > 80) |
|
maxfbspeed = 80; |
|
} |
|
|
|
// Set the speed |
|
SetMaxSpeed( maxfbspeed ); |
|
} |
|
|
|
|
|
void CTFCPlayer::Event_Killed( const CTakeDamageInfo &info ) |
|
{ |
|
DoAnimationEvent( PLAYERANIMEVENT_DIE ); |
|
State_Transition( STATE_DYING ); // Transition into the dying state. |
|
|
|
// Remove all items.. |
|
RemoveAllItems( true ); |
|
|
|
BaseClass::Event_Killed( info ); |
|
|
|
// Don't overflow the value for this. |
|
m_iHealth = 0; |
|
} |
|
|
|
|
|
void CTFCPlayer::ClientHearVox( const char *pSentence ) |
|
{ |
|
//TFCTODO: implement this. |
|
} |
|
|
|
|
|
//========================================================================= |
|
// Check all stats to make sure they're good for this class |
|
void CTFCPlayer::TeamFortress_CheckClassStats() |
|
{ |
|
// Check armor |
|
if (armortype > armor_allowed) |
|
armortype = armor_allowed; |
|
|
|
if (ArmorValue() > GetClassInfo()->m_iMaxArmor) |
|
SetArmorValue( GetClassInfo()->m_iMaxArmor ); |
|
|
|
if (ArmorValue() < 0) |
|
SetArmorValue( 0 ); |
|
|
|
if (armortype < 0) |
|
armortype = 0; |
|
|
|
// Check ammo |
|
for ( int iAmmoType=0; iAmmoType < TFC_NUM_AMMO_TYPES; iAmmoType++ ) |
|
{ |
|
if ( GetAmmoCount( iAmmoType ) > GetClassInfo()->m_MaxAmmo[iAmmoType] ) |
|
RemoveAmmo( GetAmmoCount( iAmmoType ) - GetClassInfo()->m_MaxAmmo[iAmmoType], iAmmoType ); |
|
} |
|
|
|
// Check Grenades |
|
Assert( GetAmmoCount( TFC_AMMO_GRENADES1 ) >= 0 ); |
|
Assert( GetAmmoCount( TFC_AMMO_GRENADES2 ) >= 0 ); |
|
|
|
// Limit Nails |
|
if ( no_grenades_1() > g_nMaxGrenades[tp_grenades_1()] ) |
|
RemoveAmmo( TFC_AMMO_GRENADES1, no_grenades_1() - g_nMaxGrenades[tp_grenades_1()] ); |
|
|
|
if ( no_grenades_2() > g_nMaxGrenades[tp_grenades_2()] ) |
|
RemoveAmmo( TFC_AMMO_GRENADES2, no_grenades_2() - g_nMaxGrenades[tp_grenades_2()] ); |
|
|
|
// Check health |
|
if (GetHealth() > GetMaxHealth() && !(m_Shared.GetItemFlags() & IT_SUPERHEALTH)) |
|
SetHealth( GetMaxHealth() ); |
|
|
|
if (GetHealth() < 0) |
|
SetHealth( 0 ); |
|
|
|
// Update armor picture |
|
m_Shared.RemoveItemFlags( IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3 ); |
|
if (armortype >= 0.8) |
|
m_Shared.AddItemFlags( IT_ARMOR3 ); |
|
else if (armortype >= 0.6) |
|
m_Shared.AddItemFlags( IT_ARMOR2 ); |
|
else if (armortype >= 0.3) |
|
m_Shared.AddItemFlags( IT_ARMOR1 ); |
|
} |
|
|
|
|
|
//====================================================================== |
|
// DISGUISE HANDLING |
|
//====================================================================== |
|
// Reset spy skin and color or remove invisibility |
|
void CTFCPlayer::Spy_RemoveDisguise() |
|
{ |
|
if (m_Shared.GetPlayerClass() == PC_SPY) |
|
{ |
|
if ( undercover_team || undercover_skin ) |
|
ClientPrint( this, HUD_PRINTCENTER, "#Disguise_Lost" ); |
|
|
|
// Set their color |
|
undercover_team = 0; |
|
undercover_skin = 0; |
|
|
|
immune_to_check = gpGlobals->curtime + 10; |
|
is_undercover = 0; |
|
|
|
// undisguise weapon |
|
TeamFortress_SetSkin(); |
|
TeamFortress_SpyCalcName(); |
|
|
|
Spy_ResetExternalWeaponModel(); |
|
|
|
// get them out of any disguise menus |
|
if ( current_menu == MENU_SPY || current_menu == MENU_SPY_SKIN || current_menu == MENU_SPY_COLOR ) |
|
{ |
|
ResetMenu(); |
|
} |
|
|
|
// Remove the Disguise timer |
|
CTimer *pTimer = Timer_FindTimer( this, TF_TIMER_DISGUISE ); |
|
if (pTimer) |
|
{ |
|
ClientPrint( this, HUD_PRINTCENTER, "#Disguise_stop" ); |
|
Timer_Remove( pTimer ); |
|
} |
|
} |
|
} |
|
|
|
|
|
// when the spy loses disguise reset his weapon |
|
void CTFCPlayer::Spy_ResetExternalWeaponModel( void ) |
|
{ |
|
// we don't show any weapon models if we're feigning |
|
if ( is_feigning ) |
|
return; |
|
|
|
#ifdef TFCTODO // spy |
|
pev->weaponmodel = MAKE_STRING( m_pszSavedWeaponModel ); |
|
strcpy( m_szAnimExtention, m_szSavedAnimExtention ); |
|
m_iCurrentAnimationState = 0; // force the current animation sequence to be recalculated |
|
#endif |
|
} |
|
|
|
|
|
//========================================================================= |
|
// Try and find the player's name who's skin and team closest fit the |
|
// current disguise of the spy |
|
void CTFCPlayer::TeamFortress_SpyCalcName() |
|
{ |
|
CBaseEntity *last_target = undercover_target;// don't redisguise self as this person |
|
|
|
undercover_target = NULL; |
|
|
|
// Find a player on the team the spy is disguised as to pretend to be |
|
if (undercover_team != 0) |
|
{ |
|
CTFCPlayer *pPlayer = NULL; |
|
|
|
// Loop through players |
|
int i; |
|
for ( i = 1; i <= gpGlobals->maxClients; i++ ) |
|
{ |
|
pPlayer = ToTFCPlayer( UTIL_PlayerByIndex( i ) ); |
|
if ( pPlayer ) |
|
{ |
|
if ( pPlayer == last_target ) |
|
{ |
|
// choose someone else, we're trying to rid ourselves of a disguise as this one |
|
continue; |
|
} |
|
|
|
// First, try to find a player with same color and skins |
|
if (pPlayer->GetTeamNumber() == undercover_team && pPlayer->m_Shared.GetPlayerClass() == undercover_skin) |
|
{ |
|
undercover_target = pPlayer; |
|
return; |
|
} |
|
} |
|
} |
|
|
|
for ( i = 1; i <= gpGlobals->maxClients; i++ ) |
|
{ |
|
pPlayer = ToTFCPlayer( UTIL_PlayerByIndex( i ) ); |
|
|
|
if ( pPlayer ) |
|
{ |
|
if (pPlayer->GetTeamNumber() == undercover_team) |
|
{ |
|
undercover_target = pPlayer; |
|
return; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
//========================================================================= |
|
// Set the skin of a player based on his/her class |
|
void CTFCPlayer::TeamFortress_SetSkin() |
|
{ |
|
immune_to_check = gpGlobals->curtime + 10; |
|
|
|
// Find out whether we should show our actual class or a disguised class |
|
int iClassToUse = m_Shared.GetPlayerClass(); |
|
if (iClassToUse == PC_SPY && undercover_skin != 0) |
|
iClassToUse = undercover_skin; |
|
|
|
int iTeamToUse = GetTeamNumber(); |
|
if (m_Shared.GetPlayerClass() == PC_SPY && undercover_team != 0) |
|
iTeamToUse = undercover_team; |
|
|
|
// TFCTODO: handle replacement_model here. |
|
|
|
SetModel( GetTFCClassInfo( iClassToUse )->m_pModelName ); |
|
|
|
// Skins in the models should be setup using the team IDs in tfc_shareddefs.h, subtracting 1 |
|
// so they're 0-based. |
|
m_nSkin = iTeamToUse - 1; |
|
|
|
if ( FBitSet(GetFlags(), FL_DUCKING) ) |
|
UTIL_SetSize(this, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); |
|
else |
|
UTIL_SetSize(this, VEC_HULL_MIN, VEC_HULL_MAX); |
|
} |
|
|
|
|
|
|
|
//========================================================================= |
|
// Displays the state of the items specified by the Goal passed in |
|
void CTFCPlayer::DisplayLocalItemStatus( CTFGoal *pGoal ) |
|
{ |
|
for (int i = 0; i < 4; i++) |
|
{ |
|
if (pGoal->display_item_status[i] != 0) |
|
{ |
|
CTFGoalItem *pItem = Finditem(pGoal->display_item_status[i]); |
|
if (pItem) |
|
DisplayItemStatus(pGoal, this, pItem); |
|
else |
|
ClientPrint( this, HUD_PRINTTALK, "#Item_missing" ); |
|
} |
|
} |
|
} |
|
|
|
|
|
//========================================================================= |
|
// Removes all the Engineer's buildings |
|
void CTFCPlayer::Engineer_RemoveBuildings() |
|
{ |
|
// If the player's building already, stop |
|
if (is_building == 1) |
|
{ |
|
m_Shared.RemoveStateFlags( TFSTATE_CANT_MOVE ); |
|
TeamFortress_SetSpeed(); |
|
|
|
// Remove the timer |
|
CTimer *pTimer = Timer_FindTimer( this, TF_TIMER_BUILD ); |
|
if (pTimer) |
|
Timer_Remove(pTimer); |
|
|
|
// Remove the building |
|
UTIL_Remove( building ); |
|
building = NULL; |
|
is_building = 0; |
|
|
|
// Stop Build Sound |
|
StopSound( "Engineer.Building" ); |
|
//STOP_SOUND( ENT(pev), CHAN_STATIC, "weapons/building.wav" ); |
|
|
|
if ( GetActiveWeapon() ) |
|
GetActiveWeapon()->Deploy(); |
|
} |
|
|
|
DestroyBuilding(this, "building_dispenser"); |
|
DestroyBuilding(this, "building_sentrygun"); |
|
DestroyTeleporter(this, BUILD_TELEPORTER_ENTRY); |
|
DestroyTeleporter(this, BUILD_TELEPORTER_EXIT); |
|
} |
|
|
|
|
|
//========================================================================= |
|
// Removes all grenades that persist for a period of time from the world |
|
void CTFCPlayer::TeamFortress_RemoveLiveGrenades( void ) |
|
{ |
|
RemoveOwnedEnt( "tf_weapon_napalmgrenade" ); |
|
RemoveOwnedEnt( "tf_weapon_nailgrenade" ); |
|
RemoveOwnedEnt( "tf_weapon_gasgrenade" ); |
|
RemoveOwnedEnt( "tf_weapon_caltrop" ); |
|
} |
|
|
|
|
|
//========================================================================= |
|
// Removes all rockets the player has fired into the world |
|
// (this prevents a team kill cheat where players would fire rockets |
|
// then change teams to kill their own team) |
|
void CTFCPlayer::TeamFortress_RemoveRockets( void ) |
|
{ |
|
RemoveOwnedEnt( "tf_rpg_rocket" ); |
|
RemoveOwnedEnt( "tf_ic_rocket" ); |
|
} |
|
|
|
// removes the player's pipebombs with no explosions |
|
void CTFCPlayer::RemovePipebombs( void ) |
|
{ |
|
CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "tf_gl_pipebomb" ); |
|
while ( pEnt ) |
|
{ |
|
CTFCPlayer *pOwner = ToTFCPlayer( pEnt->GetOwnerEntity() ); |
|
if ( pOwner == this ) |
|
{ |
|
pOwner->m_iPipebombCount--; |
|
pEnt->AddFlag( FL_KILLME ); |
|
} |
|
|
|
pEnt = gEntList.FindEntityByClassname( pEnt, "tf_gl_pipebomb" ); |
|
} |
|
} |
|
|
|
|
|
//========================================================================= |
|
// Stops the setting of the detpack |
|
void CTFCPlayer::TeamFortress_DetpackStop( void ) |
|
{ |
|
CTimer *pTimer = Timer_FindTimer( this, TF_TIMER_DETPACKSET ); |
|
|
|
if (!pTimer) |
|
return; |
|
|
|
ClientPrint( this, HUD_PRINTNOTIFY, "#Detpack_retrieve" ); |
|
|
|
// Return the detpack |
|
GiveAmmo( 1, TFC_AMMO_DETPACK ); |
|
Timer_Remove(pTimer); |
|
|
|
// Release player |
|
m_Shared.RemoveStateFlags( TFSTATE_CANT_MOVE ); |
|
is_detpacking = 0; |
|
TeamFortress_SetSpeed(); |
|
|
|
// Return their weapon |
|
if ( GetActiveWeapon() ) |
|
GetActiveWeapon()->Deploy(); |
|
} |
|
|
|
|
|
//========================================================================= |
|
// Removes any detpacks the player may have set |
|
BOOL CTFCPlayer::TeamFortress_RemoveDetpacks( void ) |
|
{ |
|
// Remove all detpacks owned by the player |
|
CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "detpack" ); |
|
while ( pEnt ) |
|
{ |
|
// if the player owns this detpack, remove it |
|
if ( pEnt->GetOwnerEntity() == this ) |
|
{ |
|
UTIL_Remove( pEnt ); |
|
return TRUE; |
|
} |
|
|
|
pEnt = gEntList.FindEntityByClassname( pEnt, "detpack" ); |
|
} |
|
|
|
return FALSE; |
|
} |
|
|
|
|
|
//========================================================================= |
|
// Remove all of an ent owned by this player |
|
void CTFCPlayer::RemoveOwnedEnt( char *pEntName ) |
|
{ |
|
CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, pEntName ); |
|
while ( pEnt ) |
|
{ |
|
// if the player owns this entity, remove it |
|
if ( pEnt->GetOwnerEntity() == this ) |
|
pEnt->AddFlag( FL_KILLME ); |
|
|
|
pEnt = gEntList.FindEntityByClassname( pEnt, pEntName ); |
|
} |
|
} |
|
|
|
|
|
|