diff --git a/main/source/dlls/game.cpp b/main/source/dlls/game.cpp index 99f75c0b..2ffa2765 100644 --- a/main/source/dlls/game.cpp +++ b/main/source/dlls/game.cpp @@ -129,7 +129,7 @@ cvar_t avh_botsenabled = { kvBotsEnabled,"0", FCVAR_SERVER }; // Bots can b cvar_t avh_botautomode = { kvBotAutoMode,"0", FCVAR_SERVER }; // Defines automated behaviour for adding/removing bots. 0 = manual (must add via console), 1 = automatic (auto-fills teams), 2 = balance only (only keeps teams even) cvar_t avh_botminplayers = { kvBotMinPlayers,"0", FCVAR_SERVER }; // If bots are enabled and auto mode == 1 then it will maintain this player count by adding/removing as needed cvar_t avh_botskill = { kvBotSkill,"1", FCVAR_SERVER }; // Sets the skill for the bots (0 = easiest, 3 = hardest) -cvar_t avh_botusemapdefaults = { kvBotUseMapDefaults,"0", FCVAR_SERVER }; // If bot auto mode == 1 then the min players will be taken from the config +cvar_t avh_botusemapdefaults = { kvBotUseMapDefaults,"1", FCVAR_SERVER }; // If bot auto mode == 1 then the min players will be taken from the config cvar_t avh_botcommandermode = { kvBotCommanderMode,"0", FCVAR_SERVER }; // 0 = Bots never command, 1 = If nobody takes charge, 2 = Only if no humans on team cvar_t avh_botdebugmode = { kvBotDebugMode,"0", FCVAR_SERVER }; // 0 = Regular play, 1 = Drone mode, 2 = Test Navigation mode cvar_t avh_botallowlerk = { kvBotAllowLerk,"1", FCVAR_SERVER }; // 0 = Bot will never evolve lerk, 1 = Bot will go lerk when appropriate diff --git a/main/source/mod/AIPlayers/AvHAIConstants.h b/main/source/mod/AIPlayers/AvHAIConstants.h index 025dd7c4..b2ddc76f 100644 --- a/main/source/mod/AIPlayers/AvHAIConstants.h +++ b/main/source/mod/AIPlayers/AvHAIConstants.h @@ -212,6 +212,13 @@ typedef enum _AVHAICOMBATSTRATEGY COMBAT_STRATEGY_ATTACK // Attack the enemy } AvHAICombatStrategy; +typedef enum _AVHAINAVMESHSTATUS +{ + NAVMESH_STATUS_PENDING = 0, // Waiting to try loading the navmesh + NAVMESH_STATUS_FAILED, // Failed to load the navmesh + NAVMESH_STATUS_SUCCESS // Successfully loaded the navmesh +} AvHAINavMeshStatus; + typedef struct _OFF_MESH_CONN { unsigned int ConnectionRefs[2]; diff --git a/main/source/mod/AIPlayers/AvHAIHelper.cpp b/main/source/mod/AIPlayers/AvHAIHelper.cpp index 16ce05ac..9e65aac5 100644 --- a/main/source/mod/AIPlayers/AvHAIHelper.cpp +++ b/main/source/mod/AIPlayers/AvHAIHelper.cpp @@ -230,7 +230,8 @@ bool IsEdictStructure(const edict_t* edict) bool IsEdictHive(const edict_t* edict) { - return (GetDeployableObjectTypeFromEdict(edict) != STRUCTURE_ALIEN_HIVE); + if (FNullEnt(edict)) { return false; } + return (edict->v.iuser3 == AVH_USER3_HIVE); } bool IsDamagingStructure(const edict_t* StructureEdict) diff --git a/main/source/mod/AIPlayers/AvHAINavigation.cpp b/main/source/mod/AIPlayers/AvHAINavigation.cpp index c2631957..ac7e95ff 100644 --- a/main/source/mod/AIPlayers/AvHAINavigation.cpp +++ b/main/source/mod/AIPlayers/AvHAINavigation.cpp @@ -42,6 +42,8 @@ vector BaseMapConnections; nav_mesh NavMeshes[MAX_NAV_MESHES] = { }; // Array of nav meshes. Currently only 3 are used (building, onos, and regular) nav_profile BaseNavProfiles[MAX_NAV_PROFILES] = { }; // Array of nav profiles +AvHAINavMeshStatus NavmeshStatus = NAVMESH_STATUS_PENDING; + vector MapNavHints; extern bool bNavMeshModified; @@ -833,6 +835,8 @@ void UnloadNavMeshes() BaseMapConnections.clear(); MapNavHints.clear(); + + NavmeshStatus = NAVMESH_STATUS_PENDING; } void UnloadNavigationData() @@ -860,7 +864,11 @@ bool LoadNavMesh(const char* mapname) FILE* savedFile = fopen(filename, "rb"); - if (!savedFile) { return false; } + if (!savedFile) + { + ALERT(at_console, "No nav file found for %s in the navmeshes folder.", mapname); + return false; + } LinearAllocator* m_talloc = new LinearAllocator(32000); FastLZCompressor* m_tcomp = new FastLZCompressor; @@ -874,6 +882,7 @@ bool LoadNavMesh(const char* mapname) // Error or early EOF fclose(savedFile); UnloadNavigationData(); + ALERT(at_console, "The nav file found for %s is a different version to the current bot version. Please regenerate it.", mapname); return false; } @@ -881,6 +890,7 @@ bool LoadNavMesh(const char* mapname) { fclose(savedFile); UnloadNavigationData(); + ALERT(at_console, "The nav file found for %s is a different version to the current bot version. Please regenerate it.", mapname); return false; } @@ -895,6 +905,7 @@ bool LoadNavMesh(const char* mapname) { fclose(savedFile); UnloadNavigationData(); + ALERT(at_console, "Unable to allocate memory for the nav mesh.", mapname); return false; } @@ -903,6 +914,7 @@ bool LoadNavMesh(const char* mapname) { fclose(savedFile); UnloadNavigationData(); + ALERT(at_console, "Could not initialise nav mesh, please try regenerating the nav file.\n"); return false; } @@ -917,7 +929,7 @@ bool LoadNavMesh(const char* mapname) status = NavMeshes[i].tileCache->init(TileCacheParams[i], m_talloc, m_tcomp, m_tmproc); if (dtStatusFailed(status)) { - ALERT(at_console, "Could not initialise tile cache\n"); + ALERT(at_console, "Could not initialise tile cache, please try regenerating the nav file.\n"); fclose(savedFile); UnloadNavigationData(); return false; @@ -933,6 +945,7 @@ bool LoadNavMesh(const char* mapname) { fclose(savedFile); UnloadNavigationData(); + ALERT(at_console, "The nav file has been corrupted or is out of date. Please try regenerating it.\n"); return false; } if (!tileHeader.tileRef || !tileHeader.dataSize) @@ -947,6 +960,7 @@ bool LoadNavMesh(const char* mapname) dtFree(data); fclose(savedFile); UnloadNavigationData(); + ALERT(at_console, "The nav file has been corrupted or is out of date. Please try regenerating it.\n"); return false; } @@ -969,6 +983,7 @@ bool LoadNavMesh(const char* mapname) { fclose(savedFile); UnloadNavigationData(); + ALERT(at_console, "The nav file has been corrupted or is out of date. Please try regenerating it.\n"); return false; } if (!tileHeader.tileRef || !tileHeader.dataSize) @@ -983,6 +998,7 @@ bool LoadNavMesh(const char* mapname) dtFree(data); fclose(savedFile); UnloadNavigationData(); + ALERT(at_console, "The nav file has been corrupted or is out of date. Please try regenerating it.\n"); return false; } @@ -1005,6 +1021,7 @@ bool LoadNavMesh(const char* mapname) { fclose(savedFile); UnloadNavigationData(); + ALERT(at_console, "The nav file has been corrupted or is out of date. Please try regenerating it.\n"); return false; } if (!tileHeader.tileRef || !tileHeader.dataSize) @@ -1019,6 +1036,7 @@ bool LoadNavMesh(const char* mapname) dtFree(data); fclose(savedFile); UnloadNavigationData(); + ALERT(at_console, "The nav file has been corrupted or is out of date. Please try regenerating it.\n"); return false; } @@ -1103,6 +1121,7 @@ bool LoadNavMesh(const char* mapname) if (dtStatusFailed(initStatus)) { UnloadNavigationData(); + ALERT(at_console, "The nav file has been corrupted or is out of date. Please try regenerating it.\n"); return false; } } @@ -1254,8 +1273,11 @@ bool loadNavigationData(const char* mapname) if (!LoadNavMesh(mapname)) { + NavmeshStatus = NAVMESH_STATUS_FAILED; return false; } + + NavmeshStatus = NAVMESH_STATUS_SUCCESS; UTIL_PopulateBaseNavProfiles(); @@ -1267,6 +1289,11 @@ bool NavmeshLoaded() return NavMeshes[0].navMesh != nullptr; } +AvHAINavMeshStatus NAV_GetNavMeshStatus() +{ + return NavmeshStatus; +} + Vector UTIL_GetRandomPointOnNavmesh(const AvHAIPlayer* pBot) { const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(pBot->BotNavInfo.NavProfile); @@ -3081,11 +3108,36 @@ DoorTrigger* UTIL_GetNearestDoorTrigger(const Vector Location, nav_door* Door, C void CheckAndHandleBreakableObstruction(AvHAIPlayer* pBot, const Vector MoveFrom, const Vector MoveTo) { - if (pBot->BotNavInfo.CurrentPathPoint > pBot->BotNavInfo.CurrentPath.size()) { return; } + if (pBot->BotNavInfo.CurrentPathPoint >= pBot->BotNavInfo.CurrentPath.size()) { return; } + + Vector MoveDir = UTIL_GetVectorNormal2D(MoveTo - pBot->Edict->v.origin); + + if (vIsZero(MoveDir)) + { + MoveDir = UTIL_GetForwardVector2D(pBot->Edict->v.angles); + } + + TraceResult breakableHit; + + edict_t* BlockingBreakableEdict = nullptr; + + UTIL_TraceLine(pBot->Edict->v.origin, pBot->Edict->v.origin + (MoveDir * 100.0f), dont_ignore_monsters, dont_ignore_glass, pBot->Edict->v.pContainingEntity, &breakableHit); + + if (!FNullEnt(breakableHit.pHit)) + { + if (strcmp(STRING(breakableHit.pHit->v.classname), "func_breakable") == 0) + { + BlockingBreakableEdict = breakableHit.pHit; + } + } bot_path_node CurrentPathNode = pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint]; - edict_t* BlockingBreakableEdict = UTIL_GetBreakableBlockingPathPoint(pBot, pBot->Edict->v.origin, CurrentPathNode.Location, SAMPLE_POLYAREA_GROUND, nullptr); + if (FNullEnt(BlockingBreakableEdict)) + { + + BlockingBreakableEdict = UTIL_GetBreakableBlockingPathPoint(pBot, pBot->Edict->v.origin, CurrentPathNode.Location, SAMPLE_POLYAREA_GROUND, nullptr); + } if (FNullEnt(BlockingBreakableEdict)) { @@ -5912,13 +5964,18 @@ bool MoveTo(AvHAIPlayer* pBot, const Vector Destination, const BotMoveStyle Move } else { + if (!vIsZero(BotNavInfo->UnstuckMoveLocation) && vDist2DSq(pBot->CurrentFloorPosition, BotNavInfo->UnstuckMoveLocation) < sqrf(8.0f)) + { + BotNavInfo->UnstuckMoveLocation = ZERO_VECTOR; + } + if (vIsZero(BotNavInfo->UnstuckMoveLocation)) { BotNavInfo->UnstuckMoveLocation = FindClosestPointBackOnPath(pBot); } if (!vIsZero(BotNavInfo->UnstuckMoveLocation)) - { + { MoveDirectlyTo(pBot, BotNavInfo->UnstuckMoveLocation); return true; } @@ -6020,17 +6077,17 @@ Vector FindClosestPointBackOnPath(AvHAIPlayer* pBot) // Now we find a path backwards from the valid nav mesh point to our location, trying to get as close as we can to it - dtStatus BackwardFindingStatus = FindPathClosestToPoint(pBot->BotNavInfo.NavProfile, ValidNavmeshPoint, pBot->CurrentFloorPosition, BackwardsPath, 500.0f); + dtStatus BackwardFindingStatus = FindPathClosestToPoint(pBot->BotNavInfo.NavProfile, ValidNavmeshPoint, pBot->CurrentFloorPosition, BackwardsPath, UTIL_MetresToGoldSrcUnits(50.0f)); if (dtStatusSucceed(BackwardFindingStatus)) { - Vector NewMoveLocation = ZERO_VECTOR; - Vector NewMoveFromLocation = ZERO_VECTOR; + Vector NewMoveLocation = prev(BackwardsPath.end())->Location; + Vector NewMoveFromLocation = prev(BackwardsPath.end())->FromLocation; for (auto it = BackwardsPath.rbegin(); it != BackwardsPath.rend(); it++) { - if (UTIL_QuickTrace(pBot->Edict, pBot->Edict->v.origin, it->Location)) + if (vDist2DSq(pBot->Edict->v.origin, it->Location) > sqrf(GetPlayerRadius(pBot->Edict)) && UTIL_QuickTrace(pBot->Edict, pBot->Edict->v.origin, it->Location)) { NewMoveLocation = it->Location; NewMoveFromLocation = it->FromLocation; diff --git a/main/source/mod/AIPlayers/AvHAINavigation.h b/main/source/mod/AIPlayers/AvHAINavigation.h index 1add51fd..c8b62295 100644 --- a/main/source/mod/AIPlayers/AvHAINavigation.h +++ b/main/source/mod/AIPlayers/AvHAINavigation.h @@ -152,6 +152,8 @@ bool LoadNavMesh(const char* mapname); // Unloads the nav meshes (UnloadNavMeshes()) and then reloads them (LoadNavMesh). Map data such as doors, hives, locations are not touched. void ReloadNavMeshes(); +AvHAINavMeshStatus NAV_GetNavMeshStatus(); + void SetBaseNavProfile(AvHAIPlayer* pBot); void UpdateBotMoveProfile(AvHAIPlayer* pBot, BotMoveStyle MoveStyle); void MarineUpdateBotMoveProfile(AvHAIPlayer* pBot, BotMoveStyle MoveStyle); diff --git a/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp b/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp index 510bebcc..c9ad63d1 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp +++ b/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp @@ -107,7 +107,6 @@ float AIMGR_GetCommanderAllowedTime(AvHTeamNumber Team) void AIMGR_UpdateAIPlayerCounts() { - if (!NavmeshLoaded()) { return; } for (auto BotIt = ActiveAIPlayers.begin(); BotIt != ActiveAIPlayers.end();) { @@ -137,7 +136,7 @@ void AIMGR_UpdateAIPlayerCounts() LastAIPlayerCountUpdate = gpGlobals->time; // If bots are disabled, ensure we've removed all bots from the game - if (avh_botsenabled.value == 0) + if (!AIMGR_IsBotEnabled()) { if (AIMGR_GetNumAIPlayers() > 0) { @@ -591,7 +590,7 @@ void AIDEBUG_TestPathFind() void AIMGR_UpdateAIPlayers() { // If bots are not enabled then do nothing - if (avh_botsenabled.value == 0) { return; } + if (!AIMGR_IsBotEnabled()) { return; } static float PrevTime = 0.0f; static float CurrTime = 0.0f; @@ -600,6 +599,8 @@ void AIMGR_UpdateAIPlayers() static int CurrentBotSkill = 1; + static bool bUpdateEven = false; + CurrTime = gpGlobals->time; if (CurrTime < PrevTime) @@ -642,12 +643,15 @@ void AIMGR_UpdateAIPlayers() memcpy(&bot->BotSkillSettings, &NewSkillSettings, sizeof(bot_skill)); } + int BotIndex = distance(ActiveAIPlayers.begin(), BotIt); + BotUpdateViewRotation(bot, FrameDelta); if (IS_DEDICATED_SERVER() || ThinkDelta >= BOT_MIN_FRAME_TIME) { BotDeltaTime = ThinkDelta; +#ifdef DEBUG if (bot == AIMGR_GetDebugAIPlayer()) { bool bBreak = true; // Add a break point here if you want to debug a specific bot @@ -674,6 +678,7 @@ void AIMGR_UpdateAIPlayers() } } } +#endif if (bHasRoundStarted) { @@ -707,25 +712,30 @@ void AIMGR_UpdateAIPlayers() BotResumePlay(bot); } - StartNewBotFrame(bot); + bool bIsEvenBot = !(BotIndex % 2); - UpdateBotChat(bot); - - if (avh_botdebugmode.value == 1) + if (bIsEvenBot == bUpdateEven) { - DroneThink(bot); + StartNewBotFrame(bot); + + UpdateBotChat(bot); + + if (avh_botdebugmode.value == 1) + { + DroneThink(bot); + } + else if (avh_botdebugmode.value == 2) + { + TestNavThink(bot); + } + else + { + AIPlayerThink(bot); + } + + EndBotFrame(bot); } - else if (avh_botdebugmode.value == 2) - { - TestNavThink(bot); - } - else - { - AIPlayerThink(bot); - } - - EndBotFrame(bot); - + BotUpdateDesiredViewRotation(bot); } else @@ -763,7 +773,7 @@ void AIMGR_UpdateAIPlayers() } PrevTime = CurrTime; - + bUpdateEven = !bUpdateEven; } float AIMGR_GetBotDeltaTime() @@ -920,7 +930,7 @@ void AIMGR_RemoveBotsInReadyRoom() void AIMGR_ResetRound() { - if (avh_botsenabled.value == 0 || !NavmeshLoaded()) { return; } // Do nothing if we're not using bots + if (!AIMGR_IsBotEnabled()) { return; } // Do nothing if we're not using bots // AI Players would be 0 if the round is being reset because a new game is starting. If the round is reset // from a console command, or tournament mode readying up etc, then bot logic is unaffected @@ -964,7 +974,7 @@ void AIMGR_ReloadNavigationData() void AIMGR_RoundStarted() { - if (avh_botsenabled.value == 0 || !NavmeshLoaded()) { return; } // Do nothing if we're not using bots + if (!AIMGR_IsBotEnabled()) { return; } // Do nothing if we're not using bots bHasRoundStarted = true; @@ -1038,7 +1048,12 @@ void AIMGR_ClearBotData() void AIMGR_NewMap() { - if (avh_botsenabled.value == 0) { return; } // Do nothing if we're not using bots + if (NavmeshLoaded()) + { + UnloadNavigationData(); + } + + if (!AIMGR_IsBotEnabled()) { return; } // Do nothing if we're not using bots bMapDataInitialised = false; @@ -1049,11 +1064,6 @@ void AIMGR_NewMap() AITAC_ClearMapAIData(true); - if (NavmeshLoaded()) - { - UnloadNavigationData(); - } - CONFIG_ParseConfigFile(); AIMGR_BotPrecache(); @@ -1063,6 +1073,21 @@ void AIMGR_NewMap() bPlayerSpawned = false; } +bool AIMGR_IsNavmeshLoaded() +{ + return NavmeshLoaded(); +} + +bool AIMGR_IsBotEnabled() +{ + return avh_botsenabled.value > 0 && NAV_GetNavMeshStatus() != NAVMESH_STATUS_FAILED; +} + +AvHAINavMeshStatus AIMGR_GetNavMeshStatus() +{ + return NAV_GetNavMeshStatus(); +} + void AIMGR_LoadNavigationData() { // Don't reload the nav mesh if it's already loaded @@ -1072,7 +1097,7 @@ void AIMGR_LoadNavigationData() if (!loadNavigationData(theCStrLevelName)) { - ALERT(at_console, "Failed to load navigation data for %s\n"); + ALERT(at_console, "Failed to load navigation data for %s\n", theCStrLevelName); } } diff --git a/main/source/mod/AIPlayers/AvHAIPlayerManager.h b/main/source/mod/AIPlayers/AvHAIPlayerManager.h index 0b2e4422..3c178862 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayerManager.h +++ b/main/source/mod/AIPlayers/AvHAIPlayerManager.h @@ -68,6 +68,11 @@ int AIMGR_GetNumAIPlayersWithRoleOnTeam(AvHTeamNumber Team, AvHAIBotRole Role, A int AIMGR_GetNumHumansOfClassOnTeam(AvHTeamNumber Team, AvHUser3 PlayerType); +bool AIMGR_IsNavmeshLoaded(); +AvHAINavMeshStatus AIMGR_GetNavMeshStatus(); + +bool AIMGR_IsBotEnabled(); + void AIMGR_LoadNavigationData(); void AIMGR_ReloadNavigationData(); diff --git a/main/source/mod/AIPlayers/AvHAITask.cpp b/main/source/mod/AIPlayers/AvHAITask.cpp index a3eb6d60..63f944ff 100644 --- a/main/source/mod/AIPlayers/AvHAITask.cpp +++ b/main/source/mod/AIPlayers/AvHAITask.cpp @@ -153,7 +153,7 @@ bool AITASK_IsTaskUrgent(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) case TASK_GET_AMMO: return (UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) == 0); case TASK_GET_HEALTH: - return (IsPlayerMarine(pBot->Edict)) ? (pBot->Edict->v.health < 50.0f) : (GetPlayerOverallHealthPercent(pBot->Edict) < 50.0f); + return (IsPlayerMarine(pBot->Edict)) ? (pBot->Edict->v.health < 50.0f) : (GetPlayerOverallHealthPercent(pBot->Edict) < 0.5f); case TASK_ATTACK: case TASK_GET_WEAPON: case TASK_GET_EQUIPMENT: @@ -978,13 +978,20 @@ bool AITASK_IsAlienGetHealthTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* T { if (FNullEnt(Task->TaskTarget) || (Task->TaskTarget->v.deadflag != DEAD_NO)) { return false; } + if (IsEdictHive(Task->TaskTarget)) + { + AvHAIHiveDefinition* HiveRef = AITAC_GetHiveFromEdict(Task->TaskTarget); + + if (!HiveRef || HiveRef->Status != HIVE_STATUS_BUILT) { return false; } + } + if (IsEdictStructure(Task->TaskTarget) && !UTIL_IsBuildableStructureStillReachable(pBot, Task->TaskTarget)) { return false; } if (IsEdictPlayer(Task->TaskTarget)) { if (!IsPlayerGorge(Task->TaskTarget)) { return false; } } - return (pBot->Edict->v.health < pBot->Edict->v.max_health) || (!IsPlayerSkulk(pBot->Edict) && pBot->Edict->v.armorvalue < (GetPlayerMaxArmour(pBot->Edict) * 0.7f)); + return (pBot->Edict->v.health < pBot->Edict->v.max_health) || (!IsPlayerSkulk(pBot->Edict) && pBot->Edict->v.armorvalue < (GetPlayerMaxArmour(pBot->Edict) * 0.8f)); } bool AITASK_IsAlienHealTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) diff --git a/main/source/mod/AvHGamerules.cpp b/main/source/mod/AvHGamerules.cpp index 1a95c9fa..1cc290fa 100644 --- a/main/source/mod/AvHGamerules.cpp +++ b/main/source/mod/AvHGamerules.cpp @@ -236,6 +236,8 @@ extern cvar_t avh_botsenabled; extern cvar_t avh_botautomode; extern cvar_t avh_botminplayers; extern cvar_t avh_botskill; +extern cvar_t avh_botcommandermode; +extern cvar_t avh_botdebugmode; BOOL IsSpawnPointValid( CBaseEntity *pPlayer, CBaseEntity *pSpot ); inline int FNullEnt( CBaseEntity *ent ) { return (ent == NULL) || FNullEnt( ent->edict() ); } @@ -348,15 +350,19 @@ AvHGamerules::AvHGamerules() : mTeamA(TEAM_ONE), mTeamB(TEAM_TWO) RegisterServerVariable(&avh_fastjp); RegisterServerVariable(&avh_randomrfk); RegisterServerVariable(&avh_parasiteonmap); + // AI Player cvars RegisterServerVariable(&avh_botsenabled); RegisterServerVariable(&avh_botautomode); RegisterServerVariable(&avh_botminplayers); RegisterServerVariable(&avh_botskill); + RegisterServerVariable(&avh_botdebugmode); + RegisterServerVariable(&avh_botcommandermode); REGISTER_SERVER_FUNCTION("sv_addaiplayer", []() { - if (avh_botsenabled.value == 0) + if (!AIMGR_IsBotEnabled()) { + ALERT(at_console, "Bots are disabled, or the navmesh could not be loaded."); return; } @@ -377,6 +383,12 @@ AvHGamerules::AvHGamerules() : mTeamA(TEAM_ONE), mTeamB(TEAM_TWO) REGISTER_SERVER_FUNCTION("sv_removeaiplayer", []() { + if (!AIMGR_IsBotEnabled()) + { + ALERT(at_console, "Bots are disabled, or the navmesh could not be loaded."); + return; + } + int DesiredTeam = 0; if (CMD_ARGC() >= 2) @@ -394,7 +406,14 @@ AvHGamerules::AvHGamerules() : mTeamA(TEAM_ONE), mTeamB(TEAM_TWO) REGISTER_SERVER_FUNCTION("sv_reloadnavmesh", []() { - AIMGR_ReloadNavigationData(); + if (avh_botsenabled.value > 0) + { + AIMGR_ReloadNavigationData(); + } + else + { + ALERT(at_console, "Bots are disabled, enable first before loading the navmesh."); + } }); g_VoiceGameMgr.Init(&gVoiceHelper, gpGlobals->maxClients); @@ -2323,11 +2342,6 @@ void AvHGamerules::PostWorldPrecacheReset(bool inNewMap) // Must happen after processing spawn entities this->RecalculateMapMode(); - if (avh_botsenabled.value > 0) - { - AIMGR_LoadNavigationData(); - } - // Loop through all players that are playing and respawn them bool theJustResetGameAtCountdownStart = false; @@ -3615,12 +3629,16 @@ void AvHGamerules::Think(void) this->UpdateGameTime(); if(GET_RUN_CODE(4)) - { - + { AIMGR_UpdateAIPlayerCounts(); - if (avh_botsenabled.value > 0) + if (AIMGR_IsBotEnabled()) { + if (AIMGR_GetNavMeshStatus() == NAVMESH_STATUS_PENDING) + { + AIMGR_LoadNavigationData(); + } + AIMGR_UpdateAIMapData(); AIMGR_UpdateAIPlayers();