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 );
|
|
}
|
|
}
|
|
|
|
|