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.
1172 lines
35 KiB
1172 lines
35 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
// |
|
//=============================================================================// |
|
/* |
|
|
|
===== tf_playerclass.cpp ======================================================== |
|
|
|
functions dealing with the TF playerclasses |
|
|
|
*/ |
|
#include "cbase.h" |
|
#include "player.h" |
|
#include "basecombatweapon.h" |
|
#include "tf_player.h" |
|
#include "tf_obj.h" |
|
#include "weapon_builder.h" |
|
#include "tf_team.h" |
|
#include "tf_func_resource.h" |
|
#include "orders.h" |
|
#include "order_repair.h" |
|
#include "engine/IEngineSound.h" |
|
#include "tier1/strtools.h" |
|
#include "textstatsmgr.h" |
|
#include "ammodef.h" |
|
#include "weapon_objectselection.h" |
|
#include "vcollide_parse.h" |
|
#include "weapon_combatshield.h" |
|
#include "tf_vehicle_tank.h" |
|
#include "tf_obj_manned_plasmagun.h" |
|
#include "tf_obj_manned_missilelauncher.h" |
|
|
|
extern char *g_pszEMPPulseStart; |
|
extern ConVar tf_fastbuild; |
|
|
|
|
|
// Stat groupings |
|
enum |
|
{ |
|
STATS_TANK = TFCLASS_CLASS_COUNT, |
|
STATS_MANNEDGUN_PLASMA, |
|
STATS_MANNEDGUN_ROCKET, |
|
|
|
STATS_NUM_GROUPS, |
|
}; |
|
|
|
char *sNonClassStatNames[] = |
|
{ |
|
"Tanks", |
|
"Manned Plasmaguns", |
|
"Manned Rocket launchers", |
|
}; |
|
|
|
// Stats between player classes (like how many snipers killed recons). |
|
class CInterClassStats |
|
{ |
|
public: |
|
|
|
CInterClassStats() |
|
{ |
|
m_flTotalDamageInflicted = 0; |
|
m_nKills = 0; |
|
|
|
m_flTotalEngagementDist = 0; |
|
m_nEngagements = 0; |
|
|
|
m_flTotalNormalizedEngagementDist = 0; |
|
m_nNormalizedEngagements = 0; |
|
} |
|
|
|
public: |
|
// |
|
// Note: "containing class" refers to the class in the CPlayerClassStats that owns this CInterClassStats. |
|
// So if there's a CPlayerClassStats for a recon, and it has a CInterClassStats for a sniper, |
|
// then "containing class" refers to the recon. In this example, m_flTotalDamageInflicted representss |
|
// how much damage the recon players inflicted on the snipers. |
|
// |
|
double m_flTotalDamageInflicted; // Total damage inflicted on this class by the containing class. |
|
double m_flTotalEngagementDist; // Used to get the average engagement distance. |
|
int m_nEngagements; |
|
|
|
double m_flTotalNormalizedEngagementDist; |
|
int m_nNormalizedEngagements; |
|
|
|
int m_nKills; // Now many times the containing class killed this one. |
|
}; |
|
|
|
class CPlayerClassStats |
|
{ |
|
public: |
|
CPlayerClassStats() |
|
{ |
|
m_flPlayerTime = 0; |
|
} |
|
|
|
public: |
|
|
|
CInterClassStats m_InterClassStats[STATS_NUM_GROUPS]; |
|
double m_flPlayerTime; // How much player time was spent in this class. |
|
}; |
|
|
|
|
|
ConVar tf2_object_hard_limits( "tf2_object_hard_limits","0", FCVAR_NONE, "If true, use hard object limits instead of resource costs" ); |
|
|
|
CPlayerClassStats g_PlayerClassStats[STATS_NUM_GROUPS]; |
|
|
|
void AddPlayerClassTime( int classnum, float seconds ) |
|
{ |
|
g_PlayerClassStats[ classnum ].m_flPlayerTime += seconds; |
|
} |
|
|
|
int GetStatGroupFor( CBaseTFPlayer *pPlayer ) |
|
{ |
|
// In a vehicle? |
|
if ( pPlayer->IsInAVehicle() ) |
|
{ |
|
if ( dynamic_cast<CVehicleTank*>( pPlayer->GetVehicle() ) ) |
|
return STATS_TANK; |
|
if ( dynamic_cast<CObjectMannedMissileLauncher*>( pPlayer->GetVehicle() ) ) |
|
return STATS_MANNEDGUN_ROCKET; |
|
if ( dynamic_cast<CObjectMannedPlasmagun*>( pPlayer->GetVehicle() ) ) |
|
return STATS_MANNEDGUN_PLASMA; |
|
} |
|
|
|
// Otherwise, use the playerclass |
|
return pPlayer->GetPlayerClass()->GetTFClass(); |
|
} |
|
|
|
const char* GetGroupNameFor( int iStatGroup ) |
|
{ |
|
Assert( iStatGroup > TFCLASS_UNDECIDED && iStatGroup < STATS_NUM_GROUPS ); |
|
if ( iStatGroup < TFCLASS_CLASS_COUNT ) |
|
return GetTFClassInfo( iStatGroup )->m_pClassName; |
|
else |
|
return sNonClassStatNames[ iStatGroup - TFCLASS_CLASS_COUNT ]; |
|
} |
|
|
|
|
|
void PrintPlayerClassStats() |
|
{ |
|
IFileSystem *pFileSys = filesystem; |
|
|
|
FileHandle_t hFile = pFileSys->Open( "class_stats.txt", "wt", "LOGDIR" ); |
|
if ( hFile == FILESYSTEM_INVALID_HANDLE ) |
|
return; |
|
|
|
|
|
pFileSys->FPrintf( hFile, "Class\tPlayer Time (minutes)\tAvg Engagement Dist\t(OLD) Engagement Dist\n" ); |
|
for ( int i=TFCLASS_UNDECIDED+1; i < STATS_NUM_GROUPS; i++ ) |
|
{ |
|
CPlayerClassStats *pStats = &g_PlayerClassStats[i]; |
|
|
|
|
|
// Figure out the average engagement distance across all classes. |
|
int j; |
|
double flAvgEngagementDist = 0; |
|
int nTotalEngagements = 0; |
|
double flTotalEngagementDist = 0; |
|
|
|
double flTotalNormalizedEngagementDist = 0; |
|
int nTotalNormalizedEngagements = 0; |
|
|
|
for ( j=TFCLASS_UNDECIDED+1; j < STATS_NUM_GROUPS; j++ ) |
|
{ |
|
CInterClassStats *pInter = &g_PlayerClassStats[i].m_InterClassStats[j]; |
|
|
|
nTotalEngagements += pInter->m_nEngagements; |
|
flTotalEngagementDist += pInter->m_flTotalEngagementDist; |
|
|
|
nTotalNormalizedEngagements += pInter->m_nNormalizedEngagements; |
|
flTotalNormalizedEngagementDist += pInter->m_flTotalNormalizedEngagementDist; |
|
} |
|
flAvgEngagementDist = nTotalEngagements ? ( flTotalEngagementDist / nTotalEngagements ) : 0; |
|
double flAvgNormalizedEngagementDist = nTotalNormalizedEngagements ? (flTotalNormalizedEngagementDist / nTotalNormalizedEngagements) : 0; |
|
|
|
|
|
pFileSys->FPrintf( hFile, "%s", GetGroupNameFor( i ) ); |
|
pFileSys->FPrintf( hFile, "\t%.1f", (pStats->m_flPlayerTime / 60.0f) ); |
|
pFileSys->FPrintf( hFile, "\t%d", (int)flAvgNormalizedEngagementDist ); |
|
pFileSys->FPrintf( hFile, "\t%d", (int)flAvgEngagementDist ); |
|
pFileSys->FPrintf( hFile, "\n" ); |
|
} |
|
|
|
pFileSys->FPrintf( hFile, "\n" ); |
|
pFileSys->FPrintf( hFile, "\n" ); |
|
|
|
|
|
pFileSys->FPrintf( hFile, "Class\tTarget Class\tTotal Damage\tKills\tAvg Engagement Dist\t(OLD) Engagement Dist\n" ); |
|
|
|
for ( i=TFCLASS_UNDECIDED+1; i < STATS_NUM_GROUPS; i++ ) |
|
{ |
|
CPlayerClassStats *pStats = &g_PlayerClassStats[i]; |
|
|
|
// Print the inter-class stats. |
|
for ( int j=TFCLASS_UNDECIDED+1; j < STATS_NUM_GROUPS; j++ ) |
|
{ |
|
CInterClassStats *pInter = &pStats->m_InterClassStats[j]; |
|
|
|
pFileSys->FPrintf( hFile, "%s", GetGroupNameFor( i ) ); |
|
pFileSys->FPrintf( hFile, "\t%s", GetGroupNameFor( j ) ); |
|
pFileSys->FPrintf( hFile, "\t%d", (int)pInter->m_flTotalDamageInflicted ); |
|
pFileSys->FPrintf( hFile, "\t%d", pInter->m_nKills ); |
|
pFileSys->FPrintf( hFile, "\t%d", (int)(pInter->m_nNormalizedEngagements ? (pInter->m_flTotalNormalizedEngagementDist / pInter->m_nNormalizedEngagements) : 0) ); |
|
pFileSys->FPrintf( hFile, "\t%d", (int)(pInter->m_nEngagements ? (pInter->m_flTotalEngagementDist / pInter->m_nEngagements) : 0) ); |
|
pFileSys->FPrintf( hFile, "\n" ); |
|
} |
|
} |
|
|
|
pFileSys->Close( hFile ); |
|
} |
|
|
|
CTextStatFile g_PlayerClassStatsOutput( PrintPlayerClassStats ); |
|
|
|
|
|
// ---------------------------------------------------------------------------------------------------------------- // |
|
// Detailed stats output. |
|
// ---------------------------------------------------------------------------------------------------------------- // |
|
|
|
ConVar tf_DetailedStats( "tf_DetailedStats", "1", 0, "Prints extensive detailed gameplay stats into detailed_stats.txt" ); |
|
|
|
class CShotInfo |
|
{ |
|
public: |
|
float m_flDistance; |
|
int m_nDamage; |
|
}; |
|
|
|
CUtlLinkedList<CShotInfo,int> g_ClassShotInfos[STATS_NUM_GROUPS]; |
|
|
|
void PrintDetailedPlayerClassStats() |
|
{ |
|
if ( !tf_DetailedStats.GetInt() ) |
|
return; |
|
|
|
IFileSystem *pFileSys = filesystem; |
|
|
|
FileHandle_t hFile = pFileSys->Open( "class_stats_detailed.txt", "wt", "LOGDIR" ); |
|
if ( hFile != FILESYSTEM_INVALID_HANDLE ) |
|
{ |
|
// Print the header. |
|
for ( int i=TFCLASS_UNDECIDED+1; i < STATS_NUM_GROUPS; i++ ) |
|
{ |
|
pFileSys->FPrintf( hFile, "%s dist\t%s dmg\t", GetGroupNameFor( i ), GetGroupNameFor( i ) ); |
|
} |
|
pFileSys->FPrintf( hFile, "\n" ); |
|
|
|
// Write out each column. |
|
int iterators[STATS_NUM_GROUPS]; |
|
for ( i=TFCLASS_UNDECIDED+1; i < STATS_NUM_GROUPS; i++ ) |
|
iterators[i] = g_ClassShotInfos[i].Head(); |
|
|
|
bool bWroteAnything; |
|
do |
|
{ |
|
bWroteAnything = false; |
|
for ( int i=TFCLASS_UNDECIDED+1; i < STATS_NUM_GROUPS; i++ ) |
|
{ |
|
if ( iterators[i] == g_ClassShotInfos[i].InvalidIndex() ) |
|
{ |
|
pFileSys->FPrintf( hFile, "\t\t" ); |
|
} |
|
else |
|
{ |
|
CShotInfo *pInfo = &g_ClassShotInfos[i][iterators[i]]; |
|
iterators[i] = g_ClassShotInfos[i].Next( iterators[i] ); |
|
|
|
pFileSys->FPrintf( hFile, "%.2f\t%d\t", pInfo->m_flDistance, pInfo->m_nDamage ); |
|
bWroteAnything = true; |
|
} |
|
} |
|
pFileSys->FPrintf( hFile, "\n" ); |
|
|
|
} while ( bWroteAnything ); |
|
|
|
|
|
pFileSys->Close( hFile ); |
|
} |
|
} |
|
|
|
CTextStatFile g_PlayerClassStatsDetailedOutput( PrintDetailedPlayerClassStats ); |
|
|
|
|
|
|
|
//===================================================================== |
|
// PLAYER CLASS HANDLING |
|
//===================================================================== |
|
// Base PlayerClass |
|
CPlayerClass::CPlayerClass( CBaseTFPlayer *pPlayer, TFClass iClass ) |
|
: m_TFClass( iClass ) |
|
{ |
|
m_pPlayer = pPlayer; |
|
|
|
for (int i = 0; i <= MAX_TF_TEAMS; ++i) |
|
{ |
|
m_sClassModel[i] = NULL_STRING; |
|
} |
|
m_iNumWeaponTechAssociations = 0; |
|
|
|
m_bTechAssociationsSet = false; |
|
|
|
m_flNormalizedEngagementNextTime = -1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CPlayerClass::~CPlayerClass() |
|
{ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPlayerClass::ClassActivate( void ) |
|
{ |
|
// Setup the default player movement variables. |
|
SetupMoveData(); |
|
|
|
AddWeaponTechAssociations(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPlayerClass::ClassDeactivate( void ) |
|
{ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPlayerClass::AddWeaponTechAssociations( void ) |
|
{ |
|
ClearAllWeaponTechAssoc(); |
|
|
|
// Iterate tech tree for this class and find |
|
Assert( m_pPlayer ); |
|
CTechnologyTree *tree = m_pPlayer->GetTechTree(); |
|
Assert( tree ); |
|
|
|
// Loop through all of the techs to see if any of them say yes to being applicable to |
|
// this class specifically |
|
for ( int i = 0 ; i < tree->GetNumberTechnologies(); i++ ) |
|
{ |
|
CBaseTechnology *tech = tree->GetTechnology( i ); |
|
if ( !tech ) |
|
continue; |
|
|
|
if ( !tech->GetAssociateWeaponsForClass( GetTFClass() ) ) |
|
continue; |
|
|
|
// Associate weapon tech name with class |
|
AddWeaponTechAssoc( (char *)tech->GetName() ); |
|
} |
|
} |
|
|
|
|
|
void CPlayerClass::NetworkStateChanged() |
|
{ |
|
if ( m_pPlayer ) |
|
m_pPlayer->NetworkStateChanged(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Setup the default movement parameters, quite a few of these will be |
|
// overridden with class specific values. |
|
//----------------------------------------------------------------------------- |
|
void CPlayerClass::SetupMoveData( void ) |
|
{ |
|
// Set the default walking and sprinting speeds. |
|
m_flMaxWalkingSpeed = 120; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPlayerClass::SetupSizeData( void ) |
|
{ |
|
// Initially set the player to the base player class standing hull size. |
|
m_pPlayer->SetCollisionBounds( PLAYERCLASS_HULL_STAND_MIN, PLAYERCLASS_HULL_STAND_MAX ); |
|
m_pPlayer->SetViewOffset( PLAYERCLASS_VIEWOFFSET_STAND ); |
|
m_pPlayer->m_Local.m_flStepSize = PLAYERCLASS_STEPSIZE; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPlayerClass::CreateClass( void ) |
|
{ |
|
// Give them a full loadout on the initial spawn |
|
ResupplyAmmo( 100.0f, RESUPPLY_ALL_FROM_STATION ); |
|
|
|
// Create the builder weapon & all objects in it (Helpers are automatically deleted when the weapon's deleted) |
|
// Make sure they can build at least 1 object |
|
if ( GetTFClassInfo( m_TFClass )->m_pClassObjects[0] != OBJ_LAST ) |
|
{ |
|
m_pPlayer->GiveNamedItem( "weapon_builder" ); |
|
|
|
Assert( m_pPlayer->GetWeaponBuilder() ); |
|
|
|
// Do we have a construction yard? |
|
bool bHaveYard = false; |
|
CBaseEntity *pEntity = NULL; |
|
while ((pEntity = gEntList.FindEntityByClassname( pEntity, "func_construction_yard" )) != NULL) |
|
{ |
|
if ( m_pPlayer->InSameTeam( pEntity ) ) |
|
{ |
|
bHaveYard = true; |
|
break; |
|
} |
|
} |
|
|
|
for ( int i = 0; i < OBJ_LAST; i++ ) |
|
{ |
|
int iClassObject = GetTFClassInfo( m_TFClass )->m_pClassObjects[i]; |
|
|
|
// Hit the end? |
|
if ( iClassObject == OBJ_LAST ) |
|
break; |
|
|
|
// Only Humans can build the powerpacks & mortars |
|
if ( iClassObject == OBJ_POWERPACK || iClassObject == OBJ_MORTAR) |
|
{ |
|
if ( m_pPlayer->GetTeamNumber() != TEAM_HUMANS ) |
|
continue; |
|
} |
|
|
|
// Only Aliens can build shields |
|
if ( iClassObject == OBJ_SHIELDWALL ) |
|
{ |
|
if ( m_pPlayer->GetTeamNumber() != TEAM_ALIENS ) |
|
continue; |
|
} |
|
|
|
// If my team doesn't have a construction yard, don't allow me to build vehicles |
|
if ( !tf_fastbuild.GetBool() && !bHaveYard ) |
|
{ |
|
if ( IsObjectAVehicle(iClassObject) ) |
|
continue; |
|
|
|
// Don't allow them to build vehicle upgrades either |
|
if ( iClassObject == OBJ_DRIVER_MACHINEGUN ) |
|
continue; |
|
} |
|
|
|
// If my team has a construction yard, don't allow me to build defensive buildings |
|
if ( !tf_fastbuild.GetBool() && bHaveYard && IsObjectADefensiveBuilding(iClassObject) ) |
|
continue; |
|
|
|
m_pPlayer->GetWeaponBuilder()->AddBuildableObject( iClassObject ); |
|
|
|
// Give the player a fake weapon to select this object with |
|
CWeaponObjectSelection *pSelection = (CWeaponObjectSelection *)m_pPlayer->GiveNamedItem( "weapon_objectselection", iClassObject ); |
|
if ( pSelection ) |
|
{ |
|
pSelection->SetType( iClassObject ); |
|
} |
|
} |
|
} |
|
|
|
// Give the player all the weapons from tech associations |
|
CTechnologyTree *pTechTree = m_pPlayer->GetTechTree(); |
|
if ( pTechTree ) |
|
{ |
|
// Give the player any weapons s/he might have just received the tech for |
|
for ( int i = 0; i < m_iNumWeaponTechAssociations; i++ ) |
|
{ |
|
if ( m_pPlayer->HasNamedTechnology( m_WeaponTechAssociations[i].pWeaponTech ) ) |
|
{ |
|
CBaseTechnology *tech = pTechTree->GetTechnology( m_WeaponTechAssociations[i].pWeaponTech ); |
|
if ( tech ) |
|
{ |
|
for ( int j = 0; j < tech->GetNumWeaponAssociations(); j++ ) |
|
{ |
|
const char *weaponname = tech->GetAssociatedWeapon( j ); |
|
Assert( weaponname ); |
|
|
|
m_pPlayer->GiveNamedItem( weaponname ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Called on each respawn |
|
//----------------------------------------------------------------------------- |
|
void CPlayerClass::RespawnClass( void ) |
|
{ |
|
ResupplyAmmo( 100.0f, RESUPPLY_ALL_FROM_STATION ); |
|
GainedNewTechnology( NULL ); |
|
SetMaxHealth( GetMaxHealthCVarValue() ); |
|
SetMaxSpeed( GetMaxSpeed() ); |
|
CheckDeterioratingObjects(); |
|
|
|
SetupSizeData(); |
|
|
|
// Refill the clips of all my weapons |
|
for (int i = 0; i < MAX_WEAPONS; i++) |
|
{ |
|
CBaseCombatWeapon *pWeapon = m_pPlayer->GetWeapon(i); |
|
if ( pWeapon ) |
|
{ |
|
if ( pWeapon->UsesClipsForAmmo1() ) |
|
{ |
|
pWeapon->m_iClip1 = pWeapon->GetDefaultClip1(); |
|
} |
|
if ( pWeapon->UsesClipsForAmmo2() ) |
|
{ |
|
pWeapon->m_iClip2 = pWeapon->GetDefaultClip2(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Supply the player with Ammo. Return true if some ammo was given. |
|
//----------------------------------------------------------------------------- |
|
bool CPlayerClass::ResupplyAmmoType( float flAmount, const char *pAmmoType ) |
|
{ |
|
if (flAmount <= 0) |
|
return false; |
|
|
|
// Make sure at least 1 if given... |
|
int nAmount; |
|
if (flAmount <= 1) |
|
nAmount = 1; |
|
else |
|
nAmount = (int)flAmount; |
|
|
|
bool bGiven = false; |
|
if ( g_pGameRules->CanHaveAmmo( m_pPlayer, pAmmoType ) ) |
|
{ |
|
if ( m_pPlayer->GiveAmmo( nAmount, pAmmoType, true ) > 0 ) |
|
bGiven = true; |
|
} |
|
|
|
return bGiven; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Supply the player with Ammo. Return true if some ammo was given. |
|
//----------------------------------------------------------------------------- |
|
bool CPlayerClass::ResupplyAmmo( float flPercentage, ResupplyReason_t reason ) |
|
{ |
|
bool bGiven = false; |
|
|
|
// Fully resupply shield energy everytime |
|
if ( m_pPlayer->GetCombatShield() ) |
|
{ |
|
m_pPlayer->GetCombatShield()->AddShieldHealth( 1.0 ); |
|
} |
|
|
|
if ((reason == RESUPPLY_RESPAWN) || (reason == RESUPPLY_ALL_FROM_STATION) || (reason == RESUPPLY_AMMO_FROM_STATION)) |
|
{ |
|
if (ResupplyAmmoType( 1, "Sappers" )) |
|
{ |
|
bGiven = true; |
|
} |
|
} |
|
|
|
return bGiven; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Calculate the player's Speed |
|
//----------------------------------------------------------------------------- |
|
float CPlayerClass::GetMaxSpeed( void ) |
|
{ |
|
float flMaxSpeed = m_flMaxWalkingSpeed; |
|
|
|
// Is the player in adrenalin mode? |
|
if ( m_pPlayer->HasPowerup( POWERUP_RUSH ) ) |
|
{ |
|
flMaxSpeed *= ADRENALIN_SPEED_INCREASE; |
|
} |
|
|
|
// Is the player unable to move right now? |
|
if ( m_pPlayer->CantMove() ) |
|
{ |
|
flMaxSpeed = 1; |
|
} |
|
|
|
return flMaxSpeed; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Get the player's maximum walking speed |
|
//----------------------------------------------------------------------------- |
|
float CPlayerClass::GetMaxWalkSpeed( void ) |
|
{ |
|
return m_flMaxWalkingSpeed; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Set the player's speed |
|
//----------------------------------------------------------------------------- |
|
void CPlayerClass::SetMaxSpeed( float flMaxSpeed ) |
|
{ |
|
if ( m_pPlayer ) |
|
{ |
|
m_pPlayer->SetMaxSpeed( flMaxSpeed ); |
|
|
|
float curspeed = m_pPlayer->GetAbsVelocity().Length(); |
|
|
|
if ( curspeed != 0.0f && curspeed > flMaxSpeed ) |
|
{ |
|
float crop = flMaxSpeed / curspeed; |
|
|
|
Vector vecNewVelocity; |
|
VectorScale( m_pPlayer->GetAbsVelocity(), crop, vecNewVelocity ); |
|
m_pPlayer->SetAbsVelocity( vecNewVelocity ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Return the player's max health |
|
//----------------------------------------------------------------------------- |
|
int CPlayerClass::GetMaxHealthCVarValue() |
|
{ |
|
int val = GetTFClassInfo( GetTFClass() )->m_pMaxHealthCVar->GetInt(); |
|
Assert( val > 0 ); // If you hit this assert, then you probably didn't add an entry to skill?.cfg |
|
return val; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Set the player's health |
|
//----------------------------------------------------------------------------- |
|
void CPlayerClass::SetMaxHealth( float flMaxHealth ) |
|
{ |
|
if ( m_pPlayer ) |
|
{ |
|
m_pPlayer->m_iMaxHealth = m_pPlayer->m_iHealth = flMaxHealth; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
string_t CPlayerClass::GetClassModel( int nTeam ) |
|
{ |
|
return m_sClassModel[nTeam]; |
|
} |
|
|
|
|
|
const char* CPlayerClass::GetClassModelString( int nTeam ) |
|
{ |
|
// Each derived class should implement this. |
|
Assert( false ); |
|
return "INVALID"; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Called to see if another player should be displayed on the players radar |
|
//----------------------------------------------------------------------------- |
|
bool CPlayerClass::CanSeePlayerOnRadar( CBaseTFPlayer *pl ) |
|
{ |
|
// if ( pl->InSameTeam(m_pPlayer) ) |
|
// return false; |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPlayerClass::ItemPostFrame() |
|
{ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : TFClass |
|
//----------------------------------------------------------------------------- |
|
TFClass CPlayerClass::GetTFClass( void ) |
|
{ |
|
return m_TFClass; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handle custom commands for this playerclass |
|
//----------------------------------------------------------------------------- |
|
bool CPlayerClass::ClientCommand( const CCommand& args ) |
|
{ |
|
if ( FStrEq( args[0], "builder_select_obj" ) ) |
|
{ |
|
if ( args.ArgC() < 2 ) |
|
return true; |
|
|
|
// This is a total hack. Eventually this should come in via usercmds. |
|
|
|
// Get our builder weapon |
|
if ( !m_pPlayer->GetWeaponBuilder() ) |
|
return true; |
|
|
|
// Select a state for the builder weapon |
|
m_pPlayer->GetWeaponBuilder()->SetCurrentObject( atoi( args[1] ) ); |
|
m_pPlayer->GetWeaponBuilder()->SetCurrentState( BS_PLACING ); |
|
m_pPlayer->GetWeaponBuilder()->StartPlacement(); |
|
m_pPlayer->GetWeaponBuilder()->m_flNextPrimaryAttack = gpGlobals->curtime + 0.35f; |
|
return true; |
|
} |
|
else if ( FStrEq( args[0], "builder_select_mode" ) ) |
|
{ |
|
if ( args.ArgC() < 2 ) |
|
return true; |
|
|
|
// Get our builder weapon |
|
if ( !m_pPlayer->GetWeaponBuilder() ) |
|
return true; |
|
|
|
// Select a state for the builder weapon |
|
m_pPlayer->GetWeaponBuilder()->SetCurrentState( atoi( args[1] ) ); |
|
m_pPlayer->GetWeaponBuilder()->m_flNextPrimaryAttack = gpGlobals->curtime + 0.35f; |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Should we take damage-based force? |
|
//----------------------------------------------------------------------------- |
|
bool CPlayerClass::ShouldApplyDamageForce( const CTakeDamageInfo &info ) |
|
{ |
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: The player has taken damage. Return the damage done. |
|
//----------------------------------------------------------------------------- |
|
float CPlayerClass::OnTakeDamage( const CTakeDamageInfo &info ) |
|
{ |
|
if ( info.GetAttacker() ) |
|
{ |
|
CBaseTFPlayer *pPlayer = dynamic_cast< CBaseTFPlayer* >( info.GetAttacker() ); |
|
if ( pPlayer && pPlayer->GetPlayerClass() ) |
|
{ |
|
int iStatGroup = GetStatGroupFor( pPlayer ); |
|
CInterClassStats *pInter = &g_PlayerClassStats[iStatGroup].m_InterClassStats[GetTFClass()]; |
|
|
|
pInter->m_flTotalDamageInflicted += info.GetDamage(); |
|
|
|
float flDistToAttacker = pPlayer->GetAbsOrigin().DistTo( GetPlayer()->GetAbsOrigin() ); |
|
pInter->m_flTotalEngagementDist += flDistToAttacker; |
|
pInter->m_nEngagements++; |
|
|
|
if ( gpGlobals->curtime >= m_flNormalizedEngagementNextTime ) |
|
{ |
|
pInter->m_flTotalNormalizedEngagementDist += flDistToAttacker; |
|
pInter->m_nNormalizedEngagements++; |
|
|
|
m_flNormalizedEngagementNextTime = gpGlobals->curtime + 3; |
|
} |
|
|
|
// Store detailed stats for the shot? |
|
if ( tf_DetailedStats.GetInt() ) |
|
{ |
|
CShotInfo shotInfo; |
|
|
|
shotInfo.m_flDistance = flDistToAttacker; |
|
shotInfo.m_nDamage = (int)info.GetDamage(); |
|
|
|
g_ClassShotInfos[iStatGroup].AddToTail( shotInfo ); |
|
} |
|
} |
|
} |
|
|
|
return info.GetDamage(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: New technology has been gained. Recalculate any class specific technology dependencies. |
|
//----------------------------------------------------------------------------- |
|
void CPlayerClass::GainedNewTechnology( CBaseTechnology *pTechnology ) |
|
{ |
|
int i; |
|
|
|
// Tell the player's weapons that this player's gained new technology |
|
for ( i = 0; i < m_pPlayer->WeaponCount(); i++ ) |
|
{ |
|
if ( m_pPlayer->GetWeapon(i) ) |
|
{ |
|
((CBaseTFCombatWeapon*)m_pPlayer->GetWeapon(i))->GainedNewTechnology( pTechnology ); |
|
} |
|
} |
|
|
|
// Tell the player's objects that this player's gained new technology |
|
for ( i = 0; i < m_pPlayer->GetObjectCount(); i++ ) |
|
{ |
|
if (m_pPlayer->GetObject(i)) |
|
{ |
|
m_pPlayer->GetObject(i)->GainedNewTechnology( pTechnology ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Return true if this player's allowed to build another one of the specified objects |
|
//----------------------------------------------------------------------------- |
|
int CPlayerClass::CanBuild( int iObjectType ) |
|
{ |
|
int iObjectCount = GetNumObjects( iObjectType ); |
|
|
|
// Make sure we haven't hit maximum number |
|
if ( tf2_object_hard_limits.GetBool() ) |
|
{ |
|
if ( iObjectCount >= GetObjectInfo( iObjectType )->m_nMaxObjects ) |
|
return CB_LIMIT_REACHED; |
|
} |
|
else |
|
{ |
|
// Find out how much the next object should cost |
|
int iCost = CalculateObjectCost( iObjectType, GetNumObjects( iObjectType ), m_pPlayer->GetTeamNumber() ); |
|
|
|
// Make sure we have enough resources |
|
if ( m_pPlayer->GetBankResources() < iCost ) |
|
return CB_NEED_RESOURCES; |
|
} |
|
|
|
return CB_CAN_BUILD; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Object built by this player has been destroyed |
|
//----------------------------------------------------------------------------- |
|
void CPlayerClass::OwnedObjectDestroyed( CBaseObject *pObject ) |
|
{ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Get the number of the specified objects built by the player |
|
//----------------------------------------------------------------------------- |
|
int CPlayerClass::GetNumObjects( int iObjectType ) |
|
{ |
|
// For the purposes of costs/limits, Sentryguns tally up all sentrygun types |
|
if ( iObjectType == OBJ_SENTRYGUN_PLASMA || iObjectType == OBJ_SENTRYGUN_ROCKET_LAUNCHER ) |
|
{ |
|
return ( m_pPlayer->GetNumObjects( OBJ_SENTRYGUN_PLASMA ) + m_pPlayer->GetNumObjects( OBJ_SENTRYGUN_ROCKET_LAUNCHER ) ); |
|
} |
|
|
|
return m_pPlayer->GetNumObjects( iObjectType ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Player has started building an object |
|
//----------------------------------------------------------------------------- |
|
int CPlayerClass::StartedBuildingObject( int iObjectType ) |
|
{ |
|
// Deduct the cost of the object |
|
if ( !tf2_object_hard_limits.GetBool() ) |
|
{ |
|
int iCost = CalculateObjectCost( iObjectType, GetNumObjects( iObjectType ), m_pPlayer->GetTeamNumber() ); |
|
if ( iCost > m_pPlayer->GetBankResources() ) |
|
{ |
|
// Player must have lost resources since he started placing |
|
return 0; |
|
} |
|
m_pPlayer->RemoveBankResources( iCost ); |
|
|
|
// If the object costs 0, we need to return non-0 to mean success |
|
if ( !iCost ) |
|
return 1; |
|
|
|
return iCost; |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Player has aborted a build |
|
//----------------------------------------------------------------------------- |
|
void CPlayerClass::StoppedBuilding( int iObjectType ) |
|
{ |
|
// Return the cost of the object |
|
if ( !tf2_object_hard_limits.GetBool() ) |
|
{ |
|
int iCost = CalculateObjectCost( iObjectType, GetNumObjects( iObjectType ), m_pPlayer->GetTeamNumber() ); |
|
m_pPlayer->AddBankResources( iCost ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Object has been built by this player |
|
//----------------------------------------------------------------------------- |
|
void CPlayerClass::FinishedObject( CBaseObject *pObject ) |
|
{ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Object has been picked up by this player |
|
//----------------------------------------------------------------------------- |
|
void CPlayerClass::PickupObject( CBaseObject *pObject ) |
|
{ |
|
// Return the cost of the object |
|
if ( !tf2_object_hard_limits.GetBool() ) |
|
{ |
|
int iCost = CalculateObjectCost( pObject->GetType(), GetNumObjects( pObject->GetType() ), m_pPlayer->GetTeamNumber(), true ); |
|
m_pPlayer->AddBankResources( iCost ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Create a personal order for this player |
|
//----------------------------------------------------------------------------- |
|
bool CPlayerClass::CreateInitialOrder() |
|
{ |
|
if( AnyNonResourceZoneOrders() ) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
|
|
void CPlayerClass::CreatePersonalOrder() |
|
{ |
|
if( CreateInitialOrder() ) |
|
return; |
|
|
|
// Make an order to fix any objects we own. |
|
if ( COrderRepair::CreateOrder_RepairOwnObjects( this ) ) |
|
return; |
|
} |
|
|
|
|
|
bool CPlayerClass::AnyResourceZoneOrders() |
|
{ |
|
return !!m_pPlayer->GetNumResourceZoneOrders(); |
|
} |
|
|
|
|
|
bool CPlayerClass::AnyNonResourceZoneOrders() |
|
{ |
|
return |
|
m_pPlayer->GetNumResourceZoneOrders() == 0 && |
|
m_pPlayer->GetOrder(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPlayerClass::ClassThink( void ) |
|
{ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPlayerClass::PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier ) |
|
{ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPlayerClass::PowerupEnd( int iPowerup ) |
|
{ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: My player has just died |
|
//----------------------------------------------------------------------------- |
|
void CPlayerClass::PlayerDied( CBaseEntity *pAttacker ) |
|
{ |
|
if ( pAttacker ) |
|
{ |
|
CBaseTFPlayer *pAttackerPlayer = dynamic_cast< CBaseTFPlayer* >( pAttacker ); |
|
if ( pAttackerPlayer ) |
|
{ |
|
CPlayerClass *pAttackerClass = pAttackerPlayer->GetPlayerClass(); |
|
if ( pAttackerClass ) |
|
{ |
|
int iStatGroup = GetStatGroupFor( pAttackerPlayer ); |
|
g_PlayerClassStats[ iStatGroup ].m_InterClassStats[GetTFClass()].m_nKills++; |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: My player just killed another player |
|
//----------------------------------------------------------------------------- |
|
void CPlayerClass::PlayerKilledPlayer( CBaseTFPlayer *pVictim ) |
|
{ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPlayerClass::SetPlayerHull( void ) |
|
{ |
|
// Use the generic player hull if the class doesn't override this function. |
|
if ( m_pPlayer->GetFlags() & FL_DUCKING ) |
|
{ |
|
m_pPlayer->SetCollisionBounds( PLAYERCLASS_HULL_DUCK_MIN, PLAYERCLASS_HULL_DUCK_MAX ); |
|
} |
|
else |
|
{ |
|
m_pPlayer->SetCollisionBounds( PLAYERCLASS_HULL_STAND_MIN, PLAYERCLASS_HULL_STAND_MAX ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPlayerClass::GetPlayerHull( bool bDucking, Vector &vecMin, Vector &vecMax ) |
|
{ |
|
// Use the generic player hull if the class doesn't override this function. |
|
if ( bDucking ) |
|
{ |
|
VectorCopy( PLAYERCLASS_HULL_DUCK_MIN, vecMin ); |
|
VectorCopy( PLAYERCLASS_HULL_DUCK_MAX, vecMax ); |
|
} |
|
else |
|
{ |
|
VectorCopy( PLAYERCLASS_HULL_STAND_MIN, vecMin ); |
|
VectorCopy( PLAYERCLASS_HULL_STAND_MAX, vecMax ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPlayerClass::InitVCollision( void ) |
|
{ |
|
CPhysCollide *pStandModel = PhysCreateBbox( PLAYERCLASS_HULL_STAND_MIN, PLAYERCLASS_HULL_STAND_MAX ); |
|
CPhysCollide *pCrouchModel = PhysCreateBbox( PLAYERCLASS_HULL_DUCK_MIN, PLAYERCLASS_HULL_DUCK_MAX ); |
|
|
|
solid_t solid; |
|
solid.params = g_PhysDefaultObjectParams; |
|
solid.params.mass = 85.0f; |
|
solid.params.inertia = 1e24f; |
|
solid.params.enableCollisions = false; |
|
//disable drag |
|
solid.params.dragCoefficient = 0; |
|
|
|
// create standing hull |
|
m_pPlayer->m_pShadowStand = PhysModelCreateCustom( m_pPlayer, pStandModel, m_pPlayer->GetLocalOrigin(), m_pPlayer->GetLocalAngles(), "tfplayer_generic", false, &solid ); |
|
m_pPlayer->m_pShadowStand->SetCallbackFlags( CALLBACK_SHADOW_COLLISION ); |
|
|
|
// create crouchig hull |
|
m_pPlayer->m_pShadowCrouch = PhysModelCreateCustom( m_pPlayer, pCrouchModel, m_pPlayer->GetLocalOrigin(), m_pPlayer->GetLocalAngles(), "tfplayer_generic", false, &solid ); |
|
m_pPlayer->m_pShadowCrouch->SetCallbackFlags( CALLBACK_SHADOW_COLLISION ); |
|
|
|
// default to stand |
|
m_pPlayer->VPhysicsSetObject( m_pPlayer->m_pShadowStand ); |
|
|
|
//disable drag |
|
m_pPlayer->m_pShadowStand->EnableDrag( false ); |
|
m_pPlayer->m_pShadowCrouch->EnableDrag( false ); |
|
|
|
// tell physics lists I'm a shadow controller object |
|
PhysAddShadow( m_pPlayer ); |
|
m_pPlayer->m_pPhysicsController = physenv->CreatePlayerController( m_pPlayer->m_pShadowStand ); |
|
|
|
// init state |
|
if ( m_pPlayer->GetFlags() & FL_DUCKING ) |
|
{ |
|
m_pPlayer->SetVCollisionState( VPHYS_CROUCH ); |
|
} |
|
else |
|
{ |
|
m_pPlayer->SetVCollisionState( VPHYS_WALK ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pObject - |
|
// *pNewOwner - |
|
//----------------------------------------------------------------------------- |
|
void CPlayerClass::OwnedObjectChangeToTeam( CBaseObject *pObject, CBaseTFPlayer *pNewOwner ) |
|
{ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pObject - |
|
// *pOldOwner - |
|
//----------------------------------------------------------------------------- |
|
void CPlayerClass::OwnedObjectChangeFromTeam( CBaseObject *pObject, CBaseTFPlayer *pOldOwner ) |
|
{ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Check to see if there are any deteriorating objects I once owned that |
|
// I can now re-own (i.e. I switched teams back to my original team). |
|
// TODO: Make this use Steam IDs, so disconnecting & reconnecting players |
|
// get their objects back. |
|
//----------------------------------------------------------------------------- |
|
void CPlayerClass::CheckDeterioratingObjects( void ) |
|
{ |
|
if ( !GetTeam() ) |
|
return; |
|
|
|
// Cycle through the team's objects looking for any deteriorating objects once owned by me |
|
for ( int i = 0; i < GetTeam()->GetNumObjects(); i++ ) |
|
{ |
|
CBaseObject *pObject = GetTeam()->GetObject(i); |
|
if ( pObject->IsDeteriorating() && pObject->GetOriginalBuilder() == m_pPlayer ) |
|
{ |
|
// Can this class build this object? |
|
if ( ClassCanBuild( GetTFClass(), pObject->GetType() ) ) |
|
{ |
|
// Give the object back to this player |
|
pObject->SetBuilder( m_pPlayer ); |
|
m_pPlayer->AddObject( pObject ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : edict_t |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CPlayerClass::SelectSpawnPoint( void ) |
|
{ |
|
return NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Add a new weapon/tech association. Weapons that the player has the associated tech for |
|
// will automatically be given out if the player has the tech. |
|
//----------------------------------------------------------------------------- |
|
void CPlayerClass::AddWeaponTechAssoc( char *pWeaponTech ) |
|
{ |
|
Assert( m_iNumWeaponTechAssociations < MAX_WEAPONS ); |
|
|
|
m_WeaponTechAssociations[m_iNumWeaponTechAssociations].pWeaponTech = pWeaponTech; |
|
m_iNumWeaponTechAssociations++; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPlayerClass::ClearAllWeaponTechAssoc( ) |
|
{ |
|
m_iNumWeaponTechAssociations = 0; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CTFTeam *CPlayerClass::GetTeam() |
|
{ |
|
return (CTFTeam*)GetPlayer()->GetTeam(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPlayerClass::ResetViewOffset( void ) |
|
{ |
|
if ( m_pPlayer ) |
|
{ |
|
m_pPlayer->SetViewOffset( PLAYERCLASS_VIEWOFFSET_STAND ); |
|
} |
|
} |
|
|
|
|