From cbab0e8f776720100a0875928ca8bc4c7254c881 Mon Sep 17 00:00:00 2001 From: TheShermanTanker Date: Wed, 25 Mar 2026 11:56:19 +0800 Subject: [PATCH] Implement basic robots outside of Man vs Machine --- src/game/client/tf/c_tf_player.cpp | 6 +-- src/game/client/tf/tf_fx_impacts.cpp | 2 +- .../tf/bot/behavior/spy/tf_bot_spy_hide.cpp | 2 +- src/game/server/tf/tf_player.cpp | 37 ++++++++++++++++--- src/game/shared/tf/tf_gamerules.cpp | 22 ++++++++++- src/game/shared/tf/tf_gamerules.h | 4 ++ src/game/shared/tf/tf_player_shared.cpp | 2 +- src/game/shared/tf/tf_weaponbase_melee.cpp | 6 +-- 8 files changed, 65 insertions(+), 16 deletions(-) diff --git a/src/game/client/tf/c_tf_player.cpp b/src/game/client/tf/c_tf_player.cpp index 4d6251437c0..1c033f5eb71 100644 --- a/src/game/client/tf/c_tf_player.cpp +++ b/src/game/client/tf/c_tf_player.cpp @@ -1244,7 +1244,7 @@ void C_TFRagdoll::OnDataChanged( DataUpdateType_t type ) CreateTFHeadGib(); EmitSound( "TFPlayer.Decapitated" ); - bool bBlood = true; + bool bBlood = !( pPlayer && IsRobotTeam( pPlayer->GetTeamNumber() ) ); if ( TFGameRules() && ( TFGameRules()->UseSillyGibs() || ( TFGameRules()->IsMannVsMachineMode() && pPlayer && pPlayer->GetTeamNumber() == TF_TEAM_PVE_INVADERS ) ) ) { @@ -5800,7 +5800,7 @@ bool C_TFPlayer::CanLightCigarette( void ) } // don't light for MvM Spy robots - if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && GetTeamNumber() == TF_TEAM_PVE_INVADERS ) + if ( IsRobotTeam( GetTeamNumber() ) || ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && GetTeamNumber() == TF_TEAM_PVE_INVADERS ) ) return false; // Don't light if we are invis. @@ -10568,7 +10568,7 @@ void C_TFPlayer::UpdateKillStreakEffects( int iCount, bool bKillScored /* = fals void C_TFPlayer::UpdateMVMEyeGlowEffect( bool bVisible ) { - if ( !TFGameRules() || !TFGameRules()->IsMannVsMachineMode() || GetTeamNumber() != TF_TEAM_PVE_INVADERS ) + if ( !IsRobotTeam( GetTeamNumber() ) || !TFGameRules() || !TFGameRules()->IsMannVsMachineMode() || GetTeamNumber() != TF_TEAM_PVE_INVADERS ) { return; } diff --git a/src/game/client/tf/tf_fx_impacts.cpp b/src/game/client/tf/tf_fx_impacts.cpp index 74b7f113fa8..2d89268dd60 100644 --- a/src/game/client/tf/tf_fx_impacts.cpp +++ b/src/game/client/tf/tf_fx_impacts.cpp @@ -65,7 +65,7 @@ void ImpactCallback( const CEffectData &data ) bool bPlaySound = true; bool bIsRobotImpact = false; - if ( bIsMVM && pPlayer && nApparentTeam == TF_TEAM_PVE_INVADERS ) + if ( ( pPlayer && IsRobotTeam( nApparentTeam ) ) || ( bIsMVM && pPlayer && nApparentTeam == TF_TEAM_PVE_INVADERS ) ) { bPlaySound = true; bIsRobotImpact = true; diff --git a/src/game/server/tf/bot/behavior/spy/tf_bot_spy_hide.cpp b/src/game/server/tf/bot/behavior/spy/tf_bot_spy_hide.cpp index 914b2d9596b..15ef2bb2a4f 100644 --- a/src/game/server/tf/bot/behavior/spy/tf_bot_spy_hide.cpp +++ b/src/game/server/tf/bot/behavior/spy/tf_bot_spy_hide.cpp @@ -70,7 +70,7 @@ ActionResult< CTFBot > CTFBotSpyHide::Update( CTFBot *me, float interval ) if ( m_talkTimer.IsElapsed() ) { m_talkTimer.Start( RandomFloat( 5.0f, 10.0f ) ); - if ( TFGameRules()->IsMannVsMachineMode() && me->GetTeamNumber() == TF_TEAM_PVE_INVADERS ) + if ( IsRobotTeam( me->GetTeamNumber() ) || ( TFGameRules()->IsMannVsMachineMode() && me->GetTeamNumber() == TF_TEAM_PVE_INVADERS ) ) { me->EmitSound( "Spy.MVM_TeaseVictim" ); } diff --git a/src/game/server/tf/tf_player.cpp b/src/game/server/tf/tf_player.cpp index d29e346def0..27d973d6cc0 100644 --- a/src/game/server/tf/tf_player.cpp +++ b/src/game/server/tf/tf_player.cpp @@ -1221,7 +1221,7 @@ void CTFPlayer::SetGrapplingHookTarget( CBaseEntity *pTarget, bool bShouldBleed //----------------------------------------------------------------------------- bool CTFPlayer::CanBeForcedToLaugh( void ) { - if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && IsBot() && ( GetTeamNumber() == TF_TEAM_PVE_INVADERS ) ) + if ( IsRobotTeam( GetTeamNumber() ) || ( TFGameRules() && ( TFGameRules()->IsMannVsMachineMode() && IsBot() && ( GetTeamNumber() == TF_TEAM_PVE_INVADERS ) ) ) ) return false; return true; @@ -10834,7 +10834,7 @@ int CTFPlayer::OnTakeDamage_Alive( const CTakeDamageInfo &info ) vDamagePos = WorldSpaceCenter(); } - if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && GetTeamNumber() == TF_TEAM_PVE_INVADERS ) + if ( IsRobotTeam( GetTeamNumber() ) || ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && GetTeamNumber() == TF_TEAM_PVE_INVADERS ) ) { if ( ( IsMiniBoss() && static_cast< float >( GetHealth() ) / GetMaxHealth() > 0.3f ) || realDamage < 50 ) { @@ -12420,7 +12420,7 @@ void CTFPlayer::Event_Killed( const CTakeDamageInfo &info ) SetGibbedOnLastDeath( bGib ); bool bIsMvMRobot = TFGameRules()->IsMannVsMachineMode() && IsBot(); - if ( bGib && !bIsMvMRobot && IsPlayerClass( TF_CLASS_SCOUT ) && RandomInt( 1, 100 ) <= SCOUT_ADD_BIRD_ON_GIB_CHANCE ) + if ( bGib && !bIsMvMRobot && !IsRobotTeam( GetTeamNumber() ) && IsPlayerClass( TF_CLASS_SCOUT ) && RandomInt( 1, 100 ) <= SCOUT_ADD_BIRD_ON_GIB_CHANCE ) { Vector vecPos = WorldSpaceCenter(); SpawnClientsideFlyingBird( vecPos ); @@ -14721,6 +14721,22 @@ void CTFPlayer::ForceRespawn( void ) } m_bSwitchedClass = false; + + if ( IsRobotTeam( GetTeamNumber() ) ) + { + const int nClassIndex = ( GetPlayerClass() ? GetPlayerClass()->GetClassIndex() : TF_CLASS_UNDEFINED ); + if ( nClassIndex >= TF_CLASS_SCOUT && nClassIndex <= TF_CLASS_ENGINEER && g_pFullFileSystem->FileExists( g_szBotModels[ nClassIndex ] ) ) + SetCustomModelWithClassAnimations( g_szBotModels[nClassIndex] ); + + SetBloodColor( DONT_BLEED ); + } + else + { + if ( GetPlayerClass()->HasCustomModel() ) + SetCustomModelWithClassAnimations( NULL ); + + SetBloodColor( BLOOD_COLOR_RED ); + } } //----------------------------------------------------------------------------- @@ -15156,7 +15172,16 @@ void CTFPlayer::PainSound( const CTakeDamageInfo &info ) TFPlayerClassData_t *pData = GetPlayerClass()->GetData(); if ( pData ) { - EmitSound( pData->GetDeathSound( DEATH_SOUND_GENERIC ) ); + int deathSound = DEATH_SOUND_GENERIC; + if ( IsRobotTeam( GetTeamNumber() ) || ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && GetTeamNumber() == TF_TEAM_PVE_INVADERS ) ) + { + deathSound = DEATH_SOUND_GENERIC_MVM; + if ( IsMiniBoss() ) + { + deathSound = DEATH_SOUND_GENERIC_GIANT_MVM; + } + } + EmitSound( pData->GetDeathSound( deathSound ) ); } } return; @@ -15258,7 +15283,7 @@ void CTFPlayer::DeathSound( const CTakeDamageInfo &info ) int nDeathSoundOffset = DEATH_SOUND_FIRST; - if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && GetTeamNumber() == TF_TEAM_PVE_INVADERS ) + if ( IsRobotTeam( GetTeamNumber() ) || ( TFGameRules() && ( TFGameRules()->IsMannVsMachineMode() && GetTeamNumber() == TF_TEAM_PVE_INVADERS ) ) ) { nDeathSoundOffset = IsMiniBoss() ? DEATH_SOUND_GIANT_MVM_FIRST : DEATH_SOUND_MVM_FIRST; } @@ -15317,7 +15342,7 @@ void CTFPlayer::DeathSound( const CTakeDamageInfo &info ) //----------------------------------------------------------------------------- const char* CTFPlayer::GetSceneSoundToken( void ) { - if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && GetTeamNumber() == TF_TEAM_PVE_INVADERS ) + if ( IsRobotTeam( GetTeamNumber() ) || ( TFGameRules() && ( TFGameRules()->IsMannVsMachineMode() && GetTeamNumber() == TF_TEAM_PVE_INVADERS ) ) ) { if ( IsMiniBoss() ) { diff --git a/src/game/shared/tf/tf_gamerules.cpp b/src/game/shared/tf/tf_gamerules.cpp index cce13ada8e2..f2a94da521c 100644 --- a/src/game/shared/tf/tf_gamerules.cpp +++ b/src/game/shared/tf/tf_gamerules.cpp @@ -918,6 +918,8 @@ ConVar tf_mvm_respec_credit_goal( "tf_mvm_respec_credit_goal", "2000", FCVAR_CHE ConVar tf_mvm_buybacks_method( "tf_mvm_buybacks_method", "0", FCVAR_REPLICATED | FCVAR_HIDDEN, "When set to 0, use the traditional, currency-based system. When set to 1, use finite, charge-based system.", true, 0.0, true, 1.0 ); ConVar tf_mvm_buybacks_per_wave( "tf_mvm_buybacks_per_wave", "3", FCVAR_REPLICATED | FCVAR_HIDDEN, "The fixed number of buybacks players can use per-wave." ); +ConVar tf_robot_team( "tf_robot_team", "1", FCVAR_GAMEDLL, "Whether to turn a given team into robots outside of Man vs Machine." ); + #ifdef GAME_DLL enum { kMVM_CurrencyPackMinSize = 1, }; @@ -1023,6 +1025,24 @@ bool IsCustomGameMode() } #endif +bool IsRobotTeam( int team ) +{ + const int setting = tf_robot_team.GetInt(); + + if ( setting == 1 ) + { + return true; + } + else if ( setting == 0 ) + { + return false; + } + else + { + return team == setting; + } +} + // Fetch holiday setting taking into account convars, etc, but NOT // taking into consideration the current game rules, map, etc. // @@ -3550,7 +3570,7 @@ void CTFGameRules::Precache( void ) CMerasmus::PrecacheMerasmus(); } - if ( MapHasPrefix( STRING( gpGlobals->mapname ), "mvm_" ) ) + if ( MapHasPrefix( STRING( gpGlobals->mapname ), "mvm_" ) || tf_robot_team.GetInt() != 0 ) { CTFPlayer::PrecacheMvM(); } diff --git a/src/game/shared/tf/tf_gamerules.h b/src/game/shared/tf/tf_gamerules.h index 56bd58bab57..6505f9bef20 100644 --- a/src/game/shared/tf/tf_gamerules.h +++ b/src/game/shared/tf/tf_gamerules.h @@ -99,6 +99,10 @@ class CMannVsMachineUpgrades; extern ConVar tf_mvm_defenders_team_size; extern ConVar tf_mvm_max_invaders; +extern ConVar tf_robot_team; + +extern bool IsRobotTeam( int ); + const int kLadder_TeamSize_6v6 = 6; const int kLadder_TeamSize_9v9 = 9; const int kLadder_TeamSize_12v12 = 12; diff --git a/src/game/shared/tf/tf_player_shared.cpp b/src/game/shared/tf/tf_player_shared.cpp index 6fd3052ee5a..41d168201cb 100644 --- a/src/game/shared/tf/tf_player_shared.cpp +++ b/src/game/shared/tf/tf_player_shared.cpp @@ -11979,7 +11979,7 @@ void CTFPlayer::SetStepSoundTime( stepsoundtimes_t iStepSoundTime, bool bWalking const char *CTFPlayer::GetOverrideStepSound( const char *pszBaseStepSoundName ) { - if( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && GetTeamNumber() == TF_TEAM_PVE_INVADERS && !IsMiniBoss() && !m_Shared.InCond( TF_COND_DISGUISED ) ) + if( IsRobotTeam( GetTeamNumber() ) || ( TFGameRules() && ( TFGameRules()->IsMannVsMachineMode() && GetTeamNumber() == TF_TEAM_PVE_INVADERS && !IsMiniBoss() && !m_Shared.InCond( TF_COND_DISGUISED ) ) ) ) { return "MVM.BotStep"; } diff --git a/src/game/shared/tf/tf_weaponbase_melee.cpp b/src/game/shared/tf/tf_weaponbase_melee.cpp index 0dac9870cfc..cbedf3ad1fb 100644 --- a/src/game/shared/tf/tf_weaponbase_melee.cpp +++ b/src/game/shared/tf/tf_weaponbase_melee.cpp @@ -100,7 +100,7 @@ void CTFWeaponBaseMelee::Precache() { BaseClass::Precache(); - if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) + if ( tf_robot_team.GetInt() != 0 && ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) ) { char szMeleeSoundStr[128] = "MVM_"; const char *shootsound = GetShootSound( MELEE_HIT ); @@ -570,9 +570,9 @@ bool CTFWeaponBaseMelee::OnSwingHit( trace_t &trace ) bool bPlayMvMHitOnly = false; // handle hitting a robot - if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) + if ( tf_robot_team.GetInt() != 0 || ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) ) { - if ( pTargetPlayer && pTargetPlayer->GetTeamNumber() == TF_TEAM_PVE_INVADERS && !pTargetPlayer->IsPlayer() ) + if ( pTargetPlayer && ( IsRobotTeam( pTargetPlayer->GetTeamNumber() ) || ( pTargetPlayer->GetTeamNumber() == TF_TEAM_PVE_INVADERS && !pTargetPlayer->IsPlayer() ) ) ) { bPlayMvMHitOnly = true;