From 91231ac069585efb01b7a9f1edff82c89f52c242 Mon Sep 17 00:00:00 2001 From: RGreenlees Date: Wed, 22 May 2024 16:21:40 +0100 Subject: [PATCH] Marine Relocation * The AI Commander can now relocate to a nearby empty hive at the start of a match, or if the current base is almost lost --- main/source/mod/AvHAICommander.cpp | 318 ++++++++++++++++++++++++- main/source/mod/AvHAICommander.h | 3 + main/source/mod/AvHAIConfig.cpp | 15 ++ main/source/mod/AvHAIConfig.h | 2 + main/source/mod/AvHAIConstants.h | 2 + main/source/mod/AvHAINavigation.cpp | 1 - main/source/mod/AvHAIPlayer.cpp | 52 +++- main/source/mod/AvHAIPlayerManager.cpp | 5 + main/source/mod/AvHAIPlayerManager.h | 2 + main/source/mod/AvHAITactical.cpp | 183 +++++++++++++- main/source/mod/AvHAITactical.h | 5 + main/source/mod/AvHAITask.cpp | 32 ++- 12 files changed, 602 insertions(+), 18 deletions(-) diff --git a/main/source/mod/AvHAICommander.cpp b/main/source/mod/AvHAICommander.cpp index fa3ac2c2..80ffe51b 100644 --- a/main/source/mod/AvHAICommander.cpp +++ b/main/source/mod/AvHAICommander.cpp @@ -469,6 +469,32 @@ void AICOMM_UpdatePlayerOrders(AvHAIPlayer* pBot) } int NumPlayersOnTeam = AITAC_GetNumActivePlayersOnTeam(pBot->Player->GetTeam()); + + if (AICOMM_ShouldCommanderRelocate(pBot)) + { + Vector RelocationPoint = pBot->RelocationSpot; + + int DesiredRelocationPlayers = imini(3, (int)ceilf((float)NumPlayersOnTeam * 0.5f)); + + const AvHAIHiveDefinition* RelocationHive = AITAC_GetHiveNearestLocation(RelocationPoint); + + if (RelocationHive) + { + int NumAssignedPlayers = AICOMM_GetNumPlayersAssignedToOrder(pBot, RelocationHive->HiveEdict, ORDERPURPOSE_SECURE_HIVE); + + if (NumAssignedPlayers < DesiredRelocationPlayers) + { + edict_t* NewAssignee = AICOMM_GetPlayerWithNoOrderNearestLocation(pBot, RelocationHive->FloorLocation); + + if (!FNullEnt(NewAssignee)) + { + AICOMM_AssignNewPlayerOrder(pBot, NewAssignee, RelocationHive->HiveEdict, ORDERPURPOSE_SECURE_HIVE); + return; + } + } + } + } + int DesiredPlayers = imini(2, (int)ceilf((float)NumPlayersOnTeam *0.5f)); AvHTeamNumber BotTeam = pBot->Player->GetTeam(); @@ -498,8 +524,6 @@ void AICOMM_UpdatePlayerOrders(AvHAIPlayer* pBot) if (AICOMM_ShouldCommanderPrioritiseNodes(pBot)) { - - DeployableSearchFilter ResNodeFilter; ResNodeFilter.ReachabilityTeam = pBot->Player->GetTeam(); ResNodeFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; @@ -2164,7 +2188,7 @@ bool AICOMM_PerformNextSecureHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefini AvHAIBuildableStructure ExistingPG; AvHAIBuildableStructure ExistingTF; - Vector OutpostLocation = (ExistingStructure.IsValid() && ExistingStructure.Purpose == STRUCTURE_PURPOSE_FORTIFY) ? ExistingStructure.Location : HiveToSecure->FloorLocation; + Vector OutpostLocation = (ExistingStructure.IsValid() && (ExistingStructure.Purpose == STRUCTURE_PURPOSE_FORTIFY || ExistingStructure.Purpose == STRUCTURE_PURPOSE_BASE)) ? ExistingStructure.Location : HiveToSecure->FloorLocation; if (HiveToSecure->HiveResNodeRef && HiveToSecure->HiveResNodeRef->OwningTeam == TEAM_IND) { @@ -2375,6 +2399,23 @@ bool AICOMM_BuildInfantryPortal(AvHAIPlayer* pBot, edict_t* CommChair) bool AICOMM_CheckForNextRecycleAction(AvHAIPlayer* pBot) { + DeployableSearchFilter OldStuffFilter; + OldStuffFilter.DeployableTeam = pBot->Player->GetTeam(); + OldStuffFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + OldStuffFilter.DeployableTypes = SEARCH_ALL_STRUCTURES; + OldStuffFilter.MinSearchRadius = UTIL_MetresToGoldSrcUnits(20.0f); + OldStuffFilter.PurposeFlags = STRUCTURE_PURPOSE_BASE; + + Vector BaseLocation = (!vIsZero(pBot->RelocationSpot)) ? pBot->RelocationSpot : AITAC_GetTeamStartingLocation(pBot->Player->GetTeam()); + + AvHAIBuildableStructure OldBaseStructure = AITAC_FindFurthestDeployableFromLocation(BaseLocation, &OldStuffFilter); + + if (OldBaseStructure.IsValid()) + { + return AICOMM_RecycleStructure(pBot, &OldBaseStructure); + } + + DeployableSearchFilter UnreachableFilter; UnreachableFilter.DeployableTypes = SEARCH_ALL_STRUCTURES; UnreachableFilter.DeployableTeam = pBot->Player->GetTeam(); @@ -3252,8 +3293,36 @@ void AICOMM_CommanderThink(AvHAIPlayer* pBot) AICOMM_UpdatePlayerOrders(pBot); - if (AICOMM_CheckForNextRecycleAction(pBot)) { return; } if (AICOMM_CheckForNextSupportAction(pBot)) { return; } + + if (AICOMM_ShouldCommanderRelocate(pBot)) + { + if (vIsZero(pBot->RelocationSpot) || !AITAC_IsRelocationPointStillValid(pBot->Player->GetTeam(), pBot->RelocationSpot)) + { + pBot->RelocationSpot = AITAC_FindNewTeamRelocationPoint(pBot->Player->GetTeam()); + + if (!vIsZero(pBot->RelocationSpot)) + { + const AvHAIHiveDefinition* RelocationHive = AITAC_GetHiveNearestLocation(pBot->RelocationSpot); + + if (RelocationHive) + { + char msg[128]; + sprintf(msg, "We're relocating to %s, lads", RelocationHive->HiveName); + BotSay(pBot, true, 1.0f, msg); + } + } + } + + if (!vIsZero(pBot->RelocationSpot)) + { + if (AICOMM_CheckForNextRelocationAction(pBot)) { return; } + + return; + } + } + + if (AICOMM_CheckForNextRecycleAction(pBot)) { return; } if (AICOMM_CheckForNextBuildAction(pBot)) { return; } if (AICOMM_CheckForNextResearchAction(pBot)) { return; } if (AICOMM_CheckForNextSupplyAction(pBot)) { return; } @@ -3343,6 +3412,9 @@ const AvHAIHiveDefinition* AICOMM_GetEmptyHiveOpportunityNearestLocation(AvHAIPl const vector Hives = AITAC_GetAllHives(); + Vector TeamStartLocation = AITAC_GetTeamStartingLocation(CommanderTeam); + Vector RelocationPoint = CommanderBot->RelocationSpot; + for (auto it = Hives.begin(); it != Hives.end(); it++) { const AvHAIHiveDefinition* Hive = (*it); @@ -3351,6 +3423,16 @@ const AvHAIHiveDefinition* AICOMM_GetEmptyHiveOpportunityNearestLocation(AvHAIPl if (AICOMM_IsHiveFullySecured(CommanderBot, Hive, false)) { continue; } + // If the hive is close enough that we could siege it from our base, then don't bother securing it + if (vDist2DSq(TeamStartLocation, Hive->FloorLocation) < sqrf(UTIL_MetresToGoldSrcUnits(15.0f))) { continue; } + + // If the hive is close enough that we could siege it from our base, then don't bother securing it + if (!vIsZero(CommanderBot->RelocationSpot)) + { + // Likewise, don't secure if we're planning to move there. We will build the base instead + if (vDist2DSq(CommanderBot->RelocationSpot, Hive->FloorLocation) < sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) { continue; } + } + Vector SecureLocation = Hive->FloorLocation; DeployableSearchFilter StructureFilter; @@ -3364,7 +3446,7 @@ const AvHAIHiveDefinition* AICOMM_GetEmptyHiveOpportunityNearestLocation(AvHAIPl AvHAIBuildableStructure ExistingStructure = AITAC_FindClosestDeployableToLocation(Hive->FloorLocation, &StructureFilter); - if (ExistingStructure.IsValid() && ExistingStructure.Purpose == STRUCTURE_PURPOSE_FORTIFY) + if (ExistingStructure.IsValid() && (ExistingStructure.Purpose == STRUCTURE_PURPOSE_FORTIFY || ExistingStructure.Purpose == STRUCTURE_PURPOSE_BASE)) { SecureLocation = ExistingStructure.Location; } @@ -3615,4 +3697,230 @@ void AICOMM_ReceiveChatRequest(AvHAIPlayer* Commander, edict_t* Requestor, const { Commander->ActiveRequests.push_back(NewRequest); } +} + +bool AICOMM_ShouldCommanderRelocate(AvHAIPlayer* pBot) +{ + AvHTeamNumber Team = pBot->Player->GetTeam(); + AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(Team); + + edict_t* CurrentCommChair = AITAC_GetCommChair(Team); + + if (FNullEnt(CurrentCommChair)) { return false; } + + Vector CurrentTeamStartLocation = CurrentCommChair->v.origin; + + DeployableSearchFilter BaseStructureFilter; + BaseStructureFilter.DeployableTeam = Team; + BaseStructureFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + BaseStructureFilter.DeployableTypes = (STRUCTURE_MARINE_OBSERVATORY | STRUCTURE_MARINE_ARMSLAB | STRUCTURE_MARINE_ADVARMOURY | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY); + BaseStructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + BaseStructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); + + vector AllBaseStructures = AITAC_FindAllDeployables(CurrentTeamStartLocation, &BaseStructureFilter); + + bool bBaseWellEstablished = false; + bool bObservatoryExists = false; + + for (auto it = AllBaseStructures.begin(); it != AllBaseStructures.end(); it++) + { + if (it->StructureType == STRUCTURE_MARINE_OBSERVATORY) + { + bObservatoryExists = true; + } + + bBaseWellEstablished = true; + } + + // If we can beacon then don't relocate + if (bBaseWellEstablished && bObservatoryExists) { return false; } + + // If our base is well established, then relocate if the base is overrun and lost + if (bBaseWellEstablished) + { + DeployableSearchFilter BaseStructureFilter; + BaseStructureFilter.DeployableTypes = (STRUCTURE_MARINE_INFANTRYPORTAL | STRUCTURE_MARINE_COMMCHAIR); + BaseStructureFilter.DeployableTeam = Team; + BaseStructureFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + BaseStructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + + vector BaseStructures = AITAC_FindAllDeployables(CurrentTeamStartLocation, &BaseStructureFilter); + + bool bHasInfantryPortals = false; + bool bBaseUnderAttack = false; + + for (auto it = BaseStructures.begin(); it != BaseStructures.end(); it++) + { + AvHAIBuildableStructure ThisStructure = (*it); + + if (ThisStructure.StructureStatusFlags & STRUCTURE_STATUS_UNDERATTACK) + { + bBaseUnderAttack = true; + } + + if (ThisStructure.StructureType == STRUCTURE_MARINE_INFANTRYPORTAL) + { + bHasInfantryPortals = true; + } + } + + if (!bBaseUnderAttack && bHasInfantryPortals) { return false; } + + int EnemyStrength = 0; + int DefenderStrength = AITAC_GetNumPlayersOfTeamInArea(Team, CurrentTeamStartLocation, UTIL_MetresToGoldSrcUnits(10.0f), false, nullptr, AVH_USER3_COMMANDER_PLAYER); + + if (AIMGR_GetTeamType(EnemyTeam) == AVH_CLASS_TYPE_MARINE) + { + EnemyStrength = AITAC_GetNumPlayersOfTeamInArea(EnemyTeam, CurrentTeamStartLocation, UTIL_MetresToGoldSrcUnits(10.0f), false, nullptr, AVH_USER3_COMMANDER_PLAYER); + } + else + { + int NumSkulks = AITAC_GetNumPlayersOfTeamAndClassInArea(EnemyTeam, CurrentTeamStartLocation, UTIL_MetresToGoldSrcUnits(10.0f), false, nullptr, AVH_USER3_ALIEN_PLAYER1); + int NumFades = AITAC_GetNumPlayersOfTeamAndClassInArea(EnemyTeam, CurrentTeamStartLocation, UTIL_MetresToGoldSrcUnits(10.0f), false, nullptr, AVH_USER3_ALIEN_PLAYER4); + int NumOnos = AITAC_GetNumPlayersOfTeamAndClassInArea(EnemyTeam, CurrentTeamStartLocation, UTIL_MetresToGoldSrcUnits(10.0f), false, nullptr, AVH_USER3_ALIEN_PLAYER5); + + EnemyStrength = NumSkulks + (NumFades * 2) + (NumOnos * 2); + } + + if (EnemyStrength >= 3 && EnemyStrength >= DefenderStrength * 3) + { + return true; + } + + return false; + } + + // Don't relocate if we're already located in a hive + const AvHAIHiveDefinition* NearestHive = AITAC_GetHiveNearestLocation(CurrentTeamStartLocation); + + if (NearestHive && NearestHive->Status == HIVE_STATUS_UNBUILT && vDist2DSq(NearestHive->FloorLocation, CurrentTeamStartLocation) < sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) + { + return false; + } + + if (!vIsZero(pBot->RelocationSpot)) + { + if (AITAC_IsRelocationCompleted(Team, pBot->RelocationSpot)) + { + return false; + } + + DeployableSearchFilter InfPortalFilter; + InfPortalFilter.DeployableTeam = Team; + InfPortalFilter.DeployableTypes = STRUCTURE_MARINE_INFANTRYPORTAL; + InfPortalFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + InfPortalFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + InfPortalFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f); + + if (AITAC_DeployableExistsAtLocation(pBot->RelocationSpot, &InfPortalFilter)) + { + return true; + } + + // 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) + { + return AITAC_GetNumPlayersOfTeamInArea(Team, pBot->RelocationSpot, UTIL_MetresToGoldSrcUnits(10.0f), false, nullptr, AVH_USER3_COMMANDER_PLAYER) > 0; + } + } + + return true; +} + +bool AICOMM_CheckForNextRelocationAction(AvHAIPlayer* pBot) +{ + AvHTeamNumber BotTeam = pBot->Player->GetTeam(); + AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam); + + Vector RelocationPoint = pBot->RelocationSpot; + + if (vIsZero(RelocationPoint)) { return false; } + + edict_t* CurrentCommChair = AITAC_GetCommChair(BotTeam); + + if (FNullEnt(CurrentCommChair)) { return false; } + + DeployableSearchFilter OrigInfPortalFilter; + OrigInfPortalFilter.DeployableTeam = BotTeam; + OrigInfPortalFilter.DeployableTypes = STRUCTURE_MARINE_INFANTRYPORTAL; + OrigInfPortalFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); + + // First ensure we have one infantry portal in our starting location, in case it goes horribly wrong + if (!AITAC_DeployableExistsAtLocation(CurrentCommChair->v.origin, &OrigInfPortalFilter)) + { + return AICOMM_BuildInfantryPortal(pBot, CurrentCommChair); + } + + // Don't do anything more if we don't have anyone at the relocation point yet, but we can drop RTs if needed + if (AITAC_GetNumPlayersOfTeamInArea(BotTeam, RelocationPoint, UTIL_MetresToGoldSrcUnits(10.0f), false, nullptr, AVH_USER3_COMMANDER_PLAYER) == 0) + { + const AvHAIResourceNode* CappableNode = AICOMM_GetNearestResourceNodeCapOpportunity(BotTeam, CurrentCommChair->v.origin); + + if (CappableNode) + { + bool bSuccess = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_RESTOWER, CappableNode->Location); + + if (bSuccess || pBot->Player->GetResources() <= BALANCE_VAR(kResourceTowerCost) + 10) { return true; } + + } + + return false; + } + + DeployableSearchFilter NewBaseFilter; + NewBaseFilter.DeployableTeam = BotTeam; + NewBaseFilter.DeployableTypes = STRUCTURE_MARINE_COMMCHAIR; + NewBaseFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + NewBaseFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f); + + AvHAIBuildableStructure RelocationCommChair = AITAC_FindClosestDeployableToLocation(RelocationPoint, &NewBaseFilter); + + if (!RelocationCommChair.IsValid()) + { + Vector BuildPoint = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), RelocationPoint, UTIL_MetresToGoldSrcUnits(2.0f)); + + if (vIsZero(BuildPoint)) + { + BuildPoint = UTIL_ProjectPointToNavmesh(RelocationPoint, Vector(500.0f, 500.0f, 500.0f), GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE)); + } + + if (!vIsZero(BuildPoint)) + { + return AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_COMMCHAIR, BuildPoint, STRUCTURE_PURPOSE_BASE); + } + + return false; + } + + if (!(RelocationCommChair.StructureStatusFlags & STRUCTURE_STATUS_COMPLETED)) { return false; } + + NewBaseFilter.DeployableTypes = STRUCTURE_MARINE_INFANTRYPORTAL; + NewBaseFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(8.0f); + NewBaseFilter.IncludeStatusFlags = STRUCTURE_STATUS_NONE; + + int NumInfPortals = AITAC_GetNumDeployablesNearLocation(RelocationCommChair.Location, &NewBaseFilter); + + if (NumInfPortals < 2) + { + return AICOMM_BuildInfantryPortal(pBot, RelocationCommChair.edict); + } + + DeployableSearchFilter OldStuffFilter; + OldStuffFilter.DeployableTeam = BotTeam; + OldStuffFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + OldStuffFilter.DeployableTypes = SEARCH_ALL_STRUCTURES; + OldStuffFilter.MinSearchRadius = UTIL_MetresToGoldSrcUnits(20.0f); + OldStuffFilter.PurposeFlags = STRUCTURE_PURPOSE_BASE; + + vector OldBaseStructures = AITAC_FindAllDeployables(pBot->RelocationSpot, &OldStuffFilter); + + for (auto it = OldBaseStructures.begin(); it != OldBaseStructures.end(); it++) + { + if (it->edict != CurrentCommChair) + { + return AICOMM_RecycleStructure(pBot, &(*it)); + } + } + + return false; } \ No newline at end of file diff --git a/main/source/mod/AvHAICommander.h b/main/source/mod/AvHAICommander.h index efa22f1b..fdf8ac1b 100644 --- a/main/source/mod/AvHAICommander.h +++ b/main/source/mod/AvHAICommander.h @@ -41,6 +41,7 @@ bool AICOMM_CheckForNextSupportAction(AvHAIPlayer* pBot); bool AICOMM_CheckForNextRecycleAction(AvHAIPlayer* pBot); bool AICOMM_CheckForNextResearchAction(AvHAIPlayer* pBot); bool AICOMM_CheckForNextSupplyAction(AvHAIPlayer* pBot); +bool AICOMM_CheckForNextRelocationAction(AvHAIPlayer* pBot); void AICOMM_SetDropHealthAction(AvHAIPlayer* pBot, commander_action* Action, edict_t* Recipient); void AICOMM_SetDropAmmoAction(AvHAIPlayer* pBot, commander_action* Action, edict_t* Recipient); @@ -71,4 +72,6 @@ bool AICOMM_ShouldBeacon(AvHAIPlayer* pBot); void AICOMM_ReceiveChatRequest(AvHAIPlayer* Commander, edict_t* Requestor, const char* Request); +bool AICOMM_ShouldCommanderRelocate(AvHAIPlayer* pBot); + #endif // AVH_AI_COMMANDER_H \ No newline at end of file diff --git a/main/source/mod/AvHAIConfig.cpp b/main/source/mod/AvHAIConfig.cpp index 36822cf0..0dd5ffeb 100644 --- a/main/source/mod/AvHAIConfig.cpp +++ b/main/source/mod/AvHAIConfig.cpp @@ -12,6 +12,8 @@ BotFillTiming CurrentBotFillTiming = FILLTIMING_ALLHUMANS; float MaxAIMatchTimeMinutes = 90.0f; +bool bRelocationAllowed = false; + std::unordered_map TeamSizeMap; bot_skill BotSkillLevels[4]; @@ -90,6 +92,11 @@ bool CONFIG_IsOnosAllowed() return avh_botallowonos.value > 0; } +bool CONFIG_IsRelocationAllowed() +{ + return bRelocationAllowed; +} + float CONFIG_GetMaxStuckTime() { return avh_botmaxstucktime.value; @@ -328,6 +335,14 @@ void CONFIG_ParseConfigFile() continue; } + if (!stricmp(keyChar, "AllowRelocation")) + { + const char* ValueChar = value.c_str(); + bRelocationAllowed = (!stricmp(ValueChar, "True") || !stricmp(ValueChar, "T")); + + continue; + } + if (!stricmp(keyChar, "MaxAIMatchTime")) { float MaxMinutes = std::stof(value.c_str()); diff --git a/main/source/mod/AvHAIConfig.h b/main/source/mod/AvHAIConfig.h index 78239f97..a93dff7c 100644 --- a/main/source/mod/AvHAIConfig.h +++ b/main/source/mod/AvHAIConfig.h @@ -43,6 +43,8 @@ bool CONFIG_IsLerkAllowed(); bool CONFIG_IsFadeAllowed(); bool CONFIG_IsOnosAllowed(); +bool CONFIG_IsRelocationAllowed(); + // 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/AvHAIConstants.h b/main/source/mod/AvHAIConstants.h index 00063062..679276fe 100644 --- a/main/source/mod/AvHAIConstants.h +++ b/main/source/mod/AvHAIConstants.h @@ -814,6 +814,8 @@ typedef struct AVH_AI_PLAYER int DebugValue = 0; // Used for debugging the bot + Vector RelocationSpot = ZERO_VECTOR; // If the bot is commanding and wants to relocate, then this is where they plan to go + } AvHAIPlayer; typedef struct _AVH_AI_SQUAD diff --git a/main/source/mod/AvHAINavigation.cpp b/main/source/mod/AvHAINavigation.cpp index ae7d9c2d..02b52b1c 100644 --- a/main/source/mod/AvHAINavigation.cpp +++ b/main/source/mod/AvHAINavigation.cpp @@ -5010,7 +5010,6 @@ void WallClimbMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndP } BotMoveLookAt(pBot, LookLocation, true); - //BotDirectLookAt(pBot, LookLocation); } diff --git a/main/source/mod/AvHAIPlayer.cpp b/main/source/mod/AvHAIPlayer.cpp index 4e62f51f..48c08a1b 100644 --- a/main/source/mod/AvHAIPlayer.cpp +++ b/main/source/mod/AvHAIPlayer.cpp @@ -1926,6 +1926,11 @@ void SetNewAIPlayerRole(AvHAIPlayer* pBot, AvHAIBotRole NewRole) AITASK_ClearBotTask(pBot, &pBot->SecondaryBotTask); pBot->BotRole = NewRole; + + if (NewRole != BOT_ROLE_COMMAND && IsPlayerCommander(pBot->Edict)) + { + BotStopCommanderMode(pBot); + } } } @@ -2072,10 +2077,37 @@ bool ShouldAIPlayerTakeCommand(AvHAIPlayer* pBot) AvHPlayer* CurrentCommander = BotTeam->GetCommanderPlayer(); - // Don't go commander if we already have one, and it's not us if (CurrentCommander) { - return CurrentCommander == pBot->Player; + // Don't go commander if we already have one, and it's not us + if (CurrentCommander != pBot->Player) { return false; } + + if (!AICOMM_ShouldCommanderRelocate(pBot)) + { + // If it is us, then check if we're relocating. If we are, and we're in the old chair, then we should stop commanding + + Vector TeamRelocationPoint = pBot->RelocationSpot; + + if (vIsZero(TeamRelocationPoint)) { return true; } + + if (AITAC_IsRelocationCompleted(BotTeamNumber, TeamRelocationPoint)) + { + DeployableSearchFilter CommChairFilter; + CommChairFilter.DeployableTeam = BotTeamNumber; + CommChairFilter.DeployableTypes = STRUCTURE_MARINE_COMMCHAIR; + CommChairFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + CommChairFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f); + + AvHAIBuildableStructure RelocationCommChair = AITAC_FindClosestDeployableToLocation(pBot->RelocationSpot, &CommChairFilter); + + // Only command if we're in the relocation chair, otherwise don't + return !RelocationCommChair.IsValid() || RelocationCommChair.edict == AITAC_GetCommChair(BotTeamNumber); + } + else + { + return true; + } + } } // Don't go commander if there is another bot already taking command @@ -6793,6 +6825,8 @@ void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task vector EnemyStructures = AITAC_FindAllDeployables(ThisHive->FloorLocation, &EnemyStuffFilter); + bool bIsRelocationHive = false; // If true, the marines have relocated here so don't try to retake it: requires a base attack task instead + // Enemy hasn't built anything here, so doesn't need clearing if (ThisHive->OwningTeam != EnemyTeam && EnemyStructures.size() == 0) { continue; } @@ -6802,6 +6836,9 @@ void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task { switch (StructureIt->StructureType) { + case STRUCTURE_MARINE_INFANTRYPORTAL: + bIsRelocationHive = true; + break; case STRUCTURE_MARINE_PHASEGATE: ThisStrength += 2; break; @@ -6819,7 +6856,7 @@ void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task } } - if (!HiveToSecure || ThisStrength < MaxHiveStrength) + if (!bIsRelocationHive && (!HiveToSecure || ThisStrength < MaxHiveStrength)) { HiveToSecure = ThisHive; MaxHiveStrength = ThisStrength; @@ -6853,8 +6890,7 @@ void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task return; } } - } - + } // FIND ANY LAST ENEMIES TO KILL AND END GAME @@ -7415,19 +7451,19 @@ void AIPlayerSetWantsAndNeedsAlienTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { if (!PlayerHasAlienUpgradeOfType(pBot->Edict, HIVE_TECH_DEFENCE) && AITAC_IsAlienUpgradeAvailableForTeam(pBot->Player->GetTeam(), HIVE_TECH_DEFENCE)) { - AITASK_SetEvolveTask(pBot, Task, pBot->CurrentFloorPosition, AlienGetDesiredUpgrade(pBot, HIVE_TECH_DEFENCE), true); + pBot->Edict->v.impulse = AlienGetDesiredUpgrade(pBot, HIVE_TECH_DEFENCE); return; } if (!PlayerHasAlienUpgradeOfType(pBot->Edict, HIVE_TECH_MOVEMENT) && AITAC_IsAlienUpgradeAvailableForTeam(pBot->Player->GetTeam(), HIVE_TECH_MOVEMENT)) { - AITASK_SetEvolveTask(pBot, Task, pBot->CurrentFloorPosition, AlienGetDesiredUpgrade(pBot, HIVE_TECH_MOVEMENT), true); + pBot->Edict->v.impulse = AlienGetDesiredUpgrade(pBot, HIVE_TECH_MOVEMENT); return; } if (!PlayerHasAlienUpgradeOfType(pBot->Edict, HIVE_TECH_SENSORY) && AITAC_IsAlienUpgradeAvailableForTeam(pBot->Player->GetTeam(), HIVE_TECH_SENSORY)) { - AITASK_SetEvolveTask(pBot, Task, pBot->CurrentFloorPosition, AlienGetDesiredUpgrade(pBot, HIVE_TECH_SENSORY), true); + pBot->Edict->v.impulse = AlienGetDesiredUpgrade(pBot, HIVE_TECH_SENSORY); return; } } diff --git a/main/source/mod/AvHAIPlayerManager.cpp b/main/source/mod/AvHAIPlayerManager.cpp index fedd133a..355fc025 100644 --- a/main/source/mod/AvHAIPlayerManager.cpp +++ b/main/source/mod/AvHAIPlayerManager.cpp @@ -1590,4 +1590,9 @@ void AIMGR_SetFrameDelta(float NewValue) float AIMGR_GetFrameDelta() { return CurrentFrameDelta; +} + +float AIMGR_GetMatchLength() +{ + return (gpGlobals->time - GetGameRules()->GetTimeGameStarted()); } \ No newline at end of file diff --git a/main/source/mod/AvHAIPlayerManager.h b/main/source/mod/AvHAIPlayerManager.h index b8ec1db3..2e6b33bd 100644 --- a/main/source/mod/AvHAIPlayerManager.h +++ b/main/source/mod/AvHAIPlayerManager.h @@ -97,6 +97,8 @@ AvHClassType AIMGR_GetTeamType(const AvHTeamNumber Team); AvHTeamNumber AIMGR_GetTeamANumber(); AvHTeamNumber AIMGR_GetTeamBNumber(); +float AIMGR_GetMatchLength(); + AvHTeam* AIMGR_GetTeamRef(const AvHTeamNumber Team); // Returns all NS AI players. Does not include third-party bots diff --git a/main/source/mod/AvHAITactical.cpp b/main/source/mod/AvHAITactical.cpp index b855dead..50ad81d4 100644 --- a/main/source/mod/AvHAITactical.cpp +++ b/main/source/mod/AvHAITactical.cpp @@ -18,6 +18,7 @@ #include "AvHAIConstants.h" #include "AvHAIPlayerManager.h" #include "AvHAIConfig.h" +#include "AvHAICommander.h" #include "AvHGamerules.h" #include "AvHServerUtil.h" @@ -54,6 +55,9 @@ unsigned int ItemRefreshFrame = 0; Vector TeamAStartingLocation = ZERO_VECTOR; Vector TeamBStartingLocation = ZERO_VECTOR; +Vector TeamARelocationPoint = ZERO_VECTOR; +Vector TeamBRelocationPoint = ZERO_VECTOR; + 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 @@ -1184,6 +1188,27 @@ void AITAC_RefreshHiveData() } } +Vector AITAC_GetTeamRelocationPoint(AvHTeamNumber Team) +{ + Vector CurrentRelocationPoint = (Team == GetGameRules()->GetTeamANumber()) ? TeamARelocationPoint : TeamBRelocationPoint; + + if (!AITAC_IsRelocationPointStillValid(Team, CurrentRelocationPoint)) + { + CurrentRelocationPoint = AITAC_FindNewTeamRelocationPoint(Team); + } + + if (Team == GetGameRules()->GetTeamANumber()) + { + TeamARelocationPoint = CurrentRelocationPoint; + } + else + { + TeamBRelocationPoint = CurrentRelocationPoint; + } + + return (Team == GetGameRules()->GetTeamANumber()) ? TeamARelocationPoint : TeamBRelocationPoint; +} + Vector AITAC_GetTeamStartingLocation(AvHTeamNumber Team) { if (vIsZero(TeamAStartingLocation) || vIsZero(TeamBStartingLocation)) @@ -4379,7 +4404,6 @@ edict_t* AITAC_GetCommChair(AvHTeamNumber Team) ChairFilter.DeployableTypes = STRUCTURE_MARINE_COMMCHAIR; ChairFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; ChairFilter.DeployableTeam = Team; - ChairFilter.ReachabilityTeam = TEAM_IND; vector CommChairs = AITAC_FindAllDeployables(ZERO_VECTOR, &ChairFilter); @@ -4391,8 +4415,10 @@ edict_t* AITAC_GetCommChair(AvHTeamNumber Team) { AvHCommandStation* ChairRef = dynamic_cast((*it).EntityRef); + if (!ChairRef) { continue; } + // Idle animation will be 3 if the chair is in use (closed animation). See AvHCommandStation::GetIdleAnimation - if (ChairRef && ChairRef->GetIdleAnimation() == 3) + if (ChairRef->GetIdleAnimation() == 3) { MainCommChair = ChairRef->edict(); } @@ -5750,4 +5776,157 @@ Vector AITAC_GetGatherLocationForSquad(AvHAISquad* Squad) } return ZERO_VECTOR; +} + +Vector AITAC_FindNewTeamRelocationPoint(AvHTeamNumber Team) +{ + if (!CONFIG_IsRelocationAllowed()) { return ZERO_VECTOR; } + + AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(Team); + + // Only relocate if: + // There is a hive to relocate to with a marine ready to build + // The current base is overrun and lost + // Or we decide we want to, and the current base isn't too built up + + Vector CurrentTeamStartLocation = AITAC_GetTeamStartingLocation(Team); + + const AvHAIHiveDefinition* RelocationHive = nullptr; + float MinDist = 0.0f; + + vector AllHives = AITAC_GetAllHives(); + + for (auto it = AllHives.begin(); it != AllHives.end(); it++) + { + const AvHAIHiveDefinition* ThisHive = (*it); + + // Obviously don't relocate to an active enemy hive... + if (ThisHive->Status != HIVE_STATUS_UNBUILT) { continue; } + + // Don't relocate if we're already located close to this hive + if (vDist2DSq(CurrentTeamStartLocation, ThisHive->FloorLocation) < sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) { continue; } + + // Don't relocate if the enemy has a foothold here + DeployableSearchFilter EnemyStuff; + EnemyStuff.DeployableTeam = EnemyTeam; + EnemyStuff.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + EnemyStuff.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + EnemyStuff.DeployableTypes = (STRUCTURE_MARINE_PHASEGATE | STRUCTURE_MARINE_COMMCHAIR | STRUCTURE_MARINE_INFANTRYPORTAL | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY | STRUCTURE_ALIEN_OFFENCECHAMBER); + EnemyStuff.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); + + if (AITAC_DeployableExistsAtLocation(ThisHive->FloorLocation, &EnemyStuff)) { continue; } + + const AvHAIHiveDefinition* NearestEnemyHive = AITAC_GetActiveHiveNearestLocation(EnemyTeam, ThisHive->FloorLocation); + + float ThisDist = 0.0f; + + // Either pick an empty hive furthest from the nearest enemy hive (if they have one) + // Or the closest one to us if the enemy don't (e.g. it's MvM) + + if (NearestEnemyHive) + { + ThisDist = vDist2DSq(NearestEnemyHive->FloorLocation, ThisHive->FloorLocation); + + if (!RelocationHive || ThisDist > MinDist) + { + RelocationHive = ThisHive; + MinDist = ThisDist; + } + } + else + { + ThisDist = vDist2DSq(CurrentTeamStartLocation, ThisHive->FloorLocation); + + if (!RelocationHive || ThisDist < MinDist) + { + RelocationHive = ThisHive; + MinDist = ThisDist; + } + } + + + + + + } + + // No hives to relocate to + if (!RelocationHive) { return ZERO_VECTOR; } + + return RelocationHive->FloorLocation; + +} + +bool AITAC_IsRelocationPointStillValid(AvHTeamNumber RelocationTeam, Vector RelocationPoint) +{ + if (vIsZero(RelocationPoint)) { return false; } + + const AvHAIHiveDefinition* ThisHive = AITAC_GetHiveNearestLocation(RelocationPoint); + + // Obviously don't relocate to an active enemy hive... + if (ThisHive->Status != HIVE_STATUS_UNBUILT) { return false; } + + AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(RelocationTeam); + + // Don't relocate if the enemy has a foothold here + DeployableSearchFilter EnemyStuff; + EnemyStuff.DeployableTeam = EnemyTeam; + EnemyStuff.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + EnemyStuff.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + EnemyStuff.DeployableTypes = (STRUCTURE_MARINE_PHASEGATE | STRUCTURE_MARINE_COMMCHAIR | STRUCTURE_MARINE_INFANTRYPORTAL | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY | STRUCTURE_ALIEN_OFFENCECHAMBER); + EnemyStuff.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); + + if (AITAC_DeployableExistsAtLocation(ThisHive->FloorLocation, &EnemyStuff)) { return false; } + + return true; +} + +bool AITAC_IsRelocationCompleted(AvHTeamNumber RelocationTeam, Vector RelocationPoint) +{ + if (vIsZero(RelocationPoint)) { return true; } + + // Don't relocate if the enemy has a foothold here + DeployableSearchFilter BaseStuffFilter; + BaseStuffFilter.DeployableTeam = RelocationTeam; + BaseStuffFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + BaseStuffFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + BaseStuffFilter.DeployableTypes = (STRUCTURE_MARINE_COMMCHAIR | STRUCTURE_MARINE_INFANTRYPORTAL); + BaseStuffFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); + + edict_t* RelocationChair = nullptr; + edict_t* CurrentCommChair = AITAC_GetCommChair(RelocationTeam); + int NumInfPortals = 0; + + vector RelocationStructures = AITAC_FindAllDeployables(RelocationPoint, &BaseStuffFilter); + + for (auto it = RelocationStructures.begin(); it != RelocationStructures.end(); it++) + { + if (it->StructureType == STRUCTURE_MARINE_COMMCHAIR) + { + RelocationChair = it->edict; + } + + if (it->StructureType == STRUCTURE_MARINE_INFANTRYPORTAL) + { + NumInfPortals++; + } + } + + if (FNullEnt(RelocationChair) || NumInfPortals < 2) { return false; } + + DeployableSearchFilter OldStuffFilter; + OldStuffFilter.DeployableTeam = RelocationTeam; + OldStuffFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + OldStuffFilter.DeployableTypes = SEARCH_ALL_STRUCTURES; + OldStuffFilter.MinSearchRadius = UTIL_MetresToGoldSrcUnits(20.0f); + OldStuffFilter.PurposeFlags = STRUCTURE_PURPOSE_BASE; + + vector AllOldStructures = AITAC_FindAllDeployables(RelocationPoint, &OldStuffFilter); + + for (auto it = AllOldStructures.begin(); it != AllOldStructures.end(); it++) + { + if (it->edict != CurrentCommChair) { return false; } + } + + return true; } \ No newline at end of file diff --git a/main/source/mod/AvHAITactical.h b/main/source/mod/AvHAITactical.h index 2157a8df..54c08ffa 100644 --- a/main/source/mod/AvHAITactical.h +++ b/main/source/mod/AvHAITactical.h @@ -63,6 +63,7 @@ Vector AITAC_GetCommChairLocation(AvHTeamNumber Team); edict_t* AITAC_GetCommChair(AvHTeamNumber Team); Vector AITAC_GetTeamStartingLocation(AvHTeamNumber Team); +Vector AITAC_GetTeamRelocationPoint(AvHTeamNumber Team); AvHAIResourceNode* AITAC_GetRandomResourceNode(AvHTeamNumber SearchingTeam, const unsigned int ReachabilityFlags); @@ -211,4 +212,8 @@ AvHAISquad* AITAC_GetSquadForObjective(AvHAIPlayer* pBot, edict_t* TaskTarget, B AvHAISquad* AITAC_GetSquadForObjective(AvHAIPlayer* pBot, Vector TaskLocation, BotTaskType ObjectiveType); Vector AITAC_GetGatherLocationForSquad(AvHAISquad* Squad); +Vector AITAC_FindNewTeamRelocationPoint(AvHTeamNumber Team); +bool AITAC_IsRelocationPointStillValid(AvHTeamNumber RelocationTeam, Vector RelocationPoint); +bool AITAC_IsRelocationCompleted(AvHTeamNumber RelocationTeam, Vector RelocationPoint); + #endif \ No newline at end of file diff --git a/main/source/mod/AvHAITask.cpp b/main/source/mod/AvHAITask.cpp index 4e1217da..237607ff 100644 --- a/main/source/mod/AvHAITask.cpp +++ b/main/source/mod/AvHAITask.cpp @@ -1928,9 +1928,34 @@ void BotProgressTakeCommandTask(AvHAIPlayer* pBot) // Don't take command if we already have a commander if (pBot->Player->GetCommander()) { return; } - edict_t* CommChair = AITAC_GetCommChair(pBot->Player->GetTeam()); + edict_t* CommChair = nullptr; - if (!CommChair) { return; } + AvHTeamNumber BotTeam = pBot->Player->GetTeam(); + + Vector RelocationPoint = pBot->RelocationSpot; + + if (!vIsZero(RelocationPoint) && AITAC_IsRelocationCompleted(BotTeam, RelocationPoint)) + { + DeployableSearchFilter RelocationChairFilter; + RelocationChairFilter.DeployableTeam = BotTeam; + RelocationChairFilter.DeployableTypes = STRUCTURE_MARINE_COMMCHAIR; + RelocationChairFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + RelocationChairFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + RelocationChairFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f); + + AvHAIBuildableStructure RelocationChair = AITAC_FindClosestDeployableToLocation(RelocationPoint, &RelocationChairFilter); + + if (RelocationChair.IsValid()) + { + CommChair = RelocationChair.edict; + } + } + else + { + CommChair = AITAC_GetCommChair(BotTeam); + } + + if (FNullEnt(CommChair)) { return; } float DistFromChair = vDist2DSq(pBot->Edict->v.origin, CommChair->v.origin); @@ -2719,6 +2744,9 @@ void AlienProgressSecureHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) if (PhaseGate.IsValid()) { + // If the phase gate is next to an electrified structure, and we are a skulk or lerk, then attack + // The electrified structure instead. I might change this to avoid it altogether, but there's nothing + // wrong with trying to chip away at the TF if you're a skulk if (bAvoidElectrified) { EnemyStuffFilter.DeployableTypes = SEARCH_ALL_STRUCTURES;