diff --git a/main/nsbots.ini b/main/nsbots.ini index c5ce194d..2ba54365 100644 --- a/main/nsbots.ini +++ b/main/nsbots.ini @@ -14,6 +14,9 @@ MaxAIMatchTime=90 # 2 = When the round has started (after countdown) BotFillTiming=1 +# Chance the AI commander will try to relocate to an empty hive at match start +# Value is a decimal between 0.0 and 1.0, with 0 being never and 1 being always +RelocationChance=0.1 ### Skill Settings ### diff --git a/main/source/mod/AvHAICommander.cpp b/main/source/mod/AvHAICommander.cpp index 80ffe51b..c8cb052b 100644 --- a/main/source/mod/AvHAICommander.cpp +++ b/main/source/mod/AvHAICommander.cpp @@ -8,6 +8,7 @@ #include "AvHAITask.h" #include "AvHAIHelper.h" #include "AvHAIPlayerManager.h" +#include "AvHAIConfig.h" #include "AvHSharedUtil.h" #include "AvHServerUtil.h" @@ -3701,6 +3702,8 @@ void AICOMM_ReceiveChatRequest(AvHAIPlayer* Commander, edict_t* Requestor, const bool AICOMM_ShouldCommanderRelocate(AvHAIPlayer* pBot) { + if (!CONFIG_IsRelocationAllowed()) { return false; } + AvHTeamNumber Team = pBot->Player->GetTeam(); AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(Team); @@ -3818,13 +3821,13 @@ bool AICOMM_ShouldCommanderRelocate(AvHAIPlayer* pBot) } // If the match has been going on for a minute and we haven't made any progress in relocation then give up, or we risk losing everything - if (AIMGR_GetMatchLength() > 60.0f) + if (AIMGR_GetMatchLength() > 90.0f) { return AITAC_GetNumPlayersOfTeamInArea(Team, pBot->RelocationSpot, UTIL_MetresToGoldSrcUnits(10.0f), false, nullptr, AVH_USER3_COMMANDER_PLAYER) > 0; } } - return true; + return AITAC_IsRelocationEnabled(); } bool AICOMM_CheckForNextRelocationAction(AvHAIPlayer* pBot) @@ -3877,7 +3880,12 @@ bool AICOMM_CheckForNextRelocationAction(AvHAIPlayer* pBot) if (!RelocationCommChair.IsValid()) { - Vector BuildPoint = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), RelocationPoint, UTIL_MetresToGoldSrcUnits(2.0f)); + Vector BuildPoint = AITAC_GetRandomBuildHintInLocation(STRUCTURE_MARINE_COMMCHAIR, pBot->RelocationSpot, UTIL_MetresToGoldSrcUnits(10.0f)); + + if (vIsZero(BuildPoint)) + { + BuildPoint = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), RelocationPoint, UTIL_MetresToGoldSrcUnits(2.0f)); + } if (vIsZero(BuildPoint)) { diff --git a/main/source/mod/AvHAIConfig.cpp b/main/source/mod/AvHAIConfig.cpp index 0dd5ffeb..61d37055 100644 --- a/main/source/mod/AvHAIConfig.cpp +++ b/main/source/mod/AvHAIConfig.cpp @@ -12,7 +12,7 @@ BotFillTiming CurrentBotFillTiming = FILLTIMING_ALLHUMANS; float MaxAIMatchTimeMinutes = 90.0f; -bool bRelocationAllowed = false; +float RelocationChance = 0.1f; std::unordered_map TeamSizeMap; @@ -94,7 +94,12 @@ bool CONFIG_IsOnosAllowed() bool CONFIG_IsRelocationAllowed() { - return bRelocationAllowed; + return RelocationChance > 0.0f; +} + +float CONFIG_GetRelocationChance() +{ + return RelocationChance; } float CONFIG_GetMaxStuckTime() @@ -335,10 +340,10 @@ void CONFIG_ParseConfigFile() continue; } - if (!stricmp(keyChar, "AllowRelocation")) + if (!stricmp(keyChar, "RelocationChance")) { - const char* ValueChar = value.c_str(); - bRelocationAllowed = (!stricmp(ValueChar, "True") || !stricmp(ValueChar, "T")); + float RelocationValue = std::stof(value.c_str()); + RelocationChance = RelocationValue; continue; } @@ -656,7 +661,11 @@ void CONFIG_RegenerateIniFile() fprintf(NewConfigFile, "# 0 = On map load (after 5 second grace period)\n"); fprintf(NewConfigFile, "# 1 = When all humans have joined a team (i.e. no more humans left in ready room)\n"); fprintf(NewConfigFile, "# 2 = When the round has started (after countdown)\n"); - fprintf(NewConfigFile, "BotFillTiming=1\n\n\n"); + fprintf(NewConfigFile, "BotFillTiming=1\n\n"); + + fprintf(NewConfigFile, "# Chance the AI Commander will try to relocate at the beginning of the game\n"); + fprintf(NewConfigFile, "# Value is a decimal between 0.0 and 1.0, with 0 being never and 1 being always\n"); + fprintf(NewConfigFile, "RelocationChance=0.1\n\n\n"); fprintf(NewConfigFile, "### Skill Settings ###\n\n"); diff --git a/main/source/mod/AvHAIConfig.h b/main/source/mod/AvHAIConfig.h index a93dff7c..4e4ae8cf 100644 --- a/main/source/mod/AvHAIConfig.h +++ b/main/source/mod/AvHAIConfig.h @@ -44,6 +44,7 @@ bool CONFIG_IsFadeAllowed(); bool CONFIG_IsOnosAllowed(); bool CONFIG_IsRelocationAllowed(); +float CONFIG_GetRelocationChance(); // Returns the max time a bot is allowed to be stuck before suiciding (0 means forever) float CONFIG_GetMaxStuckTime(); diff --git a/main/source/mod/AvHAIPlayerManager.cpp b/main/source/mod/AvHAIPlayerManager.cpp index 355fc025..095ab8b4 100644 --- a/main/source/mod/AvHAIPlayerManager.cpp +++ b/main/source/mod/AvHAIPlayerManager.cpp @@ -930,6 +930,8 @@ void AIMGR_ResetRound() bMapDataInitialised = true; CountdownStartedTime = 0.0f; + + AITAC_DetermineRelocationEnabled(); } void AIMGR_ReloadNavigationData() diff --git a/main/source/mod/AvHAIPlayerUtil.cpp b/main/source/mod/AvHAIPlayerUtil.cpp index 180f4f0f..708bc8a3 100644 --- a/main/source/mod/AvHAIPlayerUtil.cpp +++ b/main/source/mod/AvHAIPlayerUtil.cpp @@ -73,6 +73,7 @@ bool IsPlayerClimbingWall(const edict_t* Player) bool IsPlayerInReadyRoom(const edict_t* Player) { + if (FNullEnt(Player)) { return false; } return Player->v.playerclass == PLAYMODE_READYROOM; } diff --git a/main/source/mod/AvHAITactical.cpp b/main/source/mod/AvHAITactical.cpp index 50ad81d4..13b769be 100644 --- a/main/source/mod/AvHAITactical.cpp +++ b/main/source/mod/AvHAITactical.cpp @@ -58,6 +58,8 @@ Vector TeamBStartingLocation = ZERO_VECTOR; Vector TeamARelocationPoint = ZERO_VECTOR; Vector TeamBRelocationPoint = ZERO_VECTOR; +bool bEnableRelocation = false; // For this round, should the AI commander try relocating (if appropriate)? + extern nav_mesh NavMeshes[MAX_NAV_MESHES]; // Array of nav meshes. Currently only 3 are used (building, onos, and regular) extern nav_profile BaseNavProfiles[MAX_NAV_PROFILES]; // Array of nav profiles @@ -5559,7 +5561,7 @@ void AITAC_ManageSquads() for (auto pIt = it->SquadMembers.begin(); pIt != it->SquadMembers.end();) { AvHAIPlayer* ThisPlayer = (*pIt); - if (!AITAC_IsBotPursuingSquadObjective(ThisPlayer, &(*it))) + if (!ThisPlayer || FNullEnt(ThisPlayer->Edict) || !AITAC_IsBotPursuingSquadObjective(ThisPlayer, &(*it))) { pIt = it->SquadMembers.erase(pIt); } @@ -5780,8 +5782,6 @@ Vector AITAC_GetGatherLocationForSquad(AvHAISquad* Squad) Vector AITAC_FindNewTeamRelocationPoint(AvHTeamNumber Team) { - if (!CONFIG_IsRelocationAllowed()) { return ZERO_VECTOR; } - AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(Team); // Only relocate if: @@ -5929,4 +5929,21 @@ bool AITAC_IsRelocationCompleted(AvHTeamNumber RelocationTeam, Vector Relocation } return true; +} + +bool AITAC_IsRelocationEnabled() +{ + return bEnableRelocation; +} + +void AITAC_DetermineRelocationEnabled() +{ + bEnableRelocation = false; + + if (CONFIG_IsRelocationAllowed()) + { + float RandomRoll = frandrange(0.0f, 1.0f); + + bEnableRelocation = (RandomRoll <= CONFIG_GetRelocationChance()); + } } \ No newline at end of file diff --git a/main/source/mod/AvHAITactical.h b/main/source/mod/AvHAITactical.h index 54c08ffa..d6b25ca6 100644 --- a/main/source/mod/AvHAITactical.h +++ b/main/source/mod/AvHAITactical.h @@ -216,4 +216,8 @@ Vector AITAC_FindNewTeamRelocationPoint(AvHTeamNumber Team); bool AITAC_IsRelocationPointStillValid(AvHTeamNumber RelocationTeam, Vector RelocationPoint); bool AITAC_IsRelocationCompleted(AvHTeamNumber RelocationTeam, Vector RelocationPoint); +bool AITAC_IsRelocationEnabled(); + +void AITAC_DetermineRelocationEnabled(); + #endif \ No newline at end of file diff --git a/main/source/mod/AvHAITask.cpp b/main/source/mod/AvHAITask.cpp index 237607ff..b9e8503c 100644 --- a/main/source/mod/AvHAITask.cpp +++ b/main/source/mod/AvHAITask.cpp @@ -987,6 +987,8 @@ bool AITASK_IsMarineSecureHiveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* if (!HiveToSecure || HiveToSecure->Status != HIVE_STATUS_UNBUILT) { return false; } + + // A marine bot will consider their "secure hive" task completed if the following structures have been fully built: // Phase gate (only if tech available) // Turret factory (regular or advanced) @@ -995,6 +997,12 @@ bool AITASK_IsMarineSecureHiveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* AvHTeamNumber BotTeam = pBot->Player->GetTeam(); + // We've relocated to this hive, no need to secure now + if (vDist2DSq(AITAC_GetTeamStartingLocation(BotTeam), HiveToSecure->FloorLocation) < sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) + { + return false; + } + bool bPhaseGatesAvailable = AITAC_PhaseGatesAvailable(BotTeam); bool bHasPhaseGate = false;