//========= 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( pPlayer->GetVehicle() ) ) return STATS_TANK; if ( dynamic_cast( pPlayer->GetVehicle() ) ) return STATS_MANNEDGUN_ROCKET; if ( dynamic_cast( 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 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 ); } }