diff --git a/src/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build.cpp b/src/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build.cpp index e6d586e1d38..1ff7d338c1e 100644 --- a/src/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build.cpp +++ b/src/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build.cpp @@ -22,11 +22,19 @@ // this was useful when engineers build at their normal (slow) rate to make sure initial sentries get built in time ConVar tf_raid_engineer_infinte_metal( "tf_raid_engineer_infinte_metal", "1", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); +#ifdef MAPBASE +ConVar tf_bot_arena_build_teleporter( "tf_bot_arena_build_teleporter", "0" ); +#endif + //--------------------------------------------------------------------------------------------- Action< CTFBot > *CTFBotEngineerBuild::InitialContainedAction( CTFBot *me ) { +#ifdef MAPBASE + if ( TFGameRules()->IsPVEModeActive() || ( TFGameRules()->IsInArenaMode() && !tf_bot_arena_build_teleporter.GetBool() ) ) +#else if ( TFGameRules()->IsPVEModeActive() ) +#endif { return new CTFBotEngineerMoveToBuild; } diff --git a/src/game/server/tf/bot/behavior/engineer/tf_bot_engineer_building.cpp b/src/game/server/tf/bot/behavior/engineer/tf_bot_engineer_building.cpp index 245137bad13..4e503dc21b3 100644 --- a/src/game/server/tf/bot/behavior/engineer/tf_bot_engineer_building.cpp +++ b/src/game/server/tf/bot/behavior/engineer/tf_bot_engineer_building.cpp @@ -18,6 +18,10 @@ #include "bot/behavior/engineer/tf_bot_engineer_build_teleport_exit.h" #include "bot/behavior/engineer/tf_bot_engineer_build_sentrygun.h" #include "bot/behavior/engineer/tf_bot_engineer_build_dispenser.h" +#ifdef MAPBASE +#include "bot/behavior/scenario/capture_point/tf_bot_capture_point.h" +#include "bot/behavior/tf_bot_seek_and_destroy.h" +#endif #include "bot/behavior/tf_bot_attack.h" #include "bot/behavior/tf_bot_get_ammo.h" #include "bot/map_entities/tf_bot_hint_teleporter_exit.h" @@ -464,6 +468,30 @@ ActionResult< CTFBot > CTFBotEngineerBuilding::Update( CTFBot *me, float interva } } +#ifdef MAPBASE + if ( TFGameRules()->IsInArenaMode() ) + { + // If we are the only player left on our team, don't get caught in a loop in case there's + // another engineer on the other team maintaining their buildings like we are + // Defer to default capture AI + CUtlVector< CTFPlayer * > livePlayerVector; + CollectPlayers( &livePlayerVector, me->GetTeamNumber(), COLLECT_ONLY_LIVING_PLAYERS ); + + if ( livePlayerVector.Count() <= 1 ) + { + CUtlVector< CTeamControlPoint * > captureVector; + TFGameRules()->CollectCapturePoints( me, &captureVector ); + + if ( captureVector.Count() > 0 ) + { + return ChangeTo( new CTFBotCapturePoint, "Everyone is gone! Going for the point" ); + } + + return ChangeTo( new CTFBotSeekAndDestroy, "Everyone is gone! Seeking and destroying" ); + } + } +#endif + // everything is built - maintain them UpgradeAndMaintainBuildings( me ); diff --git a/src/game/server/tf/bot/behavior/engineer/tf_bot_engineer_move_to_build.cpp b/src/game/server/tf/bot/behavior/engineer/tf_bot_engineer_move_to_build.cpp index 52643d262f5..3342a453ba8 100644 --- a/src/game/server/tf/bot/behavior/engineer/tf_bot_engineer_move_to_build.cpp +++ b/src/game/server/tf/bot/behavior/engineer/tf_bot_engineer_move_to_build.cpp @@ -58,6 +58,11 @@ void CTFBotEngineerMoveToBuild::CollectBuildAreas( CTFBot *me ) m_sentryAreaVector.RemoveAll(); CUtlVector< CTFNavArea * > pointAreaVector; +#ifdef MAPBASE + // Gamemodes such as arena may use control points outside of the actual CP mode + // This is used to check if there's a valid control point, as GetControlPointCenterArea is used elsewhere in engineer AI + CTFNavArea *pointCenterArea = NULL; +#endif Vector pointCentroid = vec3_origin; float pointEnemyIncursion = 0.0f; int i; @@ -110,6 +115,10 @@ void CTFBotEngineerMoveToBuild::CollectBuildAreas( CTFBot *me ) if ( !ctrlPoint ) return; +#ifdef MAPBASE + pointCenterArea = TheTFNavMesh()->GetControlPointCenterArea( ctrlPoint->GetPointIndex() ); +#endif + const CUtlVector< CTFNavArea * > *ctrlPointAreaVector = TheTFNavMesh()->GetControlPointAreas( ctrlPoint->GetPointIndex() ); if ( ctrlPointAreaVector ) @@ -161,7 +170,12 @@ void CTFBotEngineerMoveToBuild::CollectBuildAreas( CTFBot *me ) // continue; // } +#ifdef MAPBASE + // Control points may exist outside of CP mode + if ( pointCenterArea ) +#else if ( TFGameRules()->GetGameType() == TF_GAMETYPE_CP ) +#endif { // don't build directly on the point if ( visibleArea->HasAttributeTF( TF_NAV_CONTROL_POINT ) ) diff --git a/src/game/server/tf/bot/behavior/medic/tf_bot_medic_heal.cpp b/src/game/server/tf/bot/behavior/medic/tf_bot_medic_heal.cpp index 3e03cc6654b..d172b41b138 100644 --- a/src/game/server/tf/bot/behavior/medic/tf_bot_medic_heal.cpp +++ b/src/game/server/tf/bot/behavior/medic/tf_bot_medic_heal.cpp @@ -13,6 +13,10 @@ #include "bot/behavior/medic/tf_bot_medic_retreat.h" #include "bot/behavior/tf_bot_use_teleporter.h" #include "bot/behavior/scenario/capture_the_flag/tf_bot_fetch_flag.h" +#ifdef MAPBASE +#include "bot/behavior/scenario/capture_point/tf_bot_capture_point.h" +#include "bot/behavior/tf_bot_seek_and_destroy.h" +#endif #include "nav_mesh.h" #include "tier0/vprof.h" @@ -272,7 +276,11 @@ CTFPlayer *CTFBotMedicHeal::SelectPatient( CTFBot *me, CTFPlayer *current ) CSelectPrimaryPatient choose( me, current ); +#ifdef MAPBASE + if ( TFGameRules()->IsPVEModeActive() || TFGameRules()->IsInArenaMode() ) +#else if ( TFGameRules()->IsPVEModeActive() ) +#endif { // assume perfect knowledge CUtlVector< CTFPlayer * > livePlayerVector; @@ -518,6 +526,23 @@ ActionResult< CTFBot > CTFBotMedicHeal::Update( CTFBot *me, float interval ) return Continue(); } +#ifdef MAPBASE + if ( TFGameRules()->IsInArenaMode() ) + { + // If we can't find a patient in arena mode, we probably won't for the rest of the round + // Defer to default capture AI + CUtlVector< CTeamControlPoint * > captureVector; + TFGameRules()->CollectCapturePoints( me, &captureVector ); + + if ( captureVector.Count() > 0 ) + { + return ChangeTo( new CTFBotCapturePoint, "Everyone is gone! Going for the point" ); + } + + return ChangeTo( new CTFBotSeekAndDestroy, "Everyone is gone! Seeking and destroying" ); + } +#endif + // no patients - retreat to spawn to find another one return SuspendFor( new CTFBotMedicRetreat, "Retreating to find another patient to heal" ); } diff --git a/src/game/server/tf/bot/behavior/scenario/capture_point/tf_bot_capture_point.cpp b/src/game/server/tf/bot/behavior/scenario/capture_point/tf_bot_capture_point.cpp index 6d8648b58eb..adf73a260e2 100644 --- a/src/game/server/tf/bot/behavior/scenario/capture_point/tf_bot_capture_point.cpp +++ b/src/game/server/tf/bot/behavior/scenario/capture_point/tf_bot_capture_point.cpp @@ -55,6 +55,15 @@ ActionResult< CTFBot > CTFBotCapturePoint::Update( CTFBot *me, float interval ) return SuspendFor( new CTFBotSeekAndDestroy( roamTime ), "Seek and destroy until a point becomes available" ); } +#ifdef MAPBASE + if ( TFGameRules()->IsInArenaMode() ) + { + // CollectCapturePoints() can still count a locked point in arena mode. Wait until it's open before zeroing in + if ( !TeamplayGameRules()->TeamMayCapturePoint( me->GetTeamNumber(), point->GetPointIndex() ) ) + return SuspendFor( new CTFBotSeekAndDestroy, "Seek and destroy until a point becomes available" ); + } +#endif + if ( point->GetTeamNumber() == me->GetTeamNumber() ) { return ChangeTo( new CTFBotDefendPoint, "We need to defend our point(s)" ); diff --git a/src/game/server/tf/bot/behavior/tf_bot_scenario_monitor.cpp b/src/game/server/tf/bot/behavior/tf_bot_scenario_monitor.cpp index c91e907f5e3..678dadb8929 100644 --- a/src/game/server/tf/bot/behavior/tf_bot_scenario_monitor.cpp +++ b/src/game/server/tf/bot/behavior/tf_bot_scenario_monitor.cpp @@ -287,6 +287,21 @@ Action< CTFBot > *CTFBotScenarioMonitor::DesiredScenarioAndClassAction( CTFBot * DevMsg( "%3.2f: %s: Gametype is CP, but I can't find a point to capture or defend!\n", gpGlobals->curtime, me->GetDebugIdentifier() ); return new CTFBotCapturePoint; } +#ifdef MAPBASE + else if ( TFGameRules()->GetGameType() == TF_GAMETYPE_ARENA ) + { + // if we have a point we can capture - do it + CUtlVector< CTeamControlPoint * > captureVector; + TFGameRules()->CollectCapturePoints( me, &captureVector ); + + if ( captureVector.Count() > 0 ) + { + return new CTFBotCapturePoint; + } + + return new CTFBotSeekAndDestroy; + } +#endif else { // scenario not implemented yet - just fight diff --git a/src/game/server/tf/bot/behavior/tf_bot_seek_and_destroy.cpp b/src/game/server/tf/bot/behavior/tf_bot_seek_and_destroy.cpp index 5e78ea3e45f..a715db5f6e2 100644 --- a/src/game/server/tf/bot/behavior/tf_bot_seek_and_destroy.cpp +++ b/src/game/server/tf/bot/behavior/tf_bot_seek_and_destroy.cpp @@ -38,7 +38,7 @@ ActionResult< CTFBot > CTFBotSeekAndDestroy::OnStart( CTFBot *me, Action< CTFBot RecomputeSeekPath( me ); CTeamControlPoint *point = me->GetMyControlPoint(); - m_isPointLocked = ( point && point->IsLocked() ); + m_isPointLocked = IsPointLocked( me, point ); // restart the timer if we have one if ( m_giveUpTimer.HasStarted() ) @@ -77,15 +77,20 @@ ActionResult< CTFBot > CTFBotSeekAndDestroy::Update( CTFBot *me, float interval { CTeamControlPoint *point = me->GetMyControlPoint(); - if ( point && !point->IsLocked() ) + if ( !IsPointLocked( me, point ) ) { return Done( "The point just unlocked" ); } } - if ( !TFGameRules()->RoundHasBeenWon() && me->GetTimeLeftToCapture() < tf_bot_offense_must_push_time.GetFloat() ) + // Added proper fix to the bot offense push time bug that causes seek and destroy to never happen + // Basically just check for these gamemodes and otherwise allow seek and destroy at all times + if (TFGameRules()->GetGameType() == TF_GAMETYPE_ESCORT || TFGameRules()->GetGameType() == TF_GAMETYPE_CP) { - return Done( "Time to push for the objective" ); + if (!TFGameRules()->RoundHasBeenWon() && me->GetTimeLeftToCapture() < tf_bot_offense_must_push_time.GetFloat()) + { + return Done("Time to push for the objective"); + } } } @@ -248,3 +253,25 @@ EventDesiredResult< CTFBot > CTFBotSeekAndDestroy::OnTerritoryLost( CTFBot *me, return TryDone( RESULT_IMPORTANT, "Giving up due to point lost" ); } + +//--------------------------------------------------------------------------------------------- +bool CTFBotSeekAndDestroy::IsPointLocked( CTFBot *me, CTeamControlPoint *point ) +{ + if ( !point ) + return false; + + if ( point->IsLocked() ) + return true; + +#ifdef MAPBASE + if ( TFGameRules()->IsInArenaMode() ) + { + // Arena mode has special lock conditions in CTFGameRules::TeamMayCapturePoint + if ( !TFGameRules()->TeamMayCapturePoint( me->GetTeamNumber(), point->GetPointIndex() ) ) + return true; + } +#endif + + return false; +} + diff --git a/src/game/server/tf/bot/behavior/tf_bot_seek_and_destroy.h b/src/game/server/tf/bot/behavior/tf_bot_seek_and_destroy.h index 26dba48e24f..bbcc9f55bb0 100644 --- a/src/game/server/tf/bot/behavior/tf_bot_seek_and_destroy.h +++ b/src/game/server/tf/bot/behavior/tf_bot_seek_and_destroy.h @@ -35,6 +35,8 @@ class CTFBotSeekAndDestroy : public Action< CTFBot > virtual const char *GetName( void ) const { return "SeekAndDestroy"; }; + bool IsPointLocked( CTFBot *me, CTeamControlPoint *point ); + private: CTFNavArea *m_goalArea; CTFNavArea *ChooseGoalArea( CTFBot *me ); diff --git a/src/game/server/tf/bot/tf_bot.cpp b/src/game/server/tf/bot/tf_bot.cpp index ae235092c2b..abe98781bcd 100644 --- a/src/game/server/tf/bot/tf_bot.cpp +++ b/src/game/server/tf/bot/tf_bot.cpp @@ -1381,6 +1381,12 @@ void CTFBot::SetMission( MissionType mission, bool resetBehaviorSystem ) //----------------------------------------------------------------------------------------------------- bool CTFBot::ShouldReEvaluateCurrentClass( void ) const { +#ifdef MAPBASE + // Don't change class mid-round in arena mode + if ( TFGameRules() && TFGameRules()->IsInArenaMode() && TFGameRules()->GetRoundState() >= GR_STATE_RND_RUNNING ) + return false; +#endif + ETFClass iCurrentClass = ( ETFClass )GetPlayerClass()->GetClassIndex(); Assert( iCurrentClass != TF_CLASS_UNDEFINED ); TFPlayerClassData_t *classData = GetPlayerClassData( iCurrentClass ); @@ -2286,7 +2292,8 @@ float CTFBot::GetTimeLeftToCapture( void ) const return TFGameRules()->GetActiveRoundTimer()->GetTimeRemaining(); } - return 0.0f; + // Instead of returning 0.0, return FLT_MAX to prevent any other bugs related to this check. + return FLT_MAX; } diff --git a/src/game/server/tf/bot/tf_bot_manager.cpp b/src/game/server/tf/bot/tf_bot_manager.cpp index c02a44a3efa..7ac9c49b22d 100644 --- a/src/game/server/tf/bot/tf_bot_manager.cpp +++ b/src/game/server/tf/bot/tf_bot_manager.cpp @@ -408,6 +408,13 @@ void CTFBotManager::MaintainBotQuota() { nTFBotsOnGameTeams++; } +#ifdef MAPBASE + else if ( TFGameRules()->IsInArenaMode() && tf_arena_use_queue.GetBool() && pPlayer->GetTeamNumber() == TEAM_SPECTATOR ) + { + // Spectators waiting in queue + nTFBotsOnGameTeams++; + } +#endif } else { @@ -471,6 +478,12 @@ void CTFBotManager::MaintainBotQuota() if ( pBot ) { pBot->SetAttribute( CTFBot::QUOTA_MANANGED ); + +#ifdef MAPBASE + // Don't try to change teams on anyone in arena mode queue + if ( pBot->GetTeamNumber() != TEAM_UNASSIGNED && TFGameRules()->IsInArenaMode() && tf_arena_use_queue.GetBool() ) + return; +#endif // join a team before we pick our class, since we use our teammates to decide what class to be pBot->HandleCommand_JoinTeam( "auto" ); @@ -632,7 +645,12 @@ CTFBot* CTFBotManager::GetAvailableBotFromPool() if ( ( pBot->GetFlags() & FL_FAKECLIENT ) == 0 ) continue; +#ifdef MAPBASE + // Spectators stay quota-managed in arena mode queue + if ( ( pBot->GetTeamNumber() == TEAM_SPECTATOR && ( !TFGameRules()->IsInArenaMode() || !tf_arena_use_queue.GetBool() ) ) || pBot->GetTeamNumber() == TEAM_UNASSIGNED ) +#else if ( pBot->GetTeamNumber() == TEAM_SPECTATOR || pBot->GetTeamNumber() == TEAM_UNASSIGNED ) +#endif { pBot->ClearAttribute( CTFBot::QUOTA_MANANGED ); return pBot; diff --git a/src/game/server/tf/nav_mesh/tf_nav_mesh.cpp b/src/game/server/tf/nav_mesh/tf_nav_mesh.cpp index 3747207e552..8cd1d6e8d6a 100644 --- a/src/game/server/tf/nav_mesh/tf_nav_mesh.cpp +++ b/src/game/server/tf/nav_mesh/tf_nav_mesh.cpp @@ -1497,6 +1497,45 @@ void CTFNavMesh::ComputeIncursionDistances( void ) } } +#ifdef MAPBASE + if ( IFuncRespawnRoomAutoList::AutoList().Count() <= 0 ) + { + // No respawn rooms, mark based on team spawn points instead + for ( int iTFTeamSpawn=0; iTFTeamSpawn( ITFTeamSpawnAutoList::AutoList()[iTFTeamSpawn] ); + + if ( spawnSpot->GetTeamNumber() == TF_TEAM_RED && isRedComputed ) + continue; + + if ( spawnSpot->GetTeamNumber() == TF_TEAM_BLUE && isBlueComputed ) + continue; + + if ( !spawnSpot->IsTriggered( NULL ) ) + continue; + + if ( spawnSpot->IsDisabled() ) + continue; + + // found a valid spawn spot, compute travel distances throughout the nav mesh + CTFNavArea *spawnArea = static_cast< CTFNavArea * >( TheTFNavMesh()->GetNearestNavArea( spawnSpot ) ); + if ( spawnArea ) + { + ComputeIncursionDistances( spawnArea, spawnSpot->GetTeamNumber() ); + + if ( spawnSpot->GetTeamNumber() == TF_TEAM_RED ) + { + isRedComputed = true; + } + else + { + isBlueComputed = true; + } + } + } + } +#endif + if ( !isRedComputed ) { Warning( "Can't compute incursion distances from the Red spawn room(s). Bots will perform poorly. This is caused by either a missing func_respawnroom, or missing info_player_teamspawn entities within the func_respawnroom.\n" ); @@ -1776,6 +1815,40 @@ void CTFNavMesh::DecorateMesh( void ) } } } + +#ifdef MAPBASE + if ( IFuncRespawnRoomAutoList::AutoList().Count() <= 0 ) + { + // No respawn rooms, mark based on team spawn points instead + for ( int iTFTeamSpawn=0; iTFTeamSpawn( ITFTeamSpawnAutoList::AutoList()[iTFTeamSpawn] ); + + if ( !spawnSpot->IsTriggered( NULL ) ) + continue; + + if ( spawnSpot->IsDisabled() ) + continue; + + CNavArea *area = GetNearestNavArea( spawnSpot, GETNAVAREA_CHECK_GROUND | GETNAVAREA_CHECK_LOS, 500.0f ); + if ( area ) + { + CTFNavArea *tfArea = static_cast(area); + + if ( spawnSpot->GetTeamNumber() == TF_TEAM_RED ) + { + tfArea->SetAttributeTF( TF_NAV_SPAWN_ROOM_RED ); + m_redSpawnRoomAreaVector.AddToTail( tfArea ); + } + else + { + tfArea->SetAttributeTF( TF_NAV_SPAWN_ROOM_BLUE ); + m_blueSpawnRoomAreaVector.AddToTail( tfArea ); + } + } + } + } +#endif // mark each spawn room area adjacent to a non-spawn room area as an exit m_redSpawnRoomExitAreaVector.RemoveAll(); diff --git a/src/game/shared/tf/tf_gamerules.cpp b/src/game/shared/tf/tf_gamerules.cpp index dbda77f5a79..aed0ffcdc43 100644 --- a/src/game/shared/tf/tf_gamerules.cpp +++ b/src/game/shared/tf/tf_gamerules.cpp @@ -14047,7 +14047,18 @@ bool CTFGameRules::BHavePlayers( void ) { // At least two in queue, we're always able to play if ( m_hArenaPlayerQueue.Count() >= 2 ) + { +#ifdef MAPBASE + // Only return true if at least one of the players isn't a bot + for ( int i = 0; i < m_hArenaPlayerQueue.Count(); i++ ) + { + if ( m_hArenaPlayerQueue[i] && !m_hArenaPlayerQueue[i]->IsBot() ) + return true; + } +#else return true; +#endif + } // Otherwise, return false if nobody is actually on a team, regardless of players ready-to-play state. if ( GetGlobalTFTeam( TF_TEAM_BLUE )->GetNumPlayers() == 0 || GetGlobalTFTeam( TF_TEAM_RED )->GetNumPlayers() == 0 ) @@ -14123,6 +14134,17 @@ int ScramblePlayersSort( CTFPlayer* const *p1, CTFPlayer* const *p2 ) return -1; } +#ifdef MAPBASE +// Sort function to deprioritize bots in arena queue +int PlayerArenaQueueSortFunc( const CHandle* pPlayer1, const CHandle* pPlayer2 ) +{ + if ( !(*pPlayer1).Get()->IsBot() && (*pPlayer2).Get()->IsBot() ) + return -1; + + return 1; +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -14474,6 +14496,11 @@ void CTFGameRules::Arena_RunTeamLogic( void ) iTeam = TF_TEAM_AUTOASSIGN; } +#ifdef MAPBASE + // Ensures bots are deprioritized + m_hArenaPlayerQueue.Sort( PlayerArenaQueueSortFunc ); +#endif + //Move people in the queue into a team. for ( int iPlayers = 0; iPlayers < iPlayersNeeded; iPlayers++ ) { @@ -17779,7 +17806,11 @@ void CTFGameRules::CollectCapturePoints( CBasePlayer *player, CUtlVector< CTeamC if ( pMaster ) { // special case hack for KotH mode to use control points that are locked at the start of the round +#ifdef MAPBASE + if ( ( IsInKothMode() || IsInArenaMode() ) && pMaster->GetNumPoints() == 1 ) +#else if ( IsInKothMode() && pMaster->GetNumPoints() == 1 ) +#endif { captureVector->AddToTail( pMaster->GetControlPoint( 0 ) ); return;