#include "AvHAICommander.h" #include "AvHAITactical.h" #include "AvHAIMath.h" #include "AvHAIPlayerUtil.h" #include "AvHAIWeaponHelper.h" #include "AvHAINavigation.h" #include "AvHAITask.h" #include "AvHAIHelper.h" #include "AvHAIPlayerManager.h" #include "AvHAIConfig.h" #include "AvHSharedUtil.h" #include "AvHServerUtil.h" AvHAIBuildableStructure* AICOMM_DeployStructure(AvHAIPlayer* pBot, const AvHAIDeployableStructureType StructureToDeploy, const Vector Location, StructurePurpose Purpose, bool bPlacedByHuman) { if (vIsZero(Location)) { return nullptr; } nav_profile WelderProfile = GetBaseNavProfile(MARINE_BASE_NAV_PROFILE); WelderProfile.Filters.addIncludeFlags(SAMPLE_POLYFLAGS_WELD); // Don't allow the commander to place a structure somewhere unreachable to marines if (!UTIL_PointIsReachable(WelderProfile, AITAC_GetTeamStartingLocation(pBot->Player->GetTeam()), Location, max_player_use_reach)) { return false; } AvHMessageID StructureID = UTIL_StructureTypeToImpulseCommand(StructureToDeploy); Vector BuildLocation = Location; BuildLocation.z += 4.0f; // This would be rejected if a human was trying to build here, so don't let the bot do it if (!AvHSHUGetIsSiteValidForBuild(StructureID, &BuildLocation)) { return nullptr; } string theErrorMessage; int theCost = 0; bool thePurchaseAllowed = pBot->Player->GetPurchaseAllowed(StructureID, theCost, &theErrorMessage); if (!thePurchaseAllowed) { return nullptr; } CBaseEntity* NewStructureEntity = AvHSUBuildTechForPlayer(StructureID, BuildLocation, pBot->Player); if (!NewStructureEntity) { return nullptr; } AvHAIBuildableStructure* NewStructure = AITAC_UpdateBuildableStructure(NewStructureEntity); if (NewStructure) { NewStructure->Purpose = Purpose; NewStructure->bPlacedByHuman = bPlacedByHuman; } pBot->Player->PayPurchaseCost(theCost); pBot->next_commander_action_time = gpGlobals->time + 1.0f; return NewStructure; } bool AICOMM_DeployItem(AvHAIPlayer* pBot, const AvHAIDeployableItemType ItemToDeploy, const Vector Location) { AvHMessageID StructureID = UTIL_ItemTypeToImpulseCommand(ItemToDeploy); Vector BuildLocation = Location; string theErrorMessage; int theCost = 0; bool thePurchaseAllowed = pBot->Player->GetPurchaseAllowed(StructureID, theCost, &theErrorMessage); if (!thePurchaseAllowed) { return false; } if (!AvHSHUGetIsSiteValidForBuild(StructureID, &BuildLocation)) { return false; } CBaseEntity* NewItem = AvHSUBuildTechForPlayer(StructureID, BuildLocation, pBot->Player); if (!NewItem) { return false; } AITAC_UpdateMarineItem(NewItem, ItemToDeploy); pBot->Player->PayPurchaseCost(theCost); pBot->next_commander_action_time = gpGlobals->time + 0.2f; return true; } bool AICOMM_ResearchTech(AvHAIPlayer* pBot, AvHAIBuildableStructure* StructureToResearch, AvHMessageID Research) { if (!StructureToResearch || FNullEnt(StructureToResearch->edict) || !StructureToResearch->EntityRef) { return false; } // Don't do anything if the structure is being recycled, or we DON'T want to recycle but the structure is already busy if (StructureToResearch->EntityRef->GetIsRecycling() || (Research != BUILD_RECYCLE && StructureToResearch->EntityRef->GetIsResearching())) { return false; } int StructureIndex = ENTINDEX(StructureToResearch->edict); if (StructureIndex < 0) { return false; } if (!StructureToResearch->EntityRef->GetIsTechnologyAvailable(Research)) { return false; } AvHTeam* CommanderTeamRef = AIMGR_GetTeamRef(pBot->Player->GetTeam()); if (!CommanderTeamRef) { return false; } AvHResearchManager& theResearchManager = CommanderTeamRef->GetResearchManager(); bool theIsResearchable = false; int theResearchCost = 0.0f; float theResearchTime = 0.0f; theResearchManager.GetResearchInfo(Research, theIsResearchable, theResearchCost, theResearchTime); if (pBot->Player->GetResources() < theResearchCost) { return false; } pBot->Player->SetSelection(StructureIndex, true); pBot->Button |= IN_ATTACK2; pBot->Impulse = Research; pBot->next_commander_action_time = gpGlobals->time + 1.0f; return true; } bool AICOMM_UpgradeStructure(AvHAIPlayer* pBot, AvHAIBuildableStructure* StructureToUpgrade) { AvHMessageID UpgradeImpulse = MESSAGE_NULL; switch (StructureToUpgrade->StructureType) { case STRUCTURE_MARINE_ARMOURY: UpgradeImpulse = ARMORY_UPGRADE; break; case STRUCTURE_MARINE_TURRETFACTORY: UpgradeImpulse = TURRET_FACTORY_UPGRADE; break; default: return false; } return AICOMM_ResearchTech(pBot, StructureToUpgrade, UpgradeImpulse); } bool AICOMM_RecycleStructure(AvHAIPlayer* pBot, AvHAIBuildableStructure* StructureToRecycle) { if (!StructureToRecycle || StructureToRecycle->StructureType == STRUCTURE_MARINE_DEPLOYEDMINE || UTIL_StructureIsRecycling(StructureToRecycle->edict)) { return false; } return AICOMM_ResearchTech(pBot, StructureToRecycle, BUILD_RECYCLE); } bool AICOMM_IssueMovementOrder(AvHAIPlayer* pBot, edict_t* Recipient, const Vector MoveLocation) { if (FNullEnt(Recipient) || !IsPlayerActiveInGame(Recipient) || vIsZero(MoveLocation)) { return false; } int ReceiverIndex = ENTINDEX(Recipient); if (ReceiverIndex <= 0) { return false; } AvHOrder NewOrder; NewOrder.SetOrderType(ORDERTYPEL_MOVE); NewOrder.SetReceiver(ReceiverIndex); NewOrder.SetLocation(MoveLocation); NewOrder.SetOrderID(); pBot->Player->SetSelection(ReceiverIndex, true); pBot->Player->GiveOrderToSelection(NewOrder); return true; } bool AICOMM_IssueBuildOrder(AvHAIPlayer* pBot, edict_t* Recipient, edict_t* TargetStructure) { if (FNullEnt(Recipient) || !IsPlayerActiveInGame(Recipient) || FNullEnt(TargetStructure) || UTIL_StructureIsFullyBuilt(TargetStructure)) { return false; } int ReceiverIndex = ENTINDEX(Recipient); int TargetIndex = ENTINDEX(TargetStructure); if (ReceiverIndex <= 0 || TargetIndex <= 0) { return false; } AvHBaseBuildable* BuildingRef = dynamic_cast(CBaseEntity::Instance(TargetStructure)); if (!BuildingRef) { return false; } AvHOrder NewOrder; NewOrder.SetOrderType(ORDERTYPET_BUILD); NewOrder.SetReceiver(ReceiverIndex); NewOrder.SetTargetIndex(TargetIndex); NewOrder.SetUser3TargetType((AvHUser3)TargetStructure->v.iuser3); NewOrder.SetOrderTargetType(ORDERTARGETTYPE_LOCATION); NewOrder.SetLocation(TargetStructure->v.origin); NewOrder.SetOrderID(); pBot->Player->SetSelection(ReceiverIndex, true); pBot->Player->GiveOrderToSelection(NewOrder); return true; } void AICOMM_AssignNewPlayerOrder(AvHAIPlayer* pBot, edict_t* Assignee, edict_t* TargetEntity, AvHAIOrderPurpose OrderPurpose) { if (FNullEnt(Assignee) || FNullEnt(TargetEntity) || OrderPurpose == ORDERPURPOSE_NONE) { return; } // Clear any existing order we have for this player for (auto it = pBot->ActiveOrders.begin(); it != pBot->ActiveOrders.end();) { if (it->Assignee == Assignee) { it = pBot->ActiveOrders.erase(it); } else { it++; } } ai_commander_order NewOrder; NewOrder.Assignee = Assignee; NewOrder.OrderTarget = TargetEntity; NewOrder.OrderLocation = TargetEntity->v.origin; NewOrder.OrderPurpose = OrderPurpose; NewOrder.LastReminderTime = 0.0f; NewOrder.LastPlayerDistance = 0.0f; pBot->ActiveOrders.push_back(NewOrder); if (AICOMM_DoesPlayerOrderNeedReminder(pBot, &NewOrder)) { AICOMM_IssueOrderForAssignedJob(pBot, &NewOrder); } } void AICOMM_AssignNewPlayerOrder(AvHAIPlayer* pBot, edict_t* Assignee, Vector OrderLocation, AvHAIOrderPurpose OrderPurpose) { if (FNullEnt(Assignee) || vIsZero(OrderLocation) || OrderPurpose == ORDERPURPOSE_NONE) { return; } // Clear any existing order we have for this player for (auto it = pBot->ActiveOrders.begin(); it != pBot->ActiveOrders.end();) { if (it->Assignee == Assignee) { it = pBot->ActiveOrders.erase(it); } else { it++; } } ai_commander_order NewOrder; NewOrder.Assignee = Assignee; NewOrder.OrderLocation = OrderLocation; NewOrder.OrderPurpose = OrderPurpose; NewOrder.LastReminderTime = 0.0f; NewOrder.LastPlayerDistance = 0.0f; pBot->ActiveOrders.push_back(NewOrder); if (AICOMM_DoesPlayerOrderNeedReminder(pBot, &NewOrder)) { AICOMM_IssueOrderForAssignedJob(pBot, &NewOrder); } } void AICOMM_IssueOrderForAssignedJob(AvHAIPlayer* pBot, ai_commander_order* Order) { if (Order->OrderPurpose == ORDERPURPOSE_BUILD_GUARDPOST || Order->OrderPurpose == ORDERPURPOSE_BUILD_SIEGE || Order->OrderPurpose == ORDERPURPOSE_BUILD_OUTPOST || Order->OrderPurpose == ORDERPURPOSE_BUILD_MAINBASE) { Vector MoveLoc = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), Order->OrderLocation, UTIL_MetresToGoldSrcUnits(1.0f)); AICOMM_IssueMovementOrder(pBot, Order->Assignee, MoveLoc); Order->LastReminderTime = gpGlobals->time; Order->LastPlayerDistance = vDist2DSq(Order->Assignee->v.origin, MoveLoc); Order->OrderLocation = MoveLoc; return; } if (Order->OrderPurpose == ORDERPURPOSE_SIEGE_HIVE || Order->OrderPurpose == ORDERPURPOSE_SECURE_HIVE) { bool bIsSiegeHiveOrder = (Order->OrderPurpose == ORDERPURPOSE_SIEGE_HIVE); const AvHAIHiveDefinition* Hive = AITAC_GetHiveFromEdict(Order->OrderTarget); if (Hive) { Vector OrderLocation = Hive->FloorLocation; DeployableSearchFilter StructureFilter; StructureFilter.DeployableTeam = pBot->Player->GetTeam(); StructureFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY; StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; StructureFilter.MaxSearchRadius = (bIsSiegeHiveOrder) ? UTIL_MetresToGoldSrcUnits(25.0f) : UTIL_MetresToGoldSrcUnits(10.0f); AvHAIBuildableStructure NearestToHive = AITAC_FindClosestDeployableToLocation(Hive->Location, &StructureFilter); if (NearestToHive.IsValid()) { if (!(NearestToHive.StructureStatusFlags & STRUCTURE_STATUS_COMPLETED)) { AICOMM_IssueBuildOrder(pBot, Order->Assignee, NearestToHive.edict); Order->LastReminderTime = gpGlobals->time; Order->LastPlayerDistance = vDist2DSq(Order->Assignee->v.origin, NearestToHive.Location); Order->OrderLocation = NearestToHive.Location; return; } else { Vector MoveLoc = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), NearestToHive.Location, UTIL_MetresToGoldSrcUnits(3.0f)); AICOMM_IssueMovementOrder(pBot, Order->Assignee, MoveLoc); Order->LastReminderTime = gpGlobals->time; Order->LastPlayerDistance = vDist2DSq(Order->Assignee->v.origin, MoveLoc); Order->OrderLocation = MoveLoc; return; } } else { Vector MoveLoc = (bIsSiegeHiveOrder) ? UTIL_GetRandomPointOnNavmeshInDonutIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), Hive->FloorLocation, UTIL_MetresToGoldSrcUnits(10.0f), UTIL_MetresToGoldSrcUnits(20.0f)) : Hive->FloorLocation; AICOMM_IssueMovementOrder(pBot, Order->Assignee, MoveLoc); Order->LastReminderTime = gpGlobals->time; Order->LastPlayerDistance = vDist2DSq(Order->Assignee->v.origin, MoveLoc); Order->OrderLocation = MoveLoc; return; } } Order->LastReminderTime = gpGlobals->time; return; } if (Order->OrderPurpose == ORDERPURPOSE_SECURE_RESNODE) { const AvHAIResourceNode* ResNode = AITAC_GetResourceNodeFromEdict(Order->OrderTarget); if (ResNode) { if (ResNode->OwningTeam == pBot->Player->GetTeam() && ResNode->ActiveTowerEntity && !UTIL_StructureIsFullyBuilt(ResNode->ActiveTowerEntity)) { AICOMM_IssueBuildOrder(pBot, Order->Assignee, ResNode->ActiveTowerEntity); Order->LastReminderTime = gpGlobals->time; Order->LastPlayerDistance = vDist2DSq(Order->Assignee->v.origin, ResNode->Location); Order->OrderLocation = ResNode->Location; return; } Vector MoveLoc = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), ResNode->Location, UTIL_MetresToGoldSrcUnits(3.0f)); AICOMM_IssueMovementOrder(pBot, Order->Assignee, MoveLoc); Order->LastReminderTime = gpGlobals->time; Order->LastPlayerDistance = vDist2DSq(Order->Assignee->v.origin, MoveLoc); Order->OrderLocation = MoveLoc; return; } return; } } int AICOMM_GetNumPlayersAssignedToOrderLocation(AvHAIPlayer* pBot, Vector OrderLocation, AvHAIOrderPurpose OrderPurpose) { int Result = 0; for (auto it = pBot->ActiveOrders.begin(); it != pBot->ActiveOrders.end(); it++) { if ((OrderPurpose == ORDERPURPOSE_NONE || it->OrderPurpose == OrderPurpose) && vDist2DSq(it->OrderLocation, OrderLocation) < sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) { Result++; } } return Result; } int AICOMM_GetNumPlayersAssignedToOrderType(AvHAIPlayer* pBot, AvHAIOrderPurpose OrderPurpose) { int Result = 0; for (auto it = pBot->ActiveOrders.begin(); it != pBot->ActiveOrders.end(); it++) { if (it->OrderPurpose == OrderPurpose) { Result++; } } return Result; } int AICOMM_GetNumPlayersAssignedToOrder(AvHAIPlayer* pBot, edict_t* TargetEntity, AvHAIOrderPurpose OrderPurpose) { int Result = 0; for (auto it = pBot->ActiveOrders.begin(); it != pBot->ActiveOrders.end(); it++) { if (it->OrderTarget == TargetEntity && it->OrderPurpose == OrderPurpose) { Result++; } } return Result; } AvHAIMarineBase* AICOMM_GetNearestBaseToLocation(AvHAIPlayer* pBot, Vector SearchLocation) { AvHAIMarineBase* Result = nullptr; float MinDist = 0.0f; for (auto it = pBot->Bases.begin(); it != pBot->Bases.end(); it++) { AvHAIMarineBase* ThisBase = &(*it); float ThisDist = vDist2DSq(ThisBase->BaseLocation, SearchLocation); if (!Result || ThisDist < MinDist) { Result = ThisBase; MinDist = ThisDist; } } return Result; } bool AICOMM_IsOrderStillValid(AvHAIPlayer* pBot, ai_commander_order* Order) { if (FNullEnt(Order->Assignee) || (FNullEnt(Order->OrderTarget) && vIsZero(Order->OrderLocation)) || !IsPlayerActiveInGame(Order->Assignee) || Order->OrderPurpose == ORDERPURPOSE_NONE) { return false; } switch (Order->OrderPurpose) { case ORDERPURPOSE_SECURE_HIVE: { const AvHAIHiveDefinition* Hive = AITAC_GetHiveFromEdict(Order->OrderTarget); if (!Hive || Hive->Status != HIVE_STATUS_UNBUILT) { return false; } return !AICOMM_IsHiveFullySecured(pBot, Hive, false); } break; case ORDERPURPOSE_SIEGE_HIVE: { const AvHAIHiveDefinition* Hive = AITAC_GetHiveFromEdict(Order->OrderTarget); // Hive has been destroyed, no longer needs sieging return (Hive && Hive->Status != HIVE_STATUS_UNBUILT); } break; case ORDERPURPOSE_SECURE_RESNODE: { if (!AICOMM_ShouldCommanderPrioritiseNodes(pBot)) { return false; } const AvHAIResourceNode* ResNode = AITAC_GetResourceNodeFromEdict(Order->OrderTarget); if (!ResNode) { return false; } return (ResNode->OwningTeam != pBot->Player->GetTeam() || !ResNode->ActiveTowerEntity || !UTIL_StructureIsFullyBuilt(ResNode->ActiveTowerEntity)); } break; case ORDERPURPOSE_BUILD_SIEGE: case ORDERPURPOSE_BUILD_OUTPOST: case ORDERPURPOSE_BUILD_GUARDPOST: case ORDERPURPOSE_BUILD_MAINBASE: { AvHAIMarineBase* NearestBase = AICOMM_GetNearestBaseToLocation(pBot, Order->OrderLocation); if (!NearestBase || NearestBase->bRecycleBase || !NearestBase->bIsActive || !NearestBase->bCanBeBuiltOut) { return false; } // If this is a main base, it's established (has 2 or more infantry portals) and our active comm chair is based there, then we don't need anyone ordered to this location any more if (NearestBase->BaseType == MARINE_BASE_MAINBASE && NearestBase->bBaseInitialised && vDist2DSq(NearestBase->BaseLocation, AITAC_GetCommChairLocation(pBot->Player->GetTeam())) < sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) { return false; } if (vDist2DSq(NearestBase->BaseLocation, Order->OrderLocation) > sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) { return false; } return true; } break; default: return false; } return false; } bool AICOMM_DoesPlayerOrderNeedReminder(AvHAIPlayer* pBot, ai_commander_order* Order) { // For now, disable reminders as it is annoying for humans and pointless for bots who should obey it always anyway return (Order->LastReminderTime < 0.1f); float NewDist = vDist2DSq(Order->Assignee->v.origin, Order->OrderLocation); float OldDist = Order->LastPlayerDistance; Order->LastPlayerDistance = NewDist; if (gpGlobals->time - Order->LastReminderTime < MIN_COMMANDER_REMIND_TIME) { return false; } if (Order->OrderPurpose == ORDERPURPOSE_SECURE_RESNODE) { if (vDist2DSq(Order->Assignee->v.origin, Order->OrderTarget->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) { return false; } } if (Order->OrderPurpose == ORDERPURPOSE_SIEGE_HIVE) { if (vDist2DSq(Order->Assignee->v.origin, Order->OrderTarget->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(25.0f))) { return false; } } if (Order->OrderPurpose == ORDERPURPOSE_SECURE_HIVE) { if (vDist2DSq(Order->Assignee->v.origin, Order->OrderTarget->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(15.0f))) { return false; } } return NewDist >= OldDist; } bool AICOMM_ShouldCommanderPrioritiseNodes(AvHAIPlayer* pBot) { AvHTeamNumber BotTeam = pBot->Player->GetTeam(); AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam); int NumOwnedNodes = 0; int NumEligibleNodes = 0; int NumFreeNodes = 0; // First get ours and the enemy's ownership of all eligible nodes (we can reach them, and they're in the enemy base) vector AllNodes = AITAC_GetAllReachableResourceNodes(BotTeam); for (auto it = AllNodes.begin(); it != AllNodes.end(); it++) { AvHAIResourceNode* ThisNode = (*it); // We don't care about the node at marine spawn or enemy hives, ignore then in our calculations if (ThisNode->OwningTeam == EnemyTeam && ThisNode->bIsBaseNode) { continue; } if (ThisNode->OwningTeam == TEAM_IND) { NumFreeNodes++; } NumEligibleNodes++; if (ThisNode->OwningTeam == BotTeam) { NumOwnedNodes++; } } int NumDesiredNodes = imini(4, (int)ceilf((float)NumEligibleNodes * 0.5f)); int NumNodesLeft = NumEligibleNodes - NumOwnedNodes; if (NumNodesLeft == 0) { return false; } return NumOwnedNodes < NumDesiredNodes || NumFreeNodes > 1; } void AICOMM_UpdatePlayerOrders(AvHAIPlayer* pBot) { // Clear out any orders which aren't relevant any more for (auto it = pBot->ActiveOrders.begin(); it != pBot->ActiveOrders.end();) { if (!AICOMM_IsOrderStillValid(pBot, &(*it))) { it = pBot->ActiveOrders.erase(it); } else { // If the person we're ordering around isn't doing as they're told, then issue them a reminder if (AICOMM_DoesPlayerOrderNeedReminder(pBot, &(*it))) { AICOMM_IssueOrderForAssignedJob(pBot, &(*it)); } it++; } } AvHTeamNumber BotTeam = pBot->Player->GetTeam(); AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam); int NumPlayersOnTeam = AITAC_GetNumActivePlayersOnTeam(pBot->Player->GetTeam()); for (auto it = pBot->Bases.begin(); it != pBot->Bases.end(); it++) { AvHAIMarineBase* ThisBase = &(*it); if (ThisBase->BaseType == MARINE_BASE_MAINBASE && !ThisBase->bIsBaseEstablished) { bool bCommChairInBase = vDist2DSq(ThisBase->BaseLocation, AITAC_GetCommChairLocation(BotTeam)) < sqrf(UTIL_MetresToGoldSrcUnits(10.0f)); if (bCommChairInBase && ThisBase->bIsBaseEstablished) { continue; } // Either the base isn't established (has comm chair and infantry portals), or the active comm chair isn't in the base (we're relocating) int NumDesiredBuilders = ThisBase->bIsBaseEstablished ? 1 : 2; NumDesiredBuilders = imini(NumDesiredBuilders, NumPlayersOnTeam); int NumBuilders = ThisBase->NumBuilders + AICOMM_GetNumPlayersAssignedToOrderLocation(pBot, ThisBase->BaseLocation, ORDERPURPOSE_BUILD_MAINBASE); if (NumBuilders < NumDesiredBuilders) { edict_t* NewBuilder = AICOMM_GetPlayerWithoutSpecificOrderNearestLocation(pBot, ThisBase->BaseLocation, ORDERPURPOSE_BUILD_MAINBASE); if (!FNullEnt(NewBuilder)) { AICOMM_AssignNewPlayerOrder(pBot, NewBuilder, ThisBase->BaseLocation, ORDERPURPOSE_BUILD_MAINBASE); return; } } else { continue; } } } int MinResGatherers = imini(3, (int)ceilf(NumPlayersOnTeam * 0.35f)); if (AICOMM_ShouldCommanderPrioritiseNodes(pBot) && AICOMM_GetNumPlayersAssignedToOrderType(pBot, ORDERPURPOSE_SECURE_RESNODE) < MinResGatherers) { DeployableSearchFilter ResNodeFilter; ResNodeFilter.ReachabilityTeam = pBot->Player->GetTeam(); ResNodeFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; vector EligibleResNodes = AITAC_GetAllMatchingResourceNodes(ZERO_VECTOR, &ResNodeFilter); AvHAIResourceNode* NearestNode = nullptr; float MinDist = 0.0f; Vector TeamStartingLocation = AITAC_GetCommChairLocation(BotTeam); int NumResNodesOrdered = 0; for (auto it = EligibleResNodes.begin(); it != EligibleResNodes.end(); it++) { AvHAIResourceNode* ThisNode = (*it); if (!ThisNode || ThisNode->OwningTeam == BotTeam) { continue; } int NumAssignedPlayers = AICOMM_GetNumPlayersAssignedToOrder(pBot, ThisNode->ResourceEntity->edict(), ORDERPURPOSE_SECURE_RESNODE); if (NumAssignedPlayers > 0) { continue; } float ThisDist = vDist2DSq(TeamStartingLocation, ThisNode->Location); if (ThisNode->OwningTeam == EnemyTeam) { ThisDist *= 2.0f; } if (!NearestNode || ThisDist < MinDist) { NearestNode = ThisNode; MinDist = ThisDist; } } if (NearestNode) { edict_t* NewAssignee = AICOMM_GetPlayerWithNoOrderNearestLocation(pBot, NearestNode->Location); if (!FNullEnt(NewAssignee)) { AICOMM_AssignNewPlayerOrder(pBot, NewAssignee, NearestNode->ResourceEntity->edict(), ORDERPURPOSE_SECURE_RESNODE); return; } } } AvHAIMarineBase* SiegeBase = nullptr; AvHAIMarineBase* Outpost = nullptr; AvHAIMarineBase* Guardpost = nullptr; for (auto it = pBot->Bases.begin(); it != pBot->Bases.end(); it++) { AvHAIMarineBase* ThisBase = &(*it); if (ThisBase->bRecycleBase || !ThisBase->bIsActive || !ThisBase->bCanBeBuiltOut) { continue; } if (ThisBase->BaseType == MARINE_BASE_MAINBASE) { continue; } int NumDesiredBuilders = 0; AvHAIOrderPurpose NewOrderPurpose = ORDERPURPOSE_NONE; switch (ThisBase->BaseType) { case MARINE_BASE_SIEGE: NumDesiredBuilders = imini(3, (int)ceilf((float)NumPlayersOnTeam * 0.5f)); NewOrderPurpose = ORDERPURPOSE_BUILD_SIEGE; break; case MARINE_BASE_OUTPOST: NumDesiredBuilders = imini(2, (int)ceilf((float)NumPlayersOnTeam * 0.25f)); NewOrderPurpose = ORDERPURPOSE_BUILD_OUTPOST; break; case MARINE_BASE_GUARDPOST: NumDesiredBuilders = 1; NewOrderPurpose = ORDERPURPOSE_BUILD_GUARDPOST; break; default: continue; } int NumPlayersThere = ThisBase->NumBuilders; int Deficit = NumDesiredBuilders - NumPlayersThere; if (Deficit > 0) { int NumOrders = AICOMM_GetNumPlayersAssignedToOrderLocation(pBot, ThisBase->BaseLocation, NewOrderPurpose); Deficit -= NumOrders; if (Deficit > 0) { if (ThisBase->BaseType == MARINE_BASE_SIEGE) { SiegeBase = ThisBase; } else if (ThisBase->BaseType == MARINE_BASE_OUTPOST) { Outpost = ThisBase; } else if (ThisBase->BaseType == MARINE_BASE_GUARDPOST) { Guardpost = ThisBase; } } } } if (SiegeBase) { edict_t* NearestEligiblePlayer = AICOMM_GetPlayerWithNoOrderNearestLocation(pBot, SiegeBase->BaseLocation); if (!FNullEnt(NearestEligiblePlayer)) { AICOMM_AssignNewPlayerOrder(pBot, NearestEligiblePlayer, SiegeBase->BaseLocation, ORDERPURPOSE_BUILD_SIEGE); return; } } if (Outpost) { edict_t* NearestEligiblePlayer = AICOMM_GetPlayerWithNoOrderNearestLocation(pBot, Outpost->BaseLocation); if (!FNullEnt(NearestEligiblePlayer)) { AICOMM_AssignNewPlayerOrder(pBot, NearestEligiblePlayer, Outpost->BaseLocation, ORDERPURPOSE_BUILD_OUTPOST); return; } } if (Guardpost) { edict_t* NearestEligiblePlayer = AICOMM_GetPlayerWithNoOrderNearestLocation(pBot, Guardpost->BaseLocation); if (!FNullEnt(NearestEligiblePlayer)) { AICOMM_AssignNewPlayerOrder(pBot, NearestEligiblePlayer, Guardpost->BaseLocation, ORDERPURPOSE_BUILD_GUARDPOST); return; } } } edict_t* AICOMM_GetPlayerWithoutSpecificOrderNearestLocation(AvHAIPlayer* pBot, Vector SearchLocation, AvHAIOrderPurpose OrderPurpose) { edict_t* Result = nullptr; float MinDist = 0.0f; vector PlayerList = AIMGR_GetAllPlayersOnTeam(pBot->Player->GetTeam()); // First, remove all players who are dead or otherwise not active with boots on the ground (e.g. commander, or being digested) for (auto it = PlayerList.begin(); it != PlayerList.end();) { AvHPlayer* PlayerRef = (*it); AvHAIPlayer* AIPlayerRef = AIMGR_GetBotRefFromPlayer(PlayerRef); // Don't give orders to incapacitated players, or if the bot is currently playing a defensive role. Stops the commander sending everyone out // and leaving nobody at base if (!IsPlayerActiveInGame(PlayerRef->edict()) || (AIPlayerRef && AIPlayerRef->BotRole == BOT_ROLE_SWEEPER)) { it = PlayerList.erase(it); } else { it++; } } // Next, erase all players with orders so we only have a list of players without orders assigned to them for (auto it = pBot->ActiveOrders.begin(); it != pBot->ActiveOrders.end(); it++) { if (it->OrderPurpose != OrderPurpose) { continue; } AvHPlayer* ThisPlayer = dynamic_cast(CBaseEntity::Instance(it->Assignee)); if (!ThisPlayer) { continue; } std::vector::iterator FoundPlayer = std::find(PlayerList.begin(), PlayerList.end(), ThisPlayer); if (FoundPlayer != PlayerList.end()) { PlayerList.erase(FoundPlayer); } } // Now rank them by distance and return the result for (auto it = PlayerList.begin(); it != PlayerList.end(); it++) { edict_t* PlayerEdict = (*it)->edict(); float ThisDist = vDist2DSq(PlayerEdict->v.origin, SearchLocation); if (!Result || ThisDist < MinDist) { Result = PlayerEdict; MinDist = ThisDist; } } return Result; } edict_t* AICOMM_GetPlayerWithNoOrderNearestLocation(AvHAIPlayer* pBot, Vector SearchLocation) { edict_t* Result = nullptr; float MinDist = 0.0f; vector PlayerList = AIMGR_GetAllPlayersOnTeam(pBot->Player->GetTeam()); // First, remove all players who are dead or otherwise not active with boots on the ground (e.g. commander, or being digested) for (auto it = PlayerList.begin(); it != PlayerList.end();) { AvHPlayer* PlayerRef = (*it); AvHAIPlayer* AIPlayerRef = AIMGR_GetBotRefFromPlayer(PlayerRef); // Don't give orders to incapacitated players, or if the bot is currently playing a defensive role. Stops the commander sending everyone out // and leaving nobody at base if (!IsPlayerActiveInGame(PlayerRef->edict()) || (AIPlayerRef && AIPlayerRef->BotRole == BOT_ROLE_SWEEPER)) { it = PlayerList.erase(it); } else { it++; } } // Next, erase all players with orders so we only have a list of players without orders assigned to them for (auto it = pBot->ActiveOrders.begin(); it != pBot->ActiveOrders.end(); it++) { AvHPlayer* ThisPlayer = dynamic_cast(CBaseEntity::Instance(it->Assignee)); if (!ThisPlayer) { continue; } std::vector::iterator FoundPlayer = std::find(PlayerList.begin(), PlayerList.end(), ThisPlayer); if (FoundPlayer != PlayerList.end()) { PlayerList.erase(FoundPlayer); } } // Now rank them by distance and return the result for (auto it = PlayerList.begin(); it != PlayerList.end(); it++) { edict_t* PlayerEdict = (*it)->edict(); float ThisDist = vDist2DSq(PlayerEdict->v.origin, SearchLocation); if (!Result || ThisDist < MinDist) { Result = PlayerEdict; MinDist = ThisDist; } } return Result; } bool AICOMM_IssueSecureHiveOrder(AvHAIPlayer* pBot, edict_t* Recipient, const AvHAIHiveDefinition* HiveToSecure) { if (!HiveToSecure || FNullEnt(Recipient) || !IsPlayerActiveInGame(Recipient)) { return false; } int ReceiverIndex = ENTINDEX(Recipient); if (ReceiverIndex <= 0) { return false; } AvHTeamNumber CommanderTeam = pBot->Player->GetTeam(); bool bPhaseGatesAvailable = AITAC_PhaseGatesAvailable(CommanderTeam); if (bPhaseGatesAvailable) { DeployableSearchFilter PGFilter; PGFilter.DeployableTeam = CommanderTeam; PGFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE; PGFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; PGFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; PGFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); bool bPhaseExists = AITAC_DeployableExistsAtLocation(HiveToSecure->FloorLocation, &PGFilter); if (!bPhaseExists) { Vector OrderLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), HiveToSecure->FloorLocation, UTIL_MetresToGoldSrcUnits(5.0f)); return AICOMM_IssueMovementOrder(pBot, Recipient, OrderLocation); } } DeployableSearchFilter TFFilter; TFFilter.DeployableTeam = CommanderTeam; TFFilter.DeployableTypes = STRUCTURE_MARINE_TURRETFACTORY; TFFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; TFFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; TFFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); AvHAIBuildableStructure TF = AITAC_FindClosestDeployableToLocation(HiveToSecure->FloorLocation, &TFFilter); if (!TF.IsValid()) { Vector OrderLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), HiveToSecure->FloorLocation, UTIL_MetresToGoldSrcUnits(5.0f)); return AICOMM_IssueMovementOrder(pBot, Recipient, OrderLocation); } DeployableSearchFilter TurretFilter; TurretFilter.DeployableTeam = CommanderTeam; TurretFilter.DeployableTypes = STRUCTURE_MARINE_TURRET; TurretFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; TurretFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; TurretFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); int NumTurrets = AITAC_GetNumDeployablesNearLocation(TF.Location, &TurretFilter); if (NumTurrets < 5) { Vector OrderLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), HiveToSecure->FloorLocation, UTIL_MetresToGoldSrcUnits(5.0f)); return AICOMM_IssueMovementOrder(pBot, Recipient, OrderLocation); } AvHAIResourceNode* HiveNode = HiveToSecure->HiveResNodeRef; if (HiveNode && (FNullEnt(HiveNode->ActiveTowerEntity) || HiveNode->ActiveTowerEntity->v.team != CommanderTeam || !UTIL_StructureIsFullyBuilt(HiveNode->ActiveTowerEntity))) { Vector OrderLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), HiveNode->Location, UTIL_MetresToGoldSrcUnits(2.0f)); return AICOMM_IssueMovementOrder(pBot, Recipient, OrderLocation); } Vector OrderLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), HiveToSecure->FloorLocation, UTIL_MetresToGoldSrcUnits(5.0f)); return AICOMM_IssueMovementOrder(pBot, Recipient, OrderLocation); } bool AICOMM_IssueSiegeHiveOrder(AvHAIPlayer* pBot, edict_t* Recipient, const AvHAIHiveDefinition* HiveToSiege, const Vector SiegePosition) { if (!HiveToSiege || FNullEnt(Recipient) || !IsPlayerActiveInGame(Recipient)) { return false; } return AICOMM_IssueMovementOrder(pBot, Recipient, SiegePosition); } bool AICOMM_IssueSecureResNodeOrder(AvHAIPlayer* pBot, edict_t* Recipient, const AvHAIResourceNode* ResNode) { if (!ResNode || FNullEnt(Recipient) || !IsPlayerActiveInGame(Recipient)) { return false; } Vector MoveLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), ResNode->Location, UTIL_MetresToGoldSrcUnits(3.0f)); if (MoveLocation == ZERO_VECTOR) { MoveLocation = ResNode->Location; } return AICOMM_IssueMovementOrder(pBot, Recipient, MoveLocation); } ai_commander_request* AICOMM_GetExistingRequestForPlayer(AvHAIPlayer* pBot, edict_t* Requestor) { for (auto it = pBot->ActiveRequests.begin(); it != pBot->ActiveRequests.end(); it++) { if (it->Requestor == Requestor) { return &(*it); } } return nullptr; } void AICOMM_CheckNewRequests(AvHAIPlayer* pBot) { // Clear all expired requests for (auto it = pBot->ActiveRequests.begin(); it != pBot->ActiveRequests.end();) { if (gpGlobals->time - it->RequestTime > BALANCE_VAR(kAlertExpireTime)) { it = pBot->ActiveRequests.erase(it); } else { it++; } } AvHTeam* TeamRef = pBot->Player->GetTeamPointer(); AlertListType HealthRequests = TeamRef->GetAlerts(COMMANDER_NEXTHEALTH); AlertListType AmmoRequests = TeamRef->GetAlerts(COMMANDER_NEXTAMMO); AlertListType OrderRequests = TeamRef->GetAlerts(COMMANDER_NEXTIDLE); // Cycle through all active health requests and see if any are new or overriding existing ones for (auto it = HealthRequests.begin(); it != HealthRequests.end(); it++) { edict_t* Requestor = INDEXENT(it->GetEntityIndex()); ai_commander_request* ExistingRequest = AICOMM_GetExistingRequestForPlayer(pBot, Requestor); // This is a new request from the player, update our list accordingly if (!ExistingRequest || ExistingRequest->RequestTime < it->GetTime()) { ai_commander_request NewRequest; ai_commander_request* RequestRef = (ExistingRequest) ? ExistingRequest : &NewRequest; RequestRef->bNewRequest = true; RequestRef->bResponded = false; RequestRef->RequestTime = it->GetTime(); RequestRef->RequestType = COMMANDER_NEXTHEALTH; RequestRef->Requestor = Requestor; if (!ExistingRequest) { pBot->ActiveRequests.push_back(NewRequest); } } } // Do same for ammo requests for (auto it = AmmoRequests.begin(); it != AmmoRequests.end(); it++) { edict_t* Requestor = INDEXENT(it->GetEntityIndex()); ai_commander_request* ExistingRequest = AICOMM_GetExistingRequestForPlayer(pBot, Requestor); if (!ExistingRequest || ExistingRequest->RequestTime < it->GetTime()) { ai_commander_request NewRequest; ai_commander_request* RequestRef = (ExistingRequest) ? ExistingRequest : &NewRequest; RequestRef->bNewRequest = true; RequestRef->bResponded = false; RequestRef->RequestTime = it->GetTime(); RequestRef->RequestType = COMMANDER_NEXTAMMO; RequestRef->Requestor = Requestor; if (!ExistingRequest) { pBot->ActiveRequests.push_back(NewRequest); } } } // And for order requests for (auto it = OrderRequests.begin(); it != OrderRequests.end(); it++) { edict_t* Requestor = INDEXENT(it->GetEntityIndex()); ai_commander_request* ExistingRequest = AICOMM_GetExistingRequestForPlayer(pBot, Requestor); if (!ExistingRequest || ExistingRequest->RequestTime < it->GetTime()) { ai_commander_request NewRequest; ai_commander_request* RequestRef = (ExistingRequest) ? ExistingRequest : &NewRequest; RequestRef->bNewRequest = true; RequestRef->bResponded = false; RequestRef->RequestTime = it->GetTime(); RequestRef->RequestType = COMMANDER_NEXTIDLE; RequestRef->Requestor = Requestor; if (!ExistingRequest) { pBot->ActiveRequests.push_back(NewRequest); } } } } bool AICOMM_IsRequestValid(ai_commander_request* Request) { if (Request->bResponded) { return false; } // We tried and failed to respond 5 times, give up so we don't block the queue of people needing help if (Request->ResponseAttempts > 5) { return false; } edict_t* Requestor = Request->Requestor; if (FNullEnt(Requestor) || !IsPlayerActiveInGame(Requestor)) { return false; } AvHPlayer* PlayerRef = dynamic_cast(CBaseEntity::Instance(Requestor)); if (!PlayerRef) { return false; } AvHTeamNumber RequestorTeam = PlayerRef->GetTeam(); switch (Request->RequestType) { case COMMANDER_NEXTHEALTH: return Requestor->v.health < Requestor->v.max_health; case BUILD_SHOTGUN: return !PlayerHasWeapon(PlayerRef, WEAPON_MARINE_SHOTGUN) && !AITAC_ItemExistsInLocation(Requestor->v.origin, DEPLOYABLE_ITEM_SHOTGUN, RequestorTeam, AI_REACHABILITY_MARINE, 0.0f, UTIL_MetresToGoldSrcUnits(5.0f), false) ; case BUILD_WELDER: return !PlayerHasWeapon(PlayerRef, WEAPON_MARINE_WELDER) && !AITAC_ItemExistsInLocation(Requestor->v.origin, DEPLOYABLE_ITEM_WELDER, RequestorTeam, AI_REACHABILITY_MARINE, 0.0f, UTIL_MetresToGoldSrcUnits(5.0f), false); case BUILD_HMG: return !PlayerHasWeapon(PlayerRef, WEAPON_MARINE_HMG) && !AITAC_ItemExistsInLocation(Requestor->v.origin, DEPLOYABLE_ITEM_HMG, RequestorTeam, AI_REACHABILITY_MARINE, 0.0f, UTIL_MetresToGoldSrcUnits(5.0f), false); case BUILD_HEAVY: return !PlayerHasEquipment(Requestor) && !AITAC_ItemExistsInLocation(Requestor->v.origin, DEPLOYABLE_ITEM_HEAVYARMOUR, RequestorTeam, AI_REACHABILITY_MARINE, 0.0f, UTIL_MetresToGoldSrcUnits(5.0f), false); case BUILD_JETPACK: return !PlayerHasEquipment(Requestor) && !AITAC_ItemExistsInLocation(Requestor->v.origin, DEPLOYABLE_ITEM_JETPACK, RequestorTeam, AI_REACHABILITY_MARINE, 0.0f, UTIL_MetresToGoldSrcUnits(5.0f), false); case BUILD_GRENADE_GUN: return !PlayerHasWeapon(PlayerRef, WEAPON_MARINE_GL) && !AITAC_ItemExistsInLocation(Requestor->v.origin, DEPLOYABLE_ITEM_GRENADELAUNCHER, RequestorTeam, AI_REACHABILITY_MARINE, 0.0f, UTIL_MetresToGoldSrcUnits(5.0f), false); case BUILD_MINES: return !PlayerHasWeapon(PlayerRef, WEAPON_MARINE_MINES) && !AITAC_ItemExistsInLocation(Requestor->v.origin, DEPLOYABLE_ITEM_MINES, RequestorTeam, AI_REACHABILITY_MARINE, 0.0f, UTIL_MetresToGoldSrcUnits(5.0f), false); case BUILD_CAT: return !IsPlayerBuffed(Requestor) && !AITAC_ItemExistsInLocation(Requestor->v.origin, DEPLOYABLE_ITEM_CATALYSTS, RequestorTeam, AI_REACHABILITY_MARINE, 0.0f, UTIL_MetresToGoldSrcUnits(5.0f), false); case BUILD_PHASEGATE: return !AITAC_IsStructureOfTypeNearLocation(RequestorTeam, STRUCTURE_MARINE_PHASEGATE, Requestor->v.origin, UTIL_MetresToGoldSrcUnits(10.0f)); case BUILD_TURRET_FACTORY: return !AITAC_IsStructureOfTypeNearLocation(RequestorTeam, (STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(15.0f)); case BUILD_ARMORY: return !AITAC_IsStructureOfTypeNearLocation(RequestorTeam, (STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(15.0f)); case BUILD_COMMANDSTATION: return !AITAC_IsStructureOfTypeNearLocation(RequestorTeam, STRUCTURE_MARINE_COMMCHAIR, Requestor->v.origin, UTIL_MetresToGoldSrcUnits(10.0f)); case BUILD_SCAN: return !AITAC_ItemExistsInLocation(Requestor->v.origin, DEPLOYABLE_ITEM_SCAN, RequestorTeam, AI_REACHABILITY_NONE, 0.0f, UTIL_MetresToGoldSrcUnits(5.0f), false); case BUILD_ARMSLAB: return !AITAC_IsStructureOfTypeNearLocation(RequestorTeam, STRUCTURE_MARINE_ARMSLAB, Requestor->v.origin, UTIL_MetresToGoldSrcUnits(10.0f)); case BUILD_PROTOTYPE_LAB: return !AITAC_IsStructureOfTypeNearLocation(RequestorTeam, STRUCTURE_MARINE_PROTOTYPELAB, Requestor->v.origin, UTIL_MetresToGoldSrcUnits(10.0f)); default: return true; } return true; } Vector AICOMM_GetNextScanLocation(AvHAIPlayer* pBot) { AvHTeamNumber BotTeam = pBot->Player->GetTeam(); AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam); AvHClassType EnemyType = AIMGR_GetTeamType(EnemyTeam); DeployableSearchFilter ObsFilter; ObsFilter.DeployableTeam = BotTeam; ObsFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; ObsFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; if (!AITAC_DeployableExistsAtLocation(ZERO_VECTOR, &ObsFilter)) { return ZERO_VECTOR; } AvHAIBuildableStructure SiegeTurret; for (auto it = pBot->Bases.begin(); it != pBot->Bases.end(); it++) { AvHAIMarineBase* ThisBase = &(*it); if (ThisBase->BaseType != MARINE_BASE_SIEGE || ThisBase->bRecycleBase) { continue; } bool bHasAdvTF = false; bool bHasST = false; for (auto structIt = ThisBase->PlacedStructures.begin(); structIt != ThisBase->PlacedStructures.end(); structIt++) { AvHAIBuildableStructure StructureRef = AITAC_GetDeployableStructureByEntIndex(BotTeam, *structIt); if (StructureRef.IsValid() && StructureRef.IsCompleted()) { if (StructureRef.StructureType == STRUCTURE_MARINE_ADVTURRETFACTORY) { bHasAdvTF = true; } else if (StructureRef.StructureType == STRUCTURE_MARINE_SIEGETURRET) { SiegeTurret = StructureRef; bHasST = true; } } } if (bHasAdvTF && bHasST) { if (EnemyType == AVH_CLASS_TYPE_ALIEN) { const AvHAIHiveDefinition* SiegedHive = AITAC_GetActiveHiveNearestLocation(EnemyTeam, ThisBase->BaseLocation); if (SiegedHive && vDist2DSq(SiegedHive->Location, ThisBase->BaseLocation) < sqrf(BALANCE_VAR(kSiegeTurretRange))) { bool bAlreadyScanning = AITAC_ItemExistsInLocation(SiegedHive->Location, DEPLOYABLE_ITEM_SCAN, TEAM_IND, AI_REACHABILITY_NONE, 0.0f, UTIL_MetresToGoldSrcUnits(10.0f), false); if (!bAlreadyScanning) { Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), SiegedHive->FloorLocation, UTIL_MetresToGoldSrcUnits(3.0f)); return (!vIsZero(BuildLocation)) ? BuildLocation : SiegedHive->FloorLocation; } } } DeployableSearchFilter EnemyStuffFilter; EnemyStuffFilter.DeployableTeam = EnemyTeam; EnemyStuffFilter.MaxSearchRadius = BALANCE_VAR(kSiegeTurretRange); AvHAIBuildableStructure NearestEnemyThing = AITAC_FindClosestDeployableToLocation(SiegeTurret.Location, &EnemyStuffFilter); if (NearestEnemyThing.IsValid()) { bool bAlreadyScanning = AITAC_ItemExistsInLocation(NearestEnemyThing.Location, DEPLOYABLE_ITEM_SCAN, TEAM_IND, AI_REACHABILITY_NONE, 0.0f, UTIL_MetresToGoldSrcUnits(10.0f), false); if (!bAlreadyScanning) { Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), NearestEnemyThing.Location, UTIL_MetresToGoldSrcUnits(3.0f)); return (!vIsZero(BuildLocation)) ? BuildLocation : NearestEnemyThing.Location; } } } } return ZERO_VECTOR; } bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot) { AvHTeamNumber BotTeam = pBot->Player->GetTeam(); AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam); Vector NextScanLocation = AICOMM_GetNextScanLocation(pBot); if (!vIsZero(NextScanLocation)) { bool bSuccess = AICOMM_DeployItem(pBot, DEPLOYABLE_ITEM_SCAN, NextScanLocation); if (bSuccess) { return true; } } edict_t* CommChair = AITAC_GetCommChair(BotTeam); if (FNullEnt(CommChair)) { return false; } AvHAIMarineBase* MainBase = nullptr; AvHAIMarineBase* SiegeBase = nullptr; AvHAIMarineBase* OutpostBase = nullptr; AvHAIMarineBase* GuardpostBase = nullptr; for (auto it = pBot->Bases.begin(); it != pBot->Bases.end(); it++) { AvHAIMarineBase* ThisBase = &(*it); if (!ThisBase->bCanBeBuiltOut || ThisBase->bRecycleBase || !ThisBase->bIsActive || ThisBase->NumBuilders == 0) { continue; } if (ThisBase->BaseType == MARINE_BASE_MAINBASE) { MainBase = ThisBase; continue; } if (ThisBase->BaseType == MARINE_BASE_SIEGE) { if (ThisBase->NumEnemies > ThisBase->NumBuilders) { continue; } if (!ThisBase->bBaseInitialised) { if (!AITAC_GetNearestHiddenPlayerInLocation(BotTeam, ThisBase->BaseLocation, UTIL_MetresToGoldSrcUnits(5.0f))) { continue; } } // Always favour bases which are started but not established yet if (!SiegeBase || (SiegeBase->bIsBaseEstablished && !ThisBase->bIsBaseEstablished)) { SiegeBase = ThisBase; } } else if (ThisBase->BaseType == MARINE_BASE_OUTPOST) { // Don't build this base out yet if there are enemies in it: helps avoid wasting resources if (ThisBase->NumEnemies > 0) { continue; } // Always favour bases which are started but not established yet if (!OutpostBase || (OutpostBase->bIsBaseEstablished && !ThisBase->bIsBaseEstablished)) { OutpostBase = ThisBase; } } else if (ThisBase->BaseType == MARINE_BASE_GUARDPOST) { // Don't build this base out yet if there are enemies in it: helps avoid wasting resources if (ThisBase->NumEnemies > 0) { continue; } // Always favour bases which are started but not established yet if (!GuardpostBase || (GuardpostBase->bIsBaseEstablished && !ThisBase->bIsBaseEstablished)) { GuardpostBase = ThisBase; } } } // This is important, make sure we always prioritise critical base infrastructure over everything else // If we don't have enough resources to drop the critical infrastructure then block the whole commander thought process // This ensures we aren't placing sentries in Eclipse Command when we don't even have an infantry portal at base if (MainBase) { bool bMustPrioritise = !MainBase->bIsBaseEstablished; if (!bMustPrioritise) { vector BaseStructures = AICOMM_GetBaseStructures(MainBase); bool bHasArmoury = false; bool bHasTF = false; bool bHasPG = false; int NumSentries = 0; int DesiredInfPortals = (int)ceilf((float)AIMGR_GetNumPlayersOnTeam(BotTeam) / 4.0f); int NumInfantryPortals = 0; for (auto it = BaseStructures.begin(); it != BaseStructures.end(); it++) { if (it->StructureType == STRUCTURE_MARINE_INFANTRYPORTAL) { NumInfantryPortals++; } else if (it->StructureType == STRUCTURE_MARINE_PHASEGATE) { bHasPG = true; } else if (it->StructureType & (STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY)) { bHasArmoury = true; } else if (it->StructureType & (STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY)) { bHasTF = true; } else if (it->StructureType == STRUCTURE_MARINE_TURRET) { NumSentries++; } } bMustPrioritise = !bHasArmoury || !bHasTF || NumInfantryPortals < DesiredInfPortals || NumSentries < 3 || (!bHasPG && AITAC_ResearchIsComplete(BotTeam, TECH_RESEARCH_PHASETECH)); } if (bMustPrioritise) { bool bSuccess = AICOMM_BuildOutBase(pBot, MainBase); if (bSuccess) { return true; } // We can't build anything in our base right now, but don't build anything elsewhere unless we have at least 30 resources so we can put down infrastructure when needed if (pBot->Player->GetResources() < BALANCE_VAR(kInfantryPortalCost) * 1.5f) { return true; } } } const AvHAIResourceNode* CappableNode = AICOMM_GetNearestResourceNodeCapOpportunity(BotTeam, CommChair->v.origin); if (CappableNode) { AvHAIBuildableStructure* DeployedStructure = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_RESTOWER, CappableNode->Location); if (DeployedStructure || pBot->Player->GetResources() <= BALANCE_VAR(kResourceTowerCost) + 10) { return true; } } if (SiegeBase) { bool bSuccess = AICOMM_BuildOutBase(pBot, SiegeBase); if (bSuccess) { return true; } } if (OutpostBase) { bool bSuccess = AICOMM_BuildOutBase(pBot, OutpostBase); if (bSuccess) { return true; } } if (GuardpostBase) { bool bSuccess = AICOMM_BuildOutBase(pBot, GuardpostBase); if (bSuccess) { return true; } } if (MainBase) { bool bSuccess = AICOMM_BuildOutBase(pBot, MainBase); if (bSuccess) { return true; } } int Resources = pBot->Player->GetResources(); if (Resources > 100) { DeployableSearchFilter StructureFilter; StructureFilter.DeployableTypes = STRUCTURE_MARINE_RESTOWER; StructureFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; StructureFilter.ExcludeStatusFlags = (STRUCTURE_STATUS_RECYCLING | STRUCTURE_STATUS_ELECTRIFIED); AvHAIBuildableStructure ResTower = AITAC_FindFurthestDeployableFromLocation(CommChair->v.origin, &StructureFilter); if (ResTower.IsValid() && AITAC_ElectricalResearchIsAvailable(ResTower.edict)) { if (AICOMM_ResearchTech(pBot, &ResTower, RESEARCH_ELECTRICAL)) { return true; } } } return false; } bool AICOMM_CheckForNextSupplyAction(AvHAIPlayer* pBot) { AvHTeamNumber CommanderTeam = pBot->Player->GetTeam(); // First thing: if our base is damaged and there's nobody able to weld, drop a welder so we don't let the base die bool bBaseIsDamaged = false; DeployableSearchFilter DamagedBaseStructures; DamagedBaseStructures.DeployableTypes = (STRUCTURE_MARINE_COMMCHAIR | STRUCTURE_MARINE_INFANTRYPORTAL); DamagedBaseStructures.DeployableTeam = CommanderTeam; DamagedBaseStructures.ReachabilityTeam = CommanderTeam; DamagedBaseStructures.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; DamagedBaseStructures.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; DamagedBaseStructures.IncludeStatusFlags = STRUCTURE_STATUS_DAMAGED; DamagedBaseStructures.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; DamagedBaseStructures.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); bBaseIsDamaged = AITAC_DeployableExistsAtLocation(AITAC_GetCommChairLocation(CommanderTeam), &DamagedBaseStructures); if (bBaseIsDamaged) { AvHAIDroppedItem NearestWelder = AITAC_FindClosestItemToLocation(AITAC_GetCommChairLocation(CommanderTeam), DEPLOYABLE_ITEM_WELDER, CommanderTeam, AI_REACHABILITY_MARINE, 0.0f, UTIL_MetresToGoldSrcUnits(15.0f), false); bool bPlayerHasWelder = false; if (!NearestWelder.IsValid()) { vector PlayersAtBase = AITAC_GetAllPlayersOfTeamInArea(CommanderTeam, AITAC_GetCommChairLocation(CommanderTeam), UTIL_MetresToGoldSrcUnits(15.0f), false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER); for (auto it = PlayersAtBase.begin(); it != PlayersAtBase.end(); it++) { AvHPlayer* ThisPlayer = (*it); if (PlayerHasWeapon(ThisPlayer, WEAPON_MARINE_WELDER)) { bPlayerHasWelder = true; } } } if (!NearestWelder.IsValid() && !bPlayerHasWelder) { DeployableSearchFilter ArmouryFilter; ArmouryFilter.DeployableTypes = (STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY); ArmouryFilter.DeployableTeam = CommanderTeam; ArmouryFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; ArmouryFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; AvHAIBuildableStructure NearestArmoury = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &ArmouryFilter); if (NearestArmoury.IsValid()) { Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), NearestArmoury.Location, UTIL_MetresToGoldSrcUnits(3.0f)); bool bSuccess = AICOMM_DeployItem(pBot, DEPLOYABLE_ITEM_WELDER, DeployLocation); return bSuccess; } } } bool bShouldPrioritiseNodes = AICOMM_ShouldCommanderPrioritiseNodes(pBot); if (bShouldPrioritiseNodes && pBot->Player->GetResources() < 25) { return false; } // Now work out how many welders we want on the team generally int NumDesiredWelders = 1; if (!bShouldPrioritiseNodes && pBot->Player->GetResources() >= 20) { NumDesiredWelders = (int)ceilf((float)AIMGR_GetNumPlayersOnTeam(CommanderTeam) * 0.3f); } int NumTeamWelders = AITAC_GetNumWeaponsInPlay(CommanderTeam, WEAPON_MARINE_WELDER); // Add additional welders to the team if we have hives or resource nodes which can only be reached with a welder vector AllNodes = AITAC_GetAllResourceNodes(); for (auto it = AllNodes.begin(); it != AllNodes.end(); it++) { AvHAIResourceNode* ThisNode = (*it); unsigned int TeamReachabilityFlags = (CommanderTeam == AIMGR_GetTeamANumber()) ? ThisNode->TeamAReachabilityFlags : ThisNode->TeamBReachabilityFlags; if ((TeamReachabilityFlags & AI_REACHABILITY_WELDER) && !(TeamReachabilityFlags & AI_REACHABILITY_MARINE)) { NumDesiredWelders++; break; } } vector AllHives = AITAC_GetAllHives(); for (auto it = AllHives.begin(); it != AllHives.end(); it++) { AvHAIHiveDefinition* ThisHive = (*it); unsigned int TeamReachabilityFlags = (CommanderTeam == AIMGR_GetTeamANumber()) ? ThisHive->TeamAReachabilityFlags : ThisHive->TeamBReachabilityFlags; if ((TeamReachabilityFlags & AI_REACHABILITY_WELDER) && !(TeamReachabilityFlags & AI_REACHABILITY_MARINE)) { NumDesiredWelders++; break; } } NumDesiredWelders = imini(NumDesiredWelders, (int)(ceilf((float)AIMGR_GetNumPlayersOnTeam(CommanderTeam) * 0.5f))); if (NumTeamWelders < NumDesiredWelders) { DeployableSearchFilter ArmouryFilter; ArmouryFilter.DeployableTypes = (STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY); ArmouryFilter.DeployableTeam = CommanderTeam; ArmouryFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; ArmouryFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; AvHAIBuildableStructure NearestArmoury = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &ArmouryFilter); if (NearestArmoury.IsValid()) { Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), NearestArmoury.Location, UTIL_MetresToGoldSrcUnits(3.0f)); bool bSuccess = AICOMM_DeployItem(pBot, DEPLOYABLE_ITEM_WELDER, DeployLocation); return bSuccess; } } // Don't drop stuff if we badly need resource nodes if (AICOMM_ShouldCommanderPrioritiseNodes(pBot) && pBot->Player->GetResources() < 20) { return false; } int NumDesiredShotguns = (int)ceilf(AIMGR_GetNumPlayersOnTeam(CommanderTeam) * 0.33f); int NumShottysInPlay = AITAC_GetNumWeaponsInPlay(CommanderTeam, WEAPON_MARINE_SHOTGUN); if (NumShottysInPlay < NumDesiredShotguns) { DeployableSearchFilter ArmouryFilter; ArmouryFilter.DeployableTypes = (STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY); ArmouryFilter.DeployableTeam = CommanderTeam; ArmouryFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; ArmouryFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; AvHAIBuildableStructure NearestArmoury = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &ArmouryFilter); if (NearestArmoury.IsValid()) { Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), NearestArmoury.Location, UTIL_MetresToGoldSrcUnits(3.0f)); bool bSuccess = AICOMM_DeployItem(pBot, DEPLOYABLE_ITEM_SHOTGUN, DeployLocation); return bSuccess; } } int NumMinesInPlay = AITAC_GetNumWeaponsInPlay(CommanderTeam, WEAPON_MARINE_MINES); bool bNeedsMines = false; int DesiredMines = 0; DeployableSearchFilter MineFilter; MineFilter.DeployableTypes = STRUCTURE_MARINE_DEPLOYEDMINE; int NumDeployedMines = AITAC_GetNumDeployablesNearLocation(ZERO_VECTOR, &MineFilter); if (NumMinesInPlay < 2 && NumDeployedMines < 32) { int UnminedStructures = 0; DeployableSearchFilter MineStructures; MineStructures.DeployableTeam = CommanderTeam; MineStructures.DeployableTypes = (STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY | STRUCTURE_MARINE_PHASEGATE | STRUCTURE_MARINE_INFANTRYPORTAL); MineStructures.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; MineStructures.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; MineStructures.ReachabilityTeam = CommanderTeam; MineStructures.ReachabilityFlags = AI_REACHABILITY_MARINE; vector MineableStructures = AITAC_FindAllDeployables(ZERO_VECTOR, &MineStructures); MineStructures.DeployableTypes = STRUCTURE_MARINE_DEPLOYEDMINE; MineStructures.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(3.0f); for (auto it = MineableStructures.begin(); it != MineableStructures.end(); it++) { AvHAIBuildableStructure ThisStructure = (*it); int NumMines = AITAC_GetNumDeployablesNearLocation(ThisStructure.Location, &MineStructures); if (NumMines < 2) { UnminedStructures++; } } DesiredMines = (int)ceilf((float)UnminedStructures / 2.0f); DesiredMines = clampi(DesiredMines, 0, 2); } if (NumMinesInPlay < DesiredMines) { DeployableSearchFilter ArmouryFilter; ArmouryFilter.DeployableTypes = (STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY); ArmouryFilter.DeployableTeam = CommanderTeam; ArmouryFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; ArmouryFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; AvHAIBuildableStructure NearestArmoury = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &ArmouryFilter); if (NearestArmoury.IsValid()) { Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), NearestArmoury.Location, UTIL_MetresToGoldSrcUnits(3.0f)); bool bSuccess = AICOMM_DeployItem(pBot, DEPLOYABLE_ITEM_MINES, DeployLocation); return bSuccess; } } if (!AITAC_ResearchIsComplete(CommanderTeam, TECH_RESEARCH_HEAVYARMOR)) { return false; } DeployableSearchFilter StructureFilter; StructureFilter.DeployableTypes = STRUCTURE_MARINE_ADVARMOURY; StructureFilter.DeployableTeam = CommanderTeam; StructureFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; AvHAIBuildableStructure NearestAdvArmoury = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &StructureFilter); StructureFilter.DeployableTypes = STRUCTURE_MARINE_PROTOTYPELAB; AvHAIBuildableStructure NearestPrototypeLab = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &StructureFilter); if (!NearestAdvArmoury.IsValid() || !NearestPrototypeLab.IsValid()) { return false; } AvHAIDroppedItem ExistingHA = AITAC_FindClosestItemToLocation(NearestPrototypeLab.Location, DEPLOYABLE_ITEM_HEAVYARMOUR, CommanderTeam, AI_REACHABILITY_MARINE, 0.0f, UTIL_MetresToGoldSrcUnits(5.0f), false); AvHAIDroppedItem ExistingHMG = AITAC_FindClosestItemToLocation(NearestAdvArmoury.Location, DEPLOYABLE_ITEM_HMG, CommanderTeam, AI_REACHABILITY_MARINE, 0.0f, UTIL_MetresToGoldSrcUnits(5.0f), false); AvHAIDroppedItem ExistingWelder = AITAC_FindClosestItemToLocation(NearestAdvArmoury.Location, DEPLOYABLE_ITEM_WELDER, CommanderTeam, AI_REACHABILITY_MARINE, 0.0f, UTIL_MetresToGoldSrcUnits(5.0f), false); if (ExistingHA.IsValid() && ExistingHMG.IsValid() && ExistingWelder.IsValid()) { return false; } vector NearbyPlayers = AITAC_GetAllPlayersOfClassInArea(CommanderTeam, NearestAdvArmoury.Location, UTIL_MetresToGoldSrcUnits(10.0f), false, pBot->Edict, AVH_USER3_MARINE_PLAYER); bool bDropWeapon = false; bool bDropWelder = false; for (auto it = NearbyPlayers.begin(); it != NearbyPlayers.end(); it++) { edict_t* PlayerEdict = (*it); AvHPlayer* PlayerRef = dynamic_cast(CBaseEntity::Instance(PlayerEdict)); if (!PlayerEdict) { continue; } if (PlayerHasHeavyArmour(PlayerEdict) || PlayerHasJetpack(PlayerEdict)) { if (PlayerHasWeapon(PlayerRef, WEAPON_MARINE_MG) || UTIL_GetPlayerPrimaryWeapon(PlayerRef) == WEAPON_INVALID) { bDropWeapon = true; } else { if (!PlayerHasWeapon(PlayerRef, WEAPON_MARINE_WELDER)) { bDropWelder = true; } } } } if (!ExistingHA.IsValid() && !bDropWelder && !bDropWeapon) { Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), NearestPrototypeLab.Location, UTIL_MetresToGoldSrcUnits(3.0f)); if (vIsZero(DeployLocation)) { DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), NearestPrototypeLab.Location, UTIL_MetresToGoldSrcUnits(3.0f)); } bool bSuccess = AICOMM_DeployItem(pBot, DEPLOYABLE_ITEM_HEAVYARMOUR, DeployLocation); return bSuccess; } if (bDropWeapon && !ExistingHMG.IsValid()) { Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), NearestAdvArmoury.Location, UTIL_MetresToGoldSrcUnits(3.0f)); if (vIsZero(DeployLocation)) { DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), NearestAdvArmoury.Location, UTIL_MetresToGoldSrcUnits(3.0f)); } bool bSuccess = AICOMM_DeployItem(pBot, DEPLOYABLE_ITEM_HMG, DeployLocation); return bSuccess; } if (bDropWelder && !ExistingWelder.IsValid()) { Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), NearestAdvArmoury.Location, UTIL_MetresToGoldSrcUnits(3.0f)); if (vIsZero(DeployLocation)) { DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), NearestAdvArmoury.Location, UTIL_MetresToGoldSrcUnits(3.0f)); } bool bSuccess = AICOMM_DeployItem(pBot, DEPLOYABLE_ITEM_WELDER, DeployLocation); return bSuccess; } return false; } bool AICOMM_CheckForNextResearchAction(AvHAIPlayer* pBot) { AvHTeamNumber CommanderTeam = pBot->Player->GetTeam(); vector Hives = AITAC_GetAllHives(); for (auto it = Hives.begin(); it != Hives.end(); it++) { AvHAIHiveDefinition* Hive = (*it); if (Hive->Status != HIVE_STATUS_UNBUILT) { continue; } DeployableSearchFilter TFFilter; TFFilter.DeployableTeam = pBot->Player->GetTeam(); TFFilter.DeployableTypes = STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY; TFFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; TFFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING | STRUCTURE_STATUS_ELECTRIFIED; TFFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); AvHAIBuildableStructure NearestTF = AITAC_FindClosestDeployableToLocation(Hive->FloorLocation, &TFFilter); if (NearestTF.IsValid()) { TFFilter.DeployableTypes = STRUCTURE_MARINE_TURRET; TFFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); int NumTurrets = AITAC_GetNumDeployablesNearLocation(NearestTF.Location, &TFFilter); if (NumTurrets > 0) { if (AICOMM_ResearchTech(pBot, &NearestTF, RESEARCH_ELECTRICAL)) { return true; } } } if (pBot->Player->GetResources() > 60) { if (Hive->HiveResNodeRef && Hive->HiveResNodeRef->OwningTeam == pBot->Player->GetTeam()) { edict_t* Tower = Hive->HiveResNodeRef->ActiveTowerEntity; if (!FNullEnt(Tower) && UTIL_StructureIsFullyBuilt(Tower) && !UTIL_IsStructureElectrified(Tower)) { AvHAIBuildableStructure ResTower = AITAC_GetDeployableFromEdict(Tower); if (ResTower.IsValid() && AICOMM_ResearchTech(pBot, &ResTower, RESEARCH_ELECTRICAL)) { return true; } } } } } DeployableSearchFilter StructureFilter; StructureFilter.DeployableTeam = CommanderTeam; StructureFilter.ReachabilityTeam = CommanderTeam; StructureFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_GRENADES)) { StructureFilter.DeployableTypes = STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY; StructureFilter.ExcludeStatusFlags |= STRUCTURE_STATUS_RESEARCHING; AvHAIBuildableStructure Armoury = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &StructureFilter); if (Armoury.IsValid()) { bool bSuccess = AICOMM_ResearchTech(pBot, &Armoury, RESEARCH_GRENADES); if (bSuccess) { return true; } return pBot->Player->GetResources() < (BALANCE_VAR(kGrenadesResearchCost) * 1.5f); } } StructureFilter.DeployableTypes = STRUCTURE_MARINE_ARMSLAB; StructureFilter.ExcludeStatusFlags |= STRUCTURE_STATUS_RESEARCHING; AvHAIBuildableStructure ArmsLab = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &StructureFilter); if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_ARMOR_ONE)) { if (ArmsLab.IsValid()) { bool bSuccess = AICOMM_ResearchTech(pBot, &ArmsLab, RESEARCH_ARMOR_ONE); if (bSuccess) { return true; } return pBot->Player->GetResources() < (BALANCE_VAR(kArmorOneResearchCost) * 1.5f); } } if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_WEAPONS_ONE)) { if (ArmsLab.IsValid()) { bool bSuccess = AICOMM_ResearchTech(pBot, &ArmsLab, RESEARCH_WEAPONS_ONE); if (bSuccess) { return true; } return pBot->Player->GetResources() < (BALANCE_VAR(kWeaponsOneResearchCost) * 1.5f); } } StructureFilter.DeployableTypes = STRUCTURE_MARINE_OBSERVATORY; StructureFilter.ExcludeStatusFlags |= STRUCTURE_STATUS_RESEARCHING; AvHAIBuildableStructure Observatory = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &StructureFilter); if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_PHASETECH)) { if (Observatory.IsValid()) { bool bSuccess = AICOMM_ResearchTech(pBot, &Observatory, RESEARCH_PHASETECH); if (bSuccess) { return true; } return pBot->Player->GetResources() < (BALANCE_VAR(kPhaseTechResearchCost) * 1.5f); } } if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_MOTIONTRACK)) { if (Observatory.IsValid()) { return AICOMM_ResearchTech(pBot, &Observatory, RESEARCH_MOTIONTRACK); } } if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_ARMOR_TWO)) { if (ArmsLab.IsValid()) { return AICOMM_ResearchTech(pBot, &ArmsLab, RESEARCH_ARMOR_TWO); } } if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_WEAPONS_TWO)) { if (ArmsLab.IsValid()) { return AICOMM_ResearchTech(pBot, &ArmsLab, RESEARCH_WEAPONS_TWO); } } if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_CATALYSTS)) { if (ArmsLab.IsValid()) { return AICOMM_ResearchTech(pBot, &ArmsLab, RESEARCH_CATALYSTS); } } StructureFilter.DeployableTypes = STRUCTURE_MARINE_PROTOTYPELAB; StructureFilter.ExcludeStatusFlags |= STRUCTURE_STATUS_RESEARCHING; AvHAIBuildableStructure ProtoLab = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &StructureFilter); if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_HEAVYARMOR)) { if (ProtoLab.IsValid()) { return AICOMM_ResearchTech(pBot, &ProtoLab, RESEARCH_HEAVYARMOR); } } if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_JETPACKS)) { if (ProtoLab.IsValid()) { return AICOMM_ResearchTech(pBot, &ProtoLab, RESEARCH_JETPACKS); } } if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_ARMOR_THREE)) { if (ArmsLab.IsValid()) { return AICOMM_ResearchTech(pBot, &ArmsLab, RESEARCH_ARMOR_THREE); } } if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_WEAPONS_THREE)) { if (ArmsLab.IsValid()) { return AICOMM_ResearchTech(pBot, &ArmsLab, RESEARCH_WEAPONS_THREE); } } return false; } const AvHAIHiveDefinition* AICOMM_GetHiveSiegeOpportunityNearestLocation(AvHAIPlayer* CommanderBot, const Vector SearchLocation) { AvHTeamNumber CommanderTeam = CommanderBot->Player->GetTeam(); bool bPhaseGatesAvailable = AITAC_PhaseGatesAvailable(CommanderTeam); // Only siege if we have phase gates available if (!bPhaseGatesAvailable) { return nullptr; } const AvHAIHiveDefinition* Result = nullptr; float MinDist = 0.0f; const vector Hives = AITAC_GetAllHives(); for (auto it = Hives.begin(); it != Hives.end(); it++) { const AvHAIHiveDefinition* Hive = (*it); if (Hive->Status == HIVE_STATUS_UNBUILT) { continue; } DeployableSearchFilter StructureFilter; StructureFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE; StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(20.0f); StructureFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; AvHAIBuildableStructure BuiltPhaseGate = AITAC_FindClosestDeployableToLocation(Hive->Location, &StructureFilter); // If we have a phase gate already in place, then keep building as long as someone is there. If we don't have a phase gate, only build if there is a marine who isn't sighted by the enemy (to allow element of surprise) if (BuiltPhaseGate.IsValid()) { int NumBuilders = AITAC_GetNumPlayersOfTeamInArea(CommanderTeam, BuiltPhaseGate.Location, UTIL_MetresToGoldSrcUnits(5.0f), false, CommanderBot->Edict, AVH_USER3_COMMANDER_PLAYER); if (NumBuilders == 0) { continue; } } else { if (AITAC_GetMarineEligibleToBuildSiege(CommanderTeam, Hive) == nullptr) { continue; } } float ThisDist = vDist2DSq(Hive->FloorLocation, SearchLocation); if (!Result || ThisDist < MinDist) { Result = Hive; MinDist = ThisDist; } } return Result; } const AvHAIResourceNode* AICOMM_GetNearestResourceNodeCapOpportunity(const AvHTeamNumber Team, const Vector SearchLocation) { vector AllNodes = AITAC_GetAllResourceNodes(); AvHAIResourceNode* Result = nullptr; float MinDist = 0.0f; for (auto it = AllNodes.begin(); it != AllNodes.end(); it++) { AvHAIResourceNode* ResNode = (*it); if (ResNode->bIsOccupied) { continue; } if (!AITAC_AnyPlayerOnTeamWithLOS(Team, (ResNode->Location + Vector(0.0f, 0.0f, 32.0f)), UTIL_MetresToGoldSrcUnits(5.0f))) { continue; } if (AITAC_AnyPlayerOnTeamWithLOS(AIMGR_GetEnemyTeam(Team), (ResNode->Location + Vector(0.0f, 0.0f, 32.0f)), UTIL_MetresToGoldSrcUnits(10.0f))) { continue; } float ThisDist = vDist2DSq(ResNode->Location, SearchLocation); if (!Result || ThisDist < MinDist) { Result = ResNode; MinDist = ThisDist; } } return Result; } bool AICOMM_PerformNextSiegeHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinition* HiveToSiege) { AvHTeamNumber CommanderTeam = pBot->Player->GetTeam(); DeployableSearchFilter StructureFilter; StructureFilter.DeployableTeam = CommanderTeam; StructureFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; StructureFilter.ReachabilityTeam = CommanderTeam; StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(25.0f); Vector SiegeLocation = ZERO_VECTOR; StructureFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE; AvHAIBuildableStructure ExistingPG = AITAC_FindClosestDeployableToLocation(HiveToSiege->Location, &StructureFilter); StructureFilter.DeployableTypes = (STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY); AvHAIBuildableStructure ExistingTF = AITAC_FindClosestDeployableToLocation(HiveToSiege->Location, &StructureFilter); StructureFilter.DeployableTypes = (STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY); AvHAIBuildableStructure ExistingArmoury = AITAC_FindClosestDeployableToLocation(HiveToSiege->Location, &StructureFilter); edict_t* NearestBuilder = nullptr; // We only build one of these at a time, so we don't drop a bunch of structures and then our intrepid sieger gets killed and the aliens nom them all if (ExistingPG.IsValid()) { if (ExistingPG.IsCompleted()) { SiegeLocation = ExistingPG.Location; NearestBuilder = AITAC_GetClosestPlayerOnTeamWithLOS(CommanderTeam, ExistingPG.Location, UTIL_MetresToGoldSrcUnits(5.0f), pBot->Edict); } else { // Don't do anything else until we've finished building the phase gate return false; } } if (ExistingTF.IsValid()) { if (ExistingTF.IsCompleted()) { if (vIsZero(SiegeLocation)) { SiegeLocation = ExistingTF.Location; } if (FNullEnt(NearestBuilder)) { NearestBuilder = AITAC_GetClosestPlayerOnTeamWithLOS(CommanderTeam, ExistingTF.Location, UTIL_MetresToGoldSrcUnits(5.0f), pBot->Edict); } } else { // Don't do anything else until we've finished building the turret factory return false; } } else { if (FNullEnt(NearestBuilder)) { NearestBuilder = AITAC_GetNearestHiddenPlayerInLocation(CommanderTeam, HiveToSiege->Location, UTIL_MetresToGoldSrcUnits(20.0f)); } } if (FNullEnt(NearestBuilder)) { return false; } bool bPhaseGatesAvailable = AITAC_PhaseGatesAvailable(CommanderTeam); if (vIsZero(SiegeLocation)) { SiegeLocation = NearestBuilder->v.origin; } AvHAIDeployableStructureType NextStructure = STRUCTURE_NONE; if (!ExistingPG.IsValid() && bPhaseGatesAvailable) { NextStructure = STRUCTURE_MARINE_PHASEGATE; } else if (!ExistingTF.IsValid()) { NextStructure = STRUCTURE_MARINE_TURRETFACTORY; } else if (!ExistingArmoury.IsValid()) { NextStructure = STRUCTURE_MARINE_ARMOURY; } if (NextStructure != STRUCTURE_NONE) { Vector NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), SiegeLocation, UTIL_MetresToGoldSrcUnits(3.0f)); if (!vIsZero(NextBuildPosition) && vDist2DSq(NextBuildPosition, HiveToSiege->Location) < sqrf(UTIL_MetresToGoldSrcUnits(22.0f))) { AvHAIBuildableStructure* DeployedStructure = AICOMM_DeployStructure(pBot, NextStructure, NextBuildPosition, STRUCTURE_PURPOSE_SIEGE); if (DeployedStructure) { return true; } } NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), SiegeLocation, UTIL_MetresToGoldSrcUnits(5.0f)); if (!vIsZero(NextBuildPosition) && vDist2DSq(NextBuildPosition, HiveToSiege->Location) < sqrf(UTIL_MetresToGoldSrcUnits(22.0f))) { AvHAIBuildableStructure* DeployedStructure = AICOMM_DeployStructure(pBot, NextStructure, NextBuildPosition, STRUCTURE_PURPOSE_SIEGE); if (DeployedStructure) { return true; } } NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), SiegeLocation, UTIL_MetresToGoldSrcUnits(5.0f)); if (!vIsZero(NextBuildPosition) && vDist2DSq(NextBuildPosition, HiveToSiege->Location) < sqrf(UTIL_MetresToGoldSrcUnits(22.0f))) { AvHAIBuildableStructure* DeployedStructure = AICOMM_DeployStructure(pBot, NextStructure, NextBuildPosition, STRUCTURE_PURPOSE_SIEGE); if (DeployedStructure) { return true; } } return false; } if (!ExistingTF.IsValid()) { return false; } if ((ExistingTF.StructureStatusFlags & STRUCTURE_STATUS_RESEARCHING)) { return false; } if (ExistingTF.StructureType != STRUCTURE_MARINE_ADVTURRETFACTORY) { return AICOMM_UpgradeStructure(pBot, &ExistingTF); } StructureFilter.DeployableTypes = STRUCTURE_MARINE_SIEGETURRET; StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); int NumSiegeTurrets = AITAC_GetNumDeployablesNearLocation(ExistingTF.Location, &StructureFilter); if (NumSiegeTurrets == 0 || (NumSiegeTurrets < 3 && UTIL_IsStructureElectrified(ExistingTF.edict))) { Vector NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), ExistingTF.Location, UTIL_MetresToGoldSrcUnits(5.0f)); if (!vIsZero(NextBuildPosition) && vDist2DSq(NextBuildPosition, HiveToSiege->Location) <= sqrf(BALANCE_VAR(kSiegeTurretRange))) { AvHAIBuildableStructure* DeployedStructure = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_SIEGETURRET, NextBuildPosition, STRUCTURE_PURPOSE_SIEGE); if (DeployedStructure) { return true; } } NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), ExistingTF.Location, UTIL_MetresToGoldSrcUnits(5.0f)); if (!vIsZero(NextBuildPosition) && vDist2DSq(NextBuildPosition, HiveToSiege->Location) <= sqrf(BALANCE_VAR(kSiegeTurretRange))) { AvHAIBuildableStructure* DeployedStructure = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_SIEGETURRET, NextBuildPosition, STRUCTURE_PURPOSE_SIEGE); if (DeployedStructure) { return true; } } NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), ExistingTF.Location, UTIL_MetresToGoldSrcUnits(5.0f)); if (!vIsZero(NextBuildPosition) && vDist2DSq(NextBuildPosition, HiveToSiege->Location) <= sqrf(BALANCE_VAR(kSiegeTurretRange))) { AvHAIBuildableStructure* DeployedStructure = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_SIEGETURRET, NextBuildPosition, STRUCTURE_PURPOSE_SIEGE); if (DeployedStructure) { return true; } } } if (!UTIL_IsStructureElectrified(ExistingTF.edict)) { return AICOMM_ResearchTech(pBot, &ExistingTF, RESEARCH_ELECTRICAL); } return false; } bool AICOMM_PerformNextSecureHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinition* HiveToSecure) { DeployableSearchFilter StructureFilter; StructureFilter.DeployableTypes = STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY | STRUCTURE_MARINE_PHASEGATE; StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); StructureFilter.DeployableTeam = pBot->Player->GetTeam(); StructureFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; StructureFilter.ReachabilityTeam = pBot->Player->GetTeam(); StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; AvHAIBuildableStructure ExistingStructure = AITAC_FindClosestDeployableToLocation(HiveToSecure->FloorLocation, &StructureFilter); AvHAIBuildableStructure ExistingPG; AvHAIBuildableStructure ExistingTF; 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) { AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_RESTOWER, HiveToSecure->HiveResNodeRef->Location); return true; } if (ExistingStructure.IsValid()) { if (ExistingStructure.StructureType == STRUCTURE_MARINE_PHASEGATE) { ExistingPG = ExistingStructure; } else { ExistingTF = ExistingStructure; } } if (AITAC_PhaseGatesAvailable(pBot->Player->GetTeam())) { if (!ExistingPG.IsValid()) { StructureFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE; ExistingPG = AITAC_FindClosestDeployableToLocation(OutpostLocation, &StructureFilter); if (!ExistingPG.IsValid()) { Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), OutpostLocation, UTIL_MetresToGoldSrcUnits(5.0f)); if (!vIsZero(BuildLocation)) { AvHAIBuildableStructure* DeployedStructure = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_PHASEGATE, BuildLocation, STRUCTURE_PURPOSE_FORTIFY); if (DeployedStructure) { return true; } } BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), OutpostLocation, UTIL_MetresToGoldSrcUnits(5.0f)); if (!vIsZero(BuildLocation)) { AvHAIBuildableStructure* DeployedStructure = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_PHASEGATE, BuildLocation, STRUCTURE_PURPOSE_FORTIFY); if (DeployedStructure) { return true; } } BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), HiveToSecure->FloorLocation, UTIL_MetresToGoldSrcUnits(5.0f)); if (!vIsZero(BuildLocation)) { AvHAIBuildableStructure* DeployedStructure = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_PHASEGATE, BuildLocation, STRUCTURE_PURPOSE_FORTIFY); if (DeployedStructure) { return true; } } return false; } } } if (!ExistingTF.IsValid()) { StructureFilter.DeployableTypes = STRUCTURE_MARINE_TURRETFACTORY; ExistingTF = AITAC_FindClosestDeployableToLocation(OutpostLocation, &StructureFilter); if (!ExistingTF.IsValid()) { // First, try and put the TF near any existing phasegate (if it exists) Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), OutpostLocation, UTIL_MetresToGoldSrcUnits(3.0f)); if (!vIsZero(BuildLocation)) { AvHAIBuildableStructure* DeployedStructure = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_TURRETFACTORY, BuildLocation, STRUCTURE_PURPOSE_FORTIFY); if (DeployedStructure) { return true; } } // That failed, now try expanding the radius a bit and ignoring reachability BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), OutpostLocation, UTIL_MetresToGoldSrcUnits(5.0f)); if (!vIsZero(BuildLocation)) { AvHAIBuildableStructure* DeployedStructure = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_TURRETFACTORY, BuildLocation, STRUCTURE_PURPOSE_FORTIFY); if (DeployedStructure) { return true; } } // That failed too, try putting it anywhere near the hive location BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), HiveToSecure->FloorLocation, UTIL_MetresToGoldSrcUnits(5.0f)); if (!vIsZero(BuildLocation)) { AvHAIBuildableStructure* DeployedStructure = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_TURRETFACTORY, BuildLocation, STRUCTURE_PURPOSE_FORTIFY); if (DeployedStructure) { return true; } } return false; } } StructureFilter.DeployableTypes = STRUCTURE_MARINE_TURRET; StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f); int NumTurrets = AITAC_GetNumDeployablesNearLocation(ExistingTF.Location, &StructureFilter); if (NumTurrets < 5) { Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), ExistingTF.Location, (BALANCE_VAR(kCommandStationBuildDistance) * 0.8f)); if (!vIsZero(BuildLocation)) { AvHAIBuildableStructure* DeployedStructure = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_TURRET, BuildLocation, STRUCTURE_PURPOSE_FORTIFY); if (DeployedStructure) { return true; } } BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), ExistingTF.Location, (BALANCE_VAR(kCommandStationBuildDistance) * 0.8f)); if (!vIsZero(BuildLocation)) { AvHAIBuildableStructure* DeployedStructure = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_TURRET, BuildLocation, STRUCTURE_PURPOSE_FORTIFY); if (DeployedStructure) { return true; } } return false; } return false; } bool AICOMM_BuildInfantryPortal(AvHAIPlayer* pBot, edict_t* CommChair) { if (FNullEnt(CommChair) || !UTIL_StructureIsFullyBuilt(CommChair)) { return false; } Vector BuildLocation = AITAC_GetRandomBuildHintInLocation(STRUCTURE_MARINE_INFANTRYPORTAL, CommChair->v.origin, BALANCE_VAR(kCommandStationBuildDistance)); if (!vIsZero(BuildLocation)) { AvHAIBuildableStructure* DeployedStructure = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_INFANTRYPORTAL, BuildLocation, STRUCTURE_PURPOSE_BASE); if (DeployedStructure) { return true; } } DeployableSearchFilter ExistingPortalFilter; ExistingPortalFilter.DeployableTypes = STRUCTURE_MARINE_INFANTRYPORTAL; ExistingPortalFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f); ExistingPortalFilter.DeployableTeam = pBot->Player->GetTeam(); ExistingPortalFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; ExistingPortalFilter.ReachabilityTeam = pBot->Player->GetTeam(); AvHAIBuildableStructure ExistingInfantryPortal = AITAC_FindClosestDeployableToLocation(CommChair->v.origin, &ExistingPortalFilter); // First see if we can place the next infantry portal next to the first one if (ExistingInfantryPortal.IsValid()) { BuildLocation = UTIL_GetRandomPointOnNavmeshInDonutIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), ExistingInfantryPortal.edict->v.origin, UTIL_MetresToGoldSrcUnits(2.0f), UTIL_MetresToGoldSrcUnits(3.0f)); if (!vIsZero(BuildLocation)) { AvHAIBuildableStructure* DeployedStructure = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_INFANTRYPORTAL, BuildLocation, STRUCTURE_PURPOSE_BASE); if (DeployedStructure) { return true; } } } Vector SearchPoint = ZERO_VECTOR; DeployableSearchFilter ResNodeFilter; ResNodeFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; ResNodeFilter.ReachabilityTeam = pBot->Player->GetTeam(); const AvHAIResourceNode* ResNode = AITAC_FindNearestResourceNodeToLocation(CommChair->v.origin, &ResNodeFilter); if (ResNode) { SearchPoint = ResNode->Location; } else { return false; } Vector NearestPointToChair = FindClosestNavigablePointToDestination(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), SearchPoint, CommChair->v.origin, BALANCE_VAR(kCommandStationBuildDistance)); if (!vIsZero(NearestPointToChair)) { float Distance = vDist2D(NearestPointToChair, CommChair->v.origin); float RandomDist = UTIL_MetresToGoldSrcUnits(5.0f) - Distance; BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), NearestPointToChair, RandomDist); if (!vIsZero(BuildLocation)) { AvHAIBuildableStructure* DeployedStructure = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_INFANTRYPORTAL, BuildLocation, STRUCTURE_PURPOSE_BASE); if (DeployedStructure) { return true; } } } BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), CommChair->v.origin, BALANCE_VAR(kCommandStationBuildDistance)); if (vIsZero(BuildLocation)) { return false; } return AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_INFANTRYPORTAL, BuildLocation, STRUCTURE_PURPOSE_BASE) != nullptr; } bool AICOMM_CheckForNextRecycleAction(AvHAIPlayer* pBot) { for (auto it = pBot->Bases.begin(); it != pBot->Bases.end(); it++) { AvHAIMarineBase* ThisBase = &(*it); if (ThisBase->bRecycleBase) { for (auto structIt = ThisBase->PlacedStructures.begin(); structIt != ThisBase->PlacedStructures.end(); structIt++) { AvHAIBuildableStructure ThisStructure = AITAC_GetDeployableStructureByEntIndex(pBot->Player->GetTeam(), (*structIt)); if (ThisStructure.IsValid() && !(ThisStructure.StructureStatusFlags & STRUCTURE_STATUS_RECYCLING)) { bool bSuccess = AICOMM_RecycleStructure(pBot, &ThisStructure); if (bSuccess) { return true; } } } } else if (ThisBase->BaseType != MARINE_BASE_MAINBASE) { for (auto structIt = ThisBase->PlacedStructures.begin(); structIt != ThisBase->PlacedStructures.end(); structIt++) { AvHAIBuildableStructure ThisStructure = AITAC_GetDeployableStructureByEntIndex(pBot->Player->GetTeam(), (*structIt)); if (ThisStructure.IsValid() && !(ThisStructure.StructureStatusFlags & STRUCTURE_STATUS_RECYCLING)) { // Don't allow any infantry portals to be scattered elsewhere outside the base if (ThisStructure.StructureType == STRUCTURE_MARINE_INFANTRYPORTAL) { bool bSuccess = AICOMM_RecycleStructure(pBot, &ThisStructure); if (bSuccess) { return true; } } } } } } return false; } bool AICOMM_CheckForNextSupportAction(AvHAIPlayer* pBot) { AvHTeamNumber CommanderTeam = pBot->Player->GetTeam(); AICOMM_CheckNewRequests(pBot); ai_commander_request* NextRequest = nullptr; float OldestTime = 0.0f; // Find the oldest request we haven't fulfilled yet for (auto it = pBot->ActiveRequests.begin(); it != pBot->ActiveRequests.end(); it++) { // Ignore if we've already responded, or the request is too new (leave a nice 1 second response time to requests) if (it->bResponded || (gpGlobals->time - it->RequestTime) < 1.0f) { continue; } // Ignore the request if it's not valid (e.g. request for health while not injured) if (!AICOMM_IsRequestValid(&(*it))) { it->bResponded = true; continue; } float ThisTime = gpGlobals->time - it->RequestTime; if (ThisTime > OldestTime) { NextRequest = &(*it); OldestTime = ThisTime; } } // We didn't find any unresponded requests outstanding if (!NextRequest) { return false; } edict_t* Requestor = NextRequest->Requestor; if (FNullEnt(Requestor) || !IsEdictPlayer(Requestor) || !IsPlayerActiveInGame(Requestor)) { NextRequest->bResponded = true; return false; } int NumDesiredHealthPacks = 0; int NumDesiredAmmoPacks = 0; float RequestorHealthDeficit = Requestor->v.max_health - Requestor->v.health; float HealthPerPack = BALANCE_VAR(kPointsPerHealth); if (RequestorHealthDeficit > 10.0f) { NumDesiredHealthPacks = (int)ceilf(RequestorHealthDeficit / HealthPerPack); int NumHealthPacksPresent = AITAC_GetNumItemsInLocation(Requestor->v.origin, DEPLOYABLE_ITEM_HEALTHPACK, (AvHTeamNumber)Requestor->v.team, AI_REACHABILITY_MARINE, 0.0f, UTIL_MetresToGoldSrcUnits(5.0f), false); NumDesiredHealthPacks -= NumHealthPacksPresent; } if (NumDesiredHealthPacks > 0) { Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(2.0f)); bool bSuccess = AICOMM_DeployItem(pBot, DEPLOYABLE_ITEM_HEALTHPACK, DeployLocation); if (bSuccess) { if (NextRequest->RequestType == COMMANDER_NEXTHEALTH && NumDesiredHealthPacks <= 1) { NextRequest->bResponded = true; } } else { NextRequest->ResponseAttempts++; } return true; } else { if (NextRequest->RequestType == COMMANDER_NEXTHEALTH) { NextRequest->bResponded = true; return false; } } if (NextRequest->RequestType == COMMANDER_NEXTAMMO) { AvHPlayer* thePlayer = dynamic_cast(CBaseEntity::Instance(Requestor)); bool bFillPrimaryWeapon = true; AvHAIWeapon WeaponType = UTIL_GetPlayerPrimaryWeapon(thePlayer); // Requesting player doesn't have a primary weapon, check if they have a pistol if (WeaponType == WEAPON_INVALID) { bFillPrimaryWeapon = false; WeaponType = UTIL_GetPlayerSecondaryWeapon(thePlayer); } // They've only got a knife, don't bother dropping ammo if (WeaponType == WEAPON_INVALID) { NextRequest->bResponded = true; return false; } int AmmoDeficit = (bFillPrimaryWeapon) ? (UTIL_GetPlayerPrimaryMaxAmmoReserve(thePlayer) - UTIL_GetPlayerPrimaryAmmoReserve(thePlayer)) : (UTIL_GetPlayerSecondaryMaxAmmoReserve(thePlayer) - UTIL_GetPlayerSecondaryAmmoReserve(thePlayer)); int WeaponClipSize = (bFillPrimaryWeapon) ? UTIL_GetPlayerPrimaryWeaponMaxClipSize(thePlayer) : UTIL_GetPlayerSecondaryWeaponMaxClipSize(thePlayer); // Player already has full ammo, they're yanking our chain if (AmmoDeficit == 0) { NextRequest->bResponded = true; return false; } int DesiredNumAmmoPacks = (int)(ceilf((float)AmmoDeficit / (float)WeaponClipSize)); // Don't drop more than 5 at any one time DesiredNumAmmoPacks = clampi(DesiredNumAmmoPacks, 0, 5); int NumAmmoPacksPresent = AITAC_GetNumItemsInLocation(Requestor->v.origin, DEPLOYABLE_ITEM_AMMO, (AvHTeamNumber)Requestor->v.team, AI_REACHABILITY_MARINE, 0.0f, UTIL_MetresToGoldSrcUnits(5.0f), false); DesiredNumAmmoPacks -= NumAmmoPacksPresent; // Do we need to drop any ammo, or has the player got enough surrounding them already? if (DesiredNumAmmoPacks > 0) { Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(2.0f)); bool bSuccess = AICOMM_DeployItem(pBot, DEPLOYABLE_ITEM_AMMO, DeployLocation); if (bSuccess) { // We've dropped enough that the player has enough to fill their boots. Mission accomplished if (DesiredNumAmmoPacks <= 1) { NextRequest->bResponded = true; } } else { NextRequest->ResponseAttempts++; } return true; } else { // Player already has enough ammo packs deployed by them to satisfy. Don't drop any more NextRequest->bResponded = true; } return false; } if (NextRequest->RequestType == COMMANDER_NEXTIDLE) { // TODO: Have the commander prioritise this player when looking for people to give orders to NextRequest->bResponded = true; } if (NextRequest->RequestType == BUILD_CAT) { if (!AITAC_ResearchIsComplete(CommanderTeam, TECH_RESEARCH_CATALYSTS)) { char msg[128]; sprintf(msg, "We haven't researched catalysts yet, %s. Ask again later.", STRING(Requestor->v.netname)); BotSay(pBot, true, 0.5f, msg); NextRequest->bResponded = true; return false; } if (!AITAC_IsCompletedStructureOfTypeNearLocation(CommanderTeam, (STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY), ZERO_VECTOR, 0.0f)) { char msg[128]; sprintf(msg, "Don't have an armory anymore, %s. We need to build one.", STRING(Requestor->v.netname)); BotSay(pBot, true, 0.5f, msg); NextRequest->bResponded = true; return false; } Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(2.0f)); bool bSuccess = AICOMM_DeployItem(pBot, DEPLOYABLE_ITEM_CATALYSTS, DeployLocation); if (bSuccess) { NextRequest->bResponded = true; } else { NextRequest->ResponseAttempts++; } return true; } if (NextRequest->RequestType == BUILD_WELDER || NextRequest->RequestType == BUILD_SHOTGUN || NextRequest->RequestType == BUILD_MINES) { AvHAIDeployableItemType ItemToDrop = DEPLOYABLE_ITEM_NONE; float Cost = 0.0f; switch (NextRequest->RequestType) { case BUILD_WELDER: ItemToDrop = DEPLOYABLE_ITEM_WELDER; Cost = BALANCE_VAR(kWelderCost); break; case BUILD_SHOTGUN: ItemToDrop = DEPLOYABLE_ITEM_SHOTGUN; Cost = BALANCE_VAR(kShotgunCost); break; case BUILD_MINES: ItemToDrop = DEPLOYABLE_ITEM_MINES; Cost = BALANCE_VAR(kMineCost); break; default: ItemToDrop = DEPLOYABLE_ITEM_WELDER; Cost = BALANCE_VAR(kWelderCost); break; } DeployableSearchFilter ArmouryFilter; ArmouryFilter.DeployableTeam = CommanderTeam; ArmouryFilter.DeployableTypes = (STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY); ArmouryFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; ArmouryFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; ArmouryFilter.MaxSearchRadius = BALANCE_VAR(kArmoryBuildDistance); AvHAIBuildableStructure NearestArmoury = AITAC_FindClosestDeployableToLocation(Requestor->v.origin, &ArmouryFilter); if (!NearestArmoury.IsValid()) { if (!NextRequest->bAcknowledged) { char msg[128]; sprintf(msg, "Get to an working armory %s, and ask again.", STRING(Requestor->v.netname)); BotSay(pBot, true, 0.5f, msg); NextRequest->bAcknowledged = true; } return false; } if (pBot->Player->GetResources() < Cost) { if (!NextRequest->bAcknowledged) { char msg[128]; sprintf(msg, "Wait for resources %s, I'll drop it soon.", STRING(Requestor->v.netname)); BotSay(pBot, true, 0.5f, msg); NextRequest->bAcknowledged = true; } return false; } Vector IdealDeployLocation = Requestor->v.origin + (UTIL_GetForwardVector2D(Requestor->v.angles) * 75.0f); Vector ProjectedDeployLocation = AdjustPointForPathfinding(IdealDeployLocation); if (vDist2DSq(ProjectedDeployLocation, NearestArmoury.Location) < BALANCE_VAR(kArmoryBuildDistance)) { bool bSuccess = AICOMM_DeployItem(pBot, ItemToDrop, ProjectedDeployLocation); if (bSuccess) { NextRequest->bResponded = bSuccess; return true; } } Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), NearestArmoury.Location, UTIL_MetresToGoldSrcUnits(4.0f)); if (vIsZero(DeployLocation)) { DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), NearestArmoury.Location, UTIL_MetresToGoldSrcUnits(4.0f)); } if (vIsZero(DeployLocation)) { char msg[128]; sprintf(msg, "I can't find a drop location, %s. Try asking again elsewhere.", STRING(Requestor->v.netname)); BotSay(pBot, true, 0.5f, msg); NextRequest->bResponded = true; return false; } bool bSuccess = AICOMM_DeployItem(pBot, ItemToDrop, DeployLocation); NextRequest->ResponseAttempts++; NextRequest->bResponded = bSuccess; return true; } if (NextRequest->RequestType == BUILD_HEAVY || NextRequest->RequestType == BUILD_JETPACK) { AvHAIDeployableItemType ItemToDrop = (NextRequest->RequestType == BUILD_HEAVY) ? DEPLOYABLE_ITEM_HEAVYARMOUR : DEPLOYABLE_ITEM_JETPACK; float Cost = (NextRequest->RequestType == BUILD_HEAVY) ? BALANCE_VAR(kHeavyArmorCost) : BALANCE_VAR(kJetpackCost); AvHTechID TechNeeded = (NextRequest->RequestType == BUILD_HEAVY) ? TECH_RESEARCH_HEAVYARMOR : TECH_RESEARCH_JETPACKS; if (!AITAC_ResearchIsComplete(CommanderTeam, TechNeeded)) { char msg[128]; sprintf(msg, "We haven't researched it yet, %s. Ask again later.", STRING(Requestor->v.netname)); BotSay(pBot, true, 0.5f, msg); NextRequest->bResponded = true; return false; } DeployableSearchFilter PrototypeLabFilter; PrototypeLabFilter.DeployableTeam = CommanderTeam; PrototypeLabFilter.DeployableTypes = STRUCTURE_MARINE_PROTOTYPELAB; PrototypeLabFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; PrototypeLabFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; AvHAIBuildableStructure NearestPL = AITAC_FindClosestDeployableToLocation(Requestor->v.origin, &PrototypeLabFilter); if (!NearestPL.IsValid()) { char msg[128]; sprintf(msg, "We don't have a prototype lab %s, ask again later.", STRING(Requestor->v.netname)); BotSay(pBot, true, 0.5f, msg); NextRequest->bResponded = true; return false; } if (vDist2DSq(Requestor->v.origin, NearestPL.Location) > sqrf(BALANCE_VAR(kArmoryBuildDistance))) { if (!NextRequest->bAcknowledged) { char msg[128]; sprintf(msg, "Get near the prototype lab %s, and I will drop it for you.", STRING(Requestor->v.netname)); BotSay(pBot, true, 0.5f, msg); NextRequest->bAcknowledged = true; } return false; } Vector IdealDeployLocation = Requestor->v.origin + (UTIL_GetForwardVector2D(Requestor->v.angles) * 75.0f); Vector ProjectedDeployLocation = AdjustPointForPathfinding(IdealDeployLocation); if (vDist2DSq(ProjectedDeployLocation, NearestPL.Location) < BALANCE_VAR(kArmoryBuildDistance)) { bool bSuccess = AICOMM_DeployItem(pBot, ItemToDrop, ProjectedDeployLocation); if (bSuccess) { NextRequest->bResponded = bSuccess; return true; } } Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), NearestPL.Location, UTIL_MetresToGoldSrcUnits(4.0f)); if (vIsZero(DeployLocation)) { DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), NearestPL.Location, UTIL_MetresToGoldSrcUnits(4.0f)); } if (vIsZero(DeployLocation)) { char msg[128]; sprintf(msg, "I can't find a drop location, %s. Try asking again elsewhere.", STRING(Requestor->v.netname)); BotSay(pBot, true, 0.5f, msg); NextRequest->bResponded = true; return false; } bool bSuccess = AICOMM_DeployItem(pBot, ItemToDrop, DeployLocation); NextRequest->ResponseAttempts++; NextRequest->bResponded = bSuccess; return true; } if (NextRequest->RequestType == BUILD_HMG || NextRequest->RequestType == BUILD_GRENADE_GUN) { AvHAIDeployableItemType ItemToDrop = (NextRequest->RequestType == BUILD_HMG) ? DEPLOYABLE_ITEM_HMG : DEPLOYABLE_ITEM_GRENADELAUNCHER; float Cost = (NextRequest->RequestType == BUILD_HMG) ? BALANCE_VAR(kHMGCost) : BALANCE_VAR(kGrenadeLauncherCost); DeployableSearchFilter ArmouryFilter; ArmouryFilter.DeployableTeam = CommanderTeam; ArmouryFilter.DeployableTypes = STRUCTURE_MARINE_ADVARMOURY; ArmouryFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; ArmouryFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; AvHAIBuildableStructure NearestArmoury = AITAC_FindClosestDeployableToLocation(Requestor->v.origin, &ArmouryFilter); if (!NearestArmoury.IsValid()) { char msg[128]; sprintf(msg, "We don't have an adv armory yet %s, ask again later.", STRING(Requestor->v.netname)); BotSay(pBot, true, 0.5f, msg); NextRequest->bResponded = true; return false; } if (vDist2DSq(Requestor->v.origin, NearestArmoury.Location) > sqrf(BALANCE_VAR(kArmoryBuildDistance))) { if (!NextRequest->bAcknowledged) { char msg[128]; sprintf(msg, "Get near the adv armory %s, and I will drop it for you.", STRING(Requestor->v.netname)); BotSay(pBot, true, 0.5f, msg); NextRequest->bAcknowledged = true; } return false; } if (pBot->Player->GetResources() < Cost) { if (!NextRequest->bAcknowledged) { char msg[128]; sprintf(msg, "Wait for resources %s, will drop asap.", STRING(Requestor->v.netname)); BotSay(pBot, true, 0.5f, msg); NextRequest->bAcknowledged = true; } return false; } Vector IdealDeployLocation = Requestor->v.origin + (UTIL_GetForwardVector2D(Requestor->v.angles) * 75.0f); Vector ProjectedDeployLocation = AdjustPointForPathfinding(IdealDeployLocation); if (vDist2DSq(ProjectedDeployLocation, NearestArmoury.Location) < BALANCE_VAR(kArmoryBuildDistance)) { bool bSuccess = AICOMM_DeployItem(pBot, ItemToDrop, ProjectedDeployLocation); if (bSuccess) { NextRequest->bResponded = bSuccess; return true; } } Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), NearestArmoury.Location, UTIL_MetresToGoldSrcUnits(4.0f)); if (vIsZero(DeployLocation)) { DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), NearestArmoury.Location, UTIL_MetresToGoldSrcUnits(4.0f)); } if (vIsZero(DeployLocation)) { char msg[128]; sprintf(msg, "I can't find a drop location, %s. Try asking again elsewhere.", STRING(Requestor->v.netname)); BotSay(pBot, true, 0.5f, msg); NextRequest->bResponded = true; return false; } bool bSuccess = AICOMM_DeployItem(pBot, ItemToDrop, DeployLocation); NextRequest->ResponseAttempts++; NextRequest->bResponded = bSuccess; return true; } if (NextRequest->RequestType == BUILD_SCAN) { DeployableSearchFilter ObsFilter; ObsFilter.DeployableTeam = CommanderTeam; ObsFilter.DeployableTypes = STRUCTURE_MARINE_OBSERVATORY; ObsFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; ObsFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; AvHAIBuildableStructure NearestObservatory = AITAC_FindClosestDeployableToLocation(Requestor->v.origin, &ObsFilter); if (!NearestObservatory.IsValid()) { char msg[128]; sprintf(msg, "We don't have an observatory yet %s, ask again later.", STRING(Requestor->v.netname)); BotSay(pBot, true, 0.5f, msg); NextRequest->bResponded = true; return false; } Vector IdealDeployLocation = Requestor->v.origin + (UTIL_GetForwardVector2D(Requestor->v.angles) * 75.0f); Vector ProjectedDeployLocation = AdjustPointForPathfinding(IdealDeployLocation, GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE)); if (!vIsZero(ProjectedDeployLocation)) { bool bSuccess = AICOMM_DeployItem(pBot, DEPLOYABLE_ITEM_SCAN, ProjectedDeployLocation); if (bSuccess) { NextRequest->bResponded = true; return true; } } Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(1.0f)); if (!vIsZero(DeployLocation)) { bool bSuccess = AICOMM_DeployItem(pBot, DEPLOYABLE_ITEM_SCAN, DeployLocation); if (bSuccess) { NextRequest->bResponded = true; return true; } } DeployLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(5.0f)); if (!vIsZero(DeployLocation)) { bool bSuccess = AICOMM_DeployItem(pBot, DEPLOYABLE_ITEM_SCAN, DeployLocation); if (bSuccess) { NextRequest->bResponded = true; return true; } else { char msg[128]; sprintf(msg, "I can't find a good scan spot, %s. Try again elsewhere.", STRING(Requestor->v.netname)); BotSay(pBot, true, 0.5f, msg); NextRequest->bResponded = true; return false; } } else { char msg[128]; sprintf(msg, "I can't find a good scan spot, %s. Try again elsewhere.", STRING(Requestor->v.netname)); BotSay(pBot, true, 0.5f, msg); NextRequest->bResponded = true; return false; } return false; } if (NextRequest->RequestType == BUILD_PHASEGATE && !AITAC_ResearchIsComplete(CommanderTeam, TECH_RESEARCH_PHASETECH)) { char msg[128]; sprintf(msg, "We haven't got phase tech yet, %s. Ask again later.", STRING(Requestor->v.netname)); BotSay(pBot, true, 0.5f, msg); NextRequest->bResponded = true; return false; } if (NextRequest->RequestType == BUILD_OBSERVATORY || NextRequest->RequestType == BUILD_ARMSLAB) { DeployableSearchFilter ArmouryFilter; ArmouryFilter.DeployableTeam = CommanderTeam; ArmouryFilter.DeployableTypes = (STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY); ArmouryFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; ArmouryFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; bool bHasArmoury = AITAC_DeployableExistsAtLocation(ZERO_VECTOR, &ArmouryFilter); if (!bHasArmoury) { char msg[128]; sprintf(msg, "We haven't got an armory yet, %s. Ask again later.", STRING(Requestor->v.netname)); BotSay(pBot, true, 0.5f, msg); NextRequest->bResponded = true; return false; } } if (NextRequest->RequestType == BUILD_PROTOTYPE_LAB) { DeployableSearchFilter RequiredFilter; RequiredFilter.DeployableTeam = CommanderTeam; RequiredFilter.DeployableTypes = STRUCTURE_MARINE_ADVARMOURY; RequiredFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; RequiredFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; bool bHasArmoury = AITAC_DeployableExistsAtLocation(ZERO_VECTOR, &RequiredFilter); if (!bHasArmoury) { char msg[128]; sprintf(msg, "We haven't got an advanced armory yet, %s. Ask again later.", STRING(Requestor->v.netname)); BotSay(pBot, true, 0.5f, msg); NextRequest->bResponded = true; return false; } RequiredFilter.DeployableTypes = STRUCTURE_MARINE_ARMSLAB; bool bHasArmsLab = AITAC_DeployableExistsAtLocation(ZERO_VECTOR, &RequiredFilter); if (!bHasArmsLab) { char msg[128]; sprintf(msg, "We haven't got an arms lab yet, %s. Ask again later.", STRING(Requestor->v.netname)); BotSay(pBot, true, 0.5f, msg); NextRequest->bResponded = true; return false; } } float RequiredRes = 0.0f; AvHAIDeployableStructureType StructureToDeploy = STRUCTURE_NONE; switch (NextRequest->RequestType) { case BUILD_ARMORY: RequiredRes = BALANCE_VAR(kArmoryCost); StructureToDeploy = STRUCTURE_MARINE_ARMOURY; break; case BUILD_COMMANDSTATION: RequiredRes = BALANCE_VAR(kCommandStationCost); StructureToDeploy = STRUCTURE_MARINE_COMMCHAIR; break; case BUILD_OBSERVATORY: RequiredRes = BALANCE_VAR(kObservatoryCost); StructureToDeploy = STRUCTURE_MARINE_OBSERVATORY; break; case BUILD_TURRET_FACTORY: RequiredRes = BALANCE_VAR(kTurretFactoryCost); StructureToDeploy = STRUCTURE_MARINE_TURRETFACTORY; break; case BUILD_TURRET: RequiredRes = BALANCE_VAR(kSentryCost); StructureToDeploy = STRUCTURE_MARINE_TURRET; break; case BUILD_PHASEGATE: RequiredRes = BALANCE_VAR(kPhaseGateCost); StructureToDeploy = STRUCTURE_MARINE_PHASEGATE; break; case BUILD_INFANTRYPORTAL: RequiredRes = BALANCE_VAR(kInfantryPortalCost); StructureToDeploy = STRUCTURE_MARINE_INFANTRYPORTAL; break; case BUILD_PROTOTYPE_LAB: RequiredRes = BALANCE_VAR(kPrototypeLabCost); StructureToDeploy = STRUCTURE_MARINE_PROTOTYPELAB; break; case BUILD_ARMSLAB: RequiredRes = BALANCE_VAR(kArmsLabCost); StructureToDeploy = STRUCTURE_MARINE_ARMSLAB; break; default: break; } // Invalid commander request if (StructureToDeploy == STRUCTURE_NONE) { NextRequest->bResponded = true; return false; } if (pBot->Player->GetResources() < RequiredRes) { if (!NextRequest->bAcknowledged) { char msg[128]; sprintf(msg, "Just waiting on resources, %s. Will drop asap.", STRING(Requestor->v.netname)); BotSay(pBot, true, 0.5f, msg); NextRequest->bAcknowledged = true; return false; } return false; } float ProjectDistance = (NextRequest->RequestType == BUILD_INFANTRYPORTAL || NextRequest->RequestType == BUILD_SIEGE) ? 32.0f : 75.0f; Vector IdealDeployLocation = Requestor->v.origin + (UTIL_GetForwardVector2D(Requestor->v.angles) * ProjectDistance); Vector ProjectedDeployLocation = AdjustPointForPathfinding(IdealDeployLocation, GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE)); if (!vIsZero(ProjectedDeployLocation)) { if (NextRequest->RequestType == BUILD_INFANTRYPORTAL) { DeployableSearchFilter CCFilter; CCFilter.DeployableTeam = CommanderTeam; CCFilter.DeployableTypes = STRUCTURE_MARINE_COMMCHAIR; CCFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; CCFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; CCFilter.MaxSearchRadius = BALANCE_VAR(kCommandStationBuildDistance); bool bHasChair = AITAC_DeployableExistsAtLocation(ProjectedDeployLocation, &CCFilter); if (!bHasChair) { char msg[128]; sprintf(msg, "There isn't a comm chair there, %s. Ask again next to the CC", STRING(Requestor->v.netname)); BotSay(pBot, true, 0.5f, msg); NextRequest->bResponded = true; return false; } } if (NextRequest->RequestType == BUILD_SIEGE) { DeployableSearchFilter TFFilter; TFFilter.DeployableTeam = CommanderTeam; TFFilter.DeployableTypes = STRUCTURE_MARINE_ADVTURRETFACTORY; TFFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; TFFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; TFFilter.MaxSearchRadius = BALANCE_VAR(kTurretFactoryBuildDistance); bool bHasChair = AITAC_DeployableExistsAtLocation(ProjectedDeployLocation, &TFFilter); if (!bHasChair) { char msg[128]; sprintf(msg, "There isn't a an advanced armory there, %s. Ask again next to one", STRING(Requestor->v.netname)); BotSay(pBot, true, 0.5f, msg); NextRequest->bResponded = true; return false; } } AvHAIBuildableStructure* DeployedStructure = AICOMM_DeployStructure(pBot, StructureToDeploy, ProjectedDeployLocation, STRUCTURE_PURPOSE_GENERAL, true); if (DeployedStructure) { NextRequest->bResponded = true; return true; } } Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(5.0f)); if (!vIsZero(DeployLocation)) { AvHAIBuildableStructure* DeployedStructure = AICOMM_DeployStructure(pBot, StructureToDeploy, DeployLocation, STRUCTURE_PURPOSE_GENERAL); if (DeployedStructure) { NextRequest->bResponded = true; return true; } } DeployLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(5.0f)); if (!vIsZero(DeployLocation)) { AvHAIBuildableStructure* DeployedStructure = AICOMM_DeployStructure(pBot, StructureToDeploy, DeployLocation, STRUCTURE_PURPOSE_GENERAL); if (DeployedStructure) { NextRequest->bResponded = true; return true; } else { char msg[128]; sprintf(msg, "I can't find a good deploy spot, %s. Try again elsewhere.", STRING(Requestor->v.netname)); BotSay(pBot, true, 0.5f, msg); NextRequest->bResponded = true; return false; } } else { char msg[128]; sprintf(msg, "I can't find a good deploy spot, %s. Try again elsewhere.", STRING(Requestor->v.netname)); BotSay(pBot, true, 0.5f, msg); NextRequest->bResponded = true; return false; } return false; } void AICOMM_PopulateBaseList(AvHAIPlayer* pBot) { pBot->Bases.clear(); AvHTeamNumber BotTeam = pBot->Player->GetTeam(); AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam); // First, figure out where our main base is. Might not be the chair we're in! edict_t* ActiveCommChair = AITAC_GetCommChair(BotTeam); // Shouldn't happen, but might as well double-check if (FNullEnt(ActiveCommChair)) { return; } edict_t* WinningCommChair = nullptr; Vector BaseLocation = ActiveCommChair->v.origin; DeployableSearchFilter CommChairsFilter; CommChairsFilter.DeployableTeam = BotTeam; CommChairsFilter.DeployableTypes = STRUCTURE_MARINE_COMMCHAIR; CommChairsFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; vector AllCommChairs = AITAC_FindAllDeployables(ZERO_VECTOR, &CommChairsFilter); int MaxInfPortals = 0; // Basically, find all comm chairs in the map, and whichever one has the most infantry portals around it, that's the base for (auto it = AllCommChairs.begin(); it != AllCommChairs.end(); it++) { DeployableSearchFilter InfPortalFilter; InfPortalFilter.DeployableTeam = BotTeam; InfPortalFilter.DeployableTypes = STRUCTURE_MARINE_INFANTRYPORTAL; InfPortalFilter.MaxSearchRadius = BALANCE_VAR(kCommandStationBuildDistance); int ThisNumInfPortals = AITAC_GetNumDeployablesNearLocation(it->Location, &InfPortalFilter); if (ThisNumInfPortals > MaxInfPortals) { WinningCommChair = it->edict; MaxInfPortals = ThisNumInfPortals; } } // None of the comm chairs have any infantry portals, therefore the chair we're in is (for now) where our base will be if (FNullEnt(WinningCommChair)) { WinningCommChair = ActiveCommChair; } // Add an entry for the base now AvHAIMarineBase* MainBase = AICOMM_AddNewBase(pBot, WinningCommChair->v.origin, MARINE_BASE_MAINBASE); if (!MainBase) { return; } // Get all structures and start adding them to our base DeployableSearchFilter AllStructuresFilter; AllStructuresFilter.DeployableTeam = BotTeam; AllStructuresFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; AllStructuresFilter.DeployableTypes = (SEARCH_ALL_STRUCTURES & ~(STRUCTURE_MARINE_RESTOWER)); // Don't include resource towers in this list vector AllTeamStructures = AITAC_FindAllDeployables(ZERO_VECTOR, &AllStructuresFilter); for (auto it = AllTeamStructures.begin(); it != AllTeamStructures.end();) { AvHAIBuildableStructure ThisStructure = (*it); // Obviously make sure the comm chair is part of the base if (ThisStructure.edict == WinningCommChair) { MainBase->PlacedStructures.push_back(ThisStructure.EntIndex); it = AllTeamStructures.erase(it); continue; } // All structures within a 15m radius of the comm chair can be considered part of the main base if (vDist2DSq(ThisStructure.Location, MainBase->BaseLocation) < sqrf(UTIL_MetresToGoldSrcUnits(15.0f))) { MainBase->PlacedStructures.push_back(ThisStructure.EntIndex); it = AllTeamStructures.erase(it); continue; } it++; } // Get all remaining phase gates not in the main base vector AllPhaseGates; for (auto it = AllTeamStructures.begin(); it != AllTeamStructures.end();) { AvHAIBuildableStructure ThisStructure = (*it); if (ThisStructure.StructureType == STRUCTURE_MARINE_PHASEGATE) { AllPhaseGates.push_back(ThisStructure); it = AllTeamStructures.erase(it); } else { it++; } } for (auto it = AllPhaseGates.begin(); it != AllPhaseGates.end(); it++) { AvHAIBuildableStructure ThisPG = (*it); AvHAIMarineBase* NewOutpost = AICOMM_AddNewBase(pBot, ThisPG.Location, MARINE_BASE_OUTPOST); if (!NewOutpost) { continue; } NewOutpost->PlacedStructures.push_back(ThisPG.EntIndex); bool bHasSiegeTurrets = false; for (auto allStructIt = AllTeamStructures.begin(); allStructIt != AllTeamStructures.end();) { AvHAIBuildableStructure ThisStructure = (*allStructIt); // All structures within a 10m radius of the phase gate can be considered part of the outpost or siege base if (vDist2DSq(ThisStructure.Location, NewOutpost->BaseLocation) < sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) { NewOutpost->PlacedStructures.push_back(ThisStructure.EntIndex); if (ThisStructure.StructureType == STRUCTURE_MARINE_SIEGETURRET) { bHasSiegeTurrets = true; } // Remove these structures from the list allStructIt = AllTeamStructures.erase(allStructIt); continue; } allStructIt++; } // Determine if the new outpost should be a siege base or not. Bot will assume it's a siege base if: // It has siege turrets (obviously) // It is in siege range of a hive which is either owned by an enemy alien team, or has been colonised by an enemy marine team if (bHasSiegeTurrets) { NewOutpost->BaseType = MARINE_BASE_SIEGE; } else { const AvHAIHiveDefinition* NearestHive = AITAC_GetHiveNearestLocation(ThisPG.Location); if (NearestHive && vDist2DSq(NearestHive->Location, ThisPG.Location) <= sqrf(UTIL_MetresToGoldSrcUnits(25.0f))) { if (AIMGR_GetTeamType(EnemyTeam) == AVH_CLASS_TYPE_ALIEN) { if (NearestHive->Status != HIVE_STATUS_UNBUILT) { NewOutpost->BaseType = MARINE_BASE_SIEGE; } } else { DeployableSearchFilter EnemyStuffFilter; EnemyStuffFilter.DeployableTeam = EnemyTeam; EnemyStuffFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; EnemyStuffFilter.DeployableTypes = (STRUCTURE_MARINE_PHASEGATE | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY); EnemyStuffFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f); if (AITAC_GetNumDeployablesNearLocation(NearestHive->FloorLocation, &EnemyStuffFilter) > 0) { NewOutpost->BaseType = MARINE_BASE_SIEGE; } } } } } // All that is left now in AllTeamStructures are any structures not yet considered part of the main base, an outpost or siege base // Get all turret factories left vector AllRemainingTFs; for (auto it = AllTeamStructures.begin(); it != AllTeamStructures.end();) { AvHAIBuildableStructure ThisStructure = (*it); if (ThisStructure.StructureType & (STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY)) { AllRemainingTFs.push_back(ThisStructure); it = AllTeamStructures.erase(it); } else { it++; } } //Loop through all the turret factories. These will either be guard posts, or siege bases without a phase gate for (auto it = AllRemainingTFs.begin(); it != AllRemainingTFs.end(); it++) { AvHAIBuildableStructure ThisTF = (*it); MarineBaseType NewBaseType = MARINE_BASE_GUARDPOST; if (vDist2DSq(ThisTF.Location, AITAC_GetTeamOriginalStartLocation(BotTeam)) < sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) { NewBaseType = MARINE_BASE_OUTPOST; } AvHAIMarineBase* NewOutpost = AICOMM_AddNewBase(pBot, ThisTF.Location, NewBaseType); if (!NewOutpost) { continue; } NewOutpost->PlacedStructures.push_back(ThisTF.EntIndex); bool bHasSiegeTurrets = false; for (auto allStructIt = AllTeamStructures.begin(); allStructIt != AllTeamStructures.end();) { AvHAIBuildableStructure ThisStructure = (*allStructIt); // All structures within a 10m radius of the phase gate can be considered part of the outpost or siege base if (vDist2DSq(ThisStructure.Location, NewOutpost->BaseLocation) < sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) { NewOutpost->PlacedStructures.push_back(ThisStructure.EntIndex); if (ThisStructure.StructureType == STRUCTURE_MARINE_SIEGETURRET) { bHasSiegeTurrets = true; } // Remove these structures from the list allStructIt = AllTeamStructures.erase(allStructIt); continue; } allStructIt++; } if (ThisTF.StructureType == STRUCTURE_MARINE_ADVTURRETFACTORY) { if (bHasSiegeTurrets) { NewOutpost->BaseType = MARINE_BASE_SIEGE; } else { const AvHAIHiveDefinition* NearestHive = AITAC_GetHiveNearestLocation(ThisTF.Location); float DistToHive = vDist2DSq(NearestHive->Location, ThisTF.Location); if (NearestHive && DistToHive <= sqrf(UTIL_MetresToGoldSrcUnits(25.0f))) { if (AIMGR_GetTeamType(EnemyTeam) == AVH_CLASS_TYPE_ALIEN) { // Player was sieging a hive if (NearestHive->Status != HIVE_STATUS_UNBUILT) { NewOutpost->BaseType = MARINE_BASE_SIEGE; } else { // This is an outpost in an empty hive if (DistToHive <= sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) { NewOutpost->BaseType = MARINE_BASE_OUTPOST; } } } else { DeployableSearchFilter EnemyStuffFilter; EnemyStuffFilter.DeployableTeam = EnemyTeam; EnemyStuffFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; EnemyStuffFilter.DeployableTypes = (STRUCTURE_MARINE_PHASEGATE | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY); EnemyStuffFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f); // Player was sieging a bunch of stuff if (AITAC_GetNumDeployablesNearLocation(NearestHive->FloorLocation, &EnemyStuffFilter) > 0) { NewOutpost->BaseType = MARINE_BASE_SIEGE; } } } } } } } void AICOMM_CommanderThink(AvHAIPlayer* pBot) { if (IsPlayerCommander(pBot->Edict)) { if (AICOMM_ShouldBeacon(pBot)) { return; } } // Thanks to EterniumDev (Alien) for the suggestion to have the commander jump out and build if nobody is around to help if (AICOMM_ShouldCommanderLeaveChair(pBot)) { if (IsPlayerCommander(pBot->Edict)) { BotStopCommanderMode(pBot); return; } DeployableSearchFilter StructureFilter; StructureFilter.DeployableTypes = SEARCH_ALL_STRUCTURES; StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(20.0f); StructureFilter.DeployableTeam = pBot->Player->GetTeam(); StructureFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; StructureFilter.ReachabilityTeam = pBot->Player->GetTeam(); StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_COMPLETED | STRUCTURE_STATUS_RECYCLING; AvHAIBuildableStructure NearestUnbuiltStructure = AITAC_FindClosestDeployableToLocation(AITAC_GetCommChairLocation(pBot->Player->GetTeam()), &StructureFilter); if (NearestUnbuiltStructure.IsValid()) { AITASK_SetBuildTask(pBot, &pBot->PrimaryBotTask, NearestUnbuiltStructure.edict, false); } BotProgressTask(pBot, &pBot->PrimaryBotTask); return; } if (!IsPlayerCommander(pBot->Edict)) { BotProgressTakeCommandTask(pBot); return; } if (gpGlobals->time < pBot->next_commander_action_time) { return; } if (pBot->Bases.size() == 0) { AICOMM_PopulateBaseList(pBot); } AICOMM_ManageActiveBases(pBot); AICOMM_DeployBases(pBot); AICOMM_UpdatePlayerOrders(pBot); if (AICOMM_CheckForNextSupportAction(pBot)) { return; } if (AICOMM_CheckForNextRecycleAction(pBot)) { return; } if (AICOMM_CheckForNextBuildAction(pBot)) { return; } if (AICOMM_CheckForNextResearchAction(pBot)) { return; } if (AICOMM_CheckForNextSupplyAction(pBot)) { return; } } bool AICOMM_IsMarineBaseValid(AvHAIMarineBase* Base) { if (Base->PlacedStructures.size() > 0) { return true; } if (!Base->bBaseInitialised && !Base->bIsActive) { return false; } if ((Base->bRecycleBase || Base->bBaseInitialised) && Base->PlacedStructures.size() == 0) { return false; } return true; } void AICOMM_UpdateBaseStatus(AvHAIPlayer* pBot, AvHAIMarineBase* Base) { if (!pBot || !Base) { return; } AvHTeamNumber BotTeam = pBot->Player->GetTeam(); AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam); // TODO: Add check if base is doomed float BuildRadius = UTIL_MetresToGoldSrcUnits(10.0f); float EnemyRadius = UTIL_MetresToGoldSrcUnits(10.0f); switch (Base->BaseType) { case MARINE_BASE_SIEGE: AICOMM_UpdateSiegeBaseStatus(pBot, Base); BuildRadius = UTIL_MetresToGoldSrcUnits(10.0f); EnemyRadius = UTIL_MetresToGoldSrcUnits(5.0f); break; case MARINE_BASE_GUARDPOST: AICOMM_UpdateGuardpostStatus(pBot, Base); BuildRadius = UTIL_MetresToGoldSrcUnits(5.0f); EnemyRadius = UTIL_MetresToGoldSrcUnits(5.0f); break; case MARINE_BASE_OUTPOST: AICOMM_UpdateOutpostStatus(pBot, Base); BuildRadius = UTIL_MetresToGoldSrcUnits(10.0f); EnemyRadius = UTIL_MetresToGoldSrcUnits(10.0f); break; case MARINE_BASE_MAINBASE: AICOMM_UpdateMainBaseStatus(pBot, Base); BuildRadius = UTIL_MetresToGoldSrcUnits(15.0f); break; default: break; } if (!Base->bRecycleBase && Base->bIsActive) { Base->bCanBeBuiltOut = AITAC_CanBuildOutBase(Base); if (Base->bCanBeBuiltOut) { Base->NumBuilders = AITAC_GetNumPlayersOfTeamInArea(BotTeam, Base->BaseLocation, BuildRadius, false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER); Base->NumEnemies = AITAC_GetNumPlayersOnTeamWithLOS(EnemyTeam, Base->BaseLocation + Vector(0.0f, 0.0f, 16.0f), EnemyRadius, nullptr); } } } vector AICOMM_GetBaseStructures(AvHAIMarineBase* Base) { vector Result; for (auto it = Base->PlacedStructures.begin(); it != Base->PlacedStructures.end(); it++) { AvHAIBuildableStructure ThisStructure = AITAC_GetDeployableStructureByEntIndex(Base->BaseTeam, (*it)); Result.push_back(ThisStructure); } return Result; } void AICOMM_UpdateMainBaseStatus(AvHAIPlayer* pBot, AvHAIMarineBase* Base) { // If this main base location is far from the active comm chair we're in, then we must be relocating // In that case, if we've not yet established the base and the enemy have already started building fortifications there, then abandon the base. if (!Base->bIsBaseEstablished && vDist2DSq(Base->BaseLocation, AITAC_GetCommChairLocation(pBot->Player->GetTeam())) > sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) { DeployableSearchFilter EnemyStuffFilter; EnemyStuffFilter.DeployableTeam = AIMGR_GetEnemyTeam(pBot->Player->GetTeam()); EnemyStuffFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; EnemyStuffFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; EnemyStuffFilter.DeployableTypes = (STRUCTURE_MARINE_COMMCHAIR | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY | STRUCTURE_MARINE_PHASEGATE | STRUCTURE_ALIEN_OFFENCECHAMBER); EnemyStuffFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f); if (AITAC_DeployableExistsAtLocation(Base->BaseLocation, &EnemyStuffFilter)) { Base->bRecycleBase = true; Base->bIsActive = false; return; } } // Either the enemy haven't started fortifying this area, or this base is where our active comm chair is. Either way, we need to crack on Base->bRecycleBase = false; Base->bIsActive = true; // Check how far we are into building this outpost. If we have a TF and at least 3 sentries then we can consider it "established" // even if it isn't finished yet vector BaseStructures = AICOMM_GetBaseStructures(Base); bool bHasCommChair = true; int NumInfPortals = 0; for (auto structIt = BaseStructures.begin(); structIt != BaseStructures.end(); structIt++) { AvHAIBuildableStructure ThisStructure = (*structIt); if (ThisStructure.StructureType == STRUCTURE_MARINE_COMMCHAIR) { bHasCommChair = true; } if (ThisStructure.StructureType == STRUCTURE_MARINE_INFANTRYPORTAL && (ThisStructure.StructureStatusFlags & STRUCTURE_STATUS_COMPLETED)) { NumInfPortals++; } } Base->bIsBaseEstablished = bHasCommChair && NumInfPortals > 0; } void AICOMM_UpdateGuardpostStatus(AvHAIPlayer* pBot, AvHAIMarineBase* Base) { // If we've not started building this base and the enemy are already securing this area then don't start building it out if (!Base->bBaseInitialised) { DeployableSearchFilter EnemyStuffFilter; EnemyStuffFilter.DeployableTeam = AIMGR_GetEnemyTeam(pBot->Player->GetTeam()); EnemyStuffFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; EnemyStuffFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; EnemyStuffFilter.DeployableTypes = (STRUCTURE_MARINE_COMMCHAIR | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY | STRUCTURE_MARINE_PHASEGATE | STRUCTURE_ALIEN_OFFENCECHAMBER); EnemyStuffFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f); if (AITAC_DeployableExistsAtLocation(Base->BaseLocation, &EnemyStuffFilter)) { Base->bRecycleBase = false; Base->bIsActive = false; return; } } // Check how far we are into building this outpost. If we have a TF and at least 3 sentries then we can consider it "established" // even if it isn't finished yet vector BaseStructures = AICOMM_GetBaseStructures(Base); bool bHasTF = true; int NumSentries = 0; for (auto structIt = BaseStructures.begin(); structIt != BaseStructures.end(); structIt++) { AvHAIBuildableStructure ThisStructure = (*structIt); if (ThisStructure.StructureType & (STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY)) { bHasTF = true; } if (ThisStructure.StructureType == STRUCTURE_MARINE_TURRET) { NumSentries++; } } Base->bIsBaseEstablished = bHasTF && NumSentries > 2; } void AICOMM_UpdateOutpostStatus(AvHAIPlayer* pBot, AvHAIMarineBase* Base) { AvHTeamNumber BotTeam = pBot->Player->GetTeam(); const AvHAIHiveDefinition* NearestHive = AITAC_GetHiveNearestLocation(Base->BaseLocation); // Recycle this base if it's meant to be securing a hive and that hive is not inactive any more // We will replace with a siege base outside the hive if (NearestHive && vDist2DSq(NearestHive->FloorLocation, Base->BaseLocation) < sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) { if (NearestHive->Status != HIVE_STATUS_UNBUILT) { Base->bIsActive = false; Base->bRecycleBase = true; return; } } // If we've not started building this base and the enemy are already securing this area then don't start building it out if (!Base->bBaseInitialised) { DeployableSearchFilter EnemyStuffFilter; EnemyStuffFilter.DeployableTeam = AIMGR_GetEnemyTeam(pBot->Player->GetTeam()); EnemyStuffFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; EnemyStuffFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; EnemyStuffFilter.DeployableTypes = (STRUCTURE_MARINE_COMMCHAIR | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY | STRUCTURE_MARINE_PHASEGATE | STRUCTURE_ALIEN_OFFENCECHAMBER); EnemyStuffFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f); if (AITAC_DeployableExistsAtLocation(Base->BaseLocation, &EnemyStuffFilter)) { Base->bRecycleBase = false; Base->bIsActive = false; return; } } // Check how far we are into building this outpost. If we have a PG (assuming phase tech researched), a TF and at least 3 sentries then we can consider it "established" // even if it isn't finished yet vector BaseStructures = AICOMM_GetBaseStructures(Base); bool bHasPG = true; bool bHasTF = true; int NumSentries = 0; for (auto structIt = BaseStructures.begin(); structIt != BaseStructures.end(); structIt++) { AvHAIBuildableStructure ThisStructure = (*structIt); if (ThisStructure.StructureType == STRUCTURE_MARINE_PHASEGATE) { bHasPG = true; } if (ThisStructure.StructureType & (STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY)) { bHasTF = true; } if (ThisStructure.StructureType == STRUCTURE_MARINE_TURRET) { NumSentries++; } } Base->bIsBaseEstablished = (bHasPG || !AITAC_ResearchIsComplete(BotTeam, TECH_RESEARCH_PHASETECH)) && bHasTF && NumSentries > 2; Base->bIsActive = true; Base->bRecycleBase = false; } void AICOMM_UpdateSiegeBaseStatus(AvHAIPlayer* pBot, AvHAIMarineBase* Base) { AvHTeamNumber BotTeam = pBot->Player->GetTeam(); AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam); DeployableSearchFilter EnemyStuffFilter; EnemyStuffFilter.DeployableTeam = EnemyTeam; EnemyStuffFilter.MaxSearchRadius = BALANCE_VAR(kSiegeTurretRange); bool bStillStuffToSiege = AITAC_DeployableExistsAtLocation(Base->BaseLocation, &EnemyStuffFilter); const AvHAIHiveDefinition* NearestHive = AITAC_GetHiveNearestLocation(Base->BaseLocation); if (bStillStuffToSiege || (NearestHive && NearestHive->Status != HIVE_STATUS_UNBUILT && vDist2DSq(Base->BaseLocation, NearestHive->Location) < sqrf(BALANCE_VAR(kSiegeTurretRange)))) { Base->bRecycleBase = false; Base->bIsActive = true; return; } else { if (!NearestHive || vDist2DSq(Base->BaseLocation, NearestHive->Location) > sqrf(BALANCE_VAR(kSiegeTurretRange))) { Base->bRecycleBase = false; Base->bIsActive = false; return; } else { for (auto it = pBot->Bases.begin(); it != pBot->Bases.end(); it++) { if (it->BaseType == MARINE_BASE_OUTPOST && vDist2DSq(NearestHive->FloorLocation, it->BaseLocation) < sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) { if (it->bIsBaseEstablished) { Base->bRecycleBase = true; Base->bIsActive = false; return; } } } Base->bRecycleBase = false; Base->bIsActive = false; return; } } Base->bRecycleBase = false; Base->bIsActive = true; } void AICOMM_DeployBases(AvHAIPlayer* pBot) { AvHTeamNumber BotTeam = pBot->Player->GetTeam(); AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam); Vector CurrentChairLocation = AITAC_GetCommChairLocation(BotTeam); if (AICOMM_ShouldCommanderRelocate(pBot)) { AvHAIMarineBase* CurrentMainBase = nullptr; for (auto it = pBot->Bases.begin(); it != pBot->Bases.end(); it++) { if (it->BaseType == MARINE_BASE_MAINBASE) { if (!CurrentMainBase || it->bIsBaseEstablished) { CurrentMainBase = &(*it); } } } if (!CurrentMainBase) { AICOMM_AddNewBase(pBot, CurrentChairLocation, MARINE_BASE_MAINBASE); return; } bool bMainBaseAtCommChair = vDist2DSq(CurrentMainBase->BaseLocation, CurrentChairLocation) < sqrf(UTIL_MetresToGoldSrcUnits(10.0f)); if (!bMainBaseAtCommChair && !CurrentMainBase->bIsBaseEstablished && AIMGR_GetMatchLength() > 90.0f) { AvHAIMarineBase* ChairBase = AICOMM_GetNearestBaseToLocation(pBot, CurrentChairLocation); if (ChairBase != CurrentMainBase) { CurrentMainBase->BaseType = MARINE_BASE_OUTPOST; ChairBase->BaseType = MARINE_BASE_MAINBASE; char NewMsg[128]; if (AICOMM_GetRelocationMessage(CurrentChairLocation, NewMsg)) { BotSay(pBot, true, 1.0f, NewMsg); } return; } else { CurrentMainBase->BaseType = MARINE_BASE_OUTPOST; AICOMM_AddNewBase(pBot, CurrentChairLocation, MARINE_BASE_MAINBASE); char NewMsg[128]; if (AICOMM_GetRelocationMessage(CurrentChairLocation, NewMsg)) { BotSay(pBot, true, 1.0f, NewMsg); } return; } } else { Vector NewBaseLocation = AITAC_FindNewTeamRelocationPoint(BotTeam); if (!vIsZero(NewBaseLocation) && vDist2DSq(NewBaseLocation, CurrentMainBase->BaseLocation) > sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) { // Turn our starting base into an outpost, and lay down a new main base elsewhere CurrentMainBase->BaseType = MARINE_BASE_OUTPOST; AICOMM_AddNewBase(pBot, NewBaseLocation, MARINE_BASE_MAINBASE); char NewMsg[128]; if (AICOMM_GetRelocationMessage(NewBaseLocation, NewMsg)) { BotSay(pBot, true, 1.0f, NewMsg); } return; } else { if (!bMainBaseAtCommChair) { AvHAIMarineBase* ChairBase = AICOMM_GetNearestBaseToLocation(pBot, CurrentChairLocation); if (ChairBase != CurrentMainBase) { CurrentMainBase->BaseType = MARINE_BASE_OUTPOST; ChairBase->BaseType = MARINE_BASE_MAINBASE; char NewMsg[128]; if (AICOMM_GetRelocationMessage(CurrentChairLocation, NewMsg)) { BotSay(pBot, true, 1.0f, NewMsg); } return; } } } } } vector AllHives = AITAC_GetAllHives(); bool bNeedsResources = AICOMM_ShouldCommanderPrioritiseNodes(pBot); for (auto it = AllHives.begin(); it != AllHives.end(); it++) { const AvHAIHiveDefinition* ThisHive = (*it); if (ThisHive->Status == HIVE_STATUS_UNBUILT) { bool bHasOutpost = false; for (auto baseIt = pBot->Bases.begin(); baseIt != pBot->Bases.end(); baseIt++) { AvHAIMarineBase* ThisBase = &(*baseIt); if ((ThisBase->BaseType == MARINE_BASE_OUTPOST || ThisBase->BaseType == MARINE_BASE_MAINBASE) && !ThisBase->bRecycleBase) { if (vDist2DSq(ThisHive->FloorLocation, ThisBase->BaseLocation) < sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) { bHasOutpost = true; break; } } } if (!bHasOutpost) { DeployableSearchFilter EnemyStuffFilter; EnemyStuffFilter.DeployableTeam = AIMGR_GetEnemyTeam(pBot->Player->GetTeam()); EnemyStuffFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; EnemyStuffFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; EnemyStuffFilter.DeployableTypes = (STRUCTURE_MARINE_COMMCHAIR | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY | STRUCTURE_MARINE_PHASEGATE | STRUCTURE_ALIEN_OFFENCECHAMBER); EnemyStuffFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f); if (!AITAC_DeployableExistsAtLocation(ThisHive->FloorLocation, &EnemyStuffFilter)) { AICOMM_AddNewBase(pBot, ThisHive->FloorLocation, MARINE_BASE_OUTPOST); } } } else { // Only siege if we can use phase gates // TODO: Define scenario where we can siege without using phase gates. Probably when we don't have any outposts or guardposts to build if (!AITAC_ResearchIsComplete(BotTeam, TECH_RESEARCH_PHASETECH)) { continue; } // Also don't try sieging if we don't have enough nodes if (bNeedsResources && pBot->Player->GetResources() < 50) { continue; } bool bHasSiege = false; for (auto baseIt = pBot->Bases.begin(); baseIt != pBot->Bases.end(); baseIt++) { AvHAIMarineBase* ThisBase = &(*baseIt); if (ThisBase->BaseType == MARINE_BASE_SIEGE && !ThisBase->bRecycleBase) { if (vDist2DSq(ThisHive->Location, ThisBase->BaseLocation) < sqrf(BALANCE_VAR(kSiegeTurretRange))) { bHasSiege = true; break; } } } if (!bHasSiege) { vector PotentialBuilders = AITAC_GetAllPlayersOfTeamInArea(BotTeam, ThisHive->FloorLocation, BALANCE_VAR(kSiegeTurretRange), false, nullptr, AVH_USER3_COMMANDER_PLAYER); for (auto playerIt = PotentialBuilders.begin(); playerIt != PotentialBuilders.end(); playerIt++) { AvHPlayer* ThisPlayer = (*playerIt); if (vDist2DSq(ThisPlayer->pev->origin, ThisHive->FloorLocation) > sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) { if (!(ThisPlayer->pev->iuser4 & MASK_VIS_SIGHTED)) { DeployableSearchFilter EnemyStuffFilter; EnemyStuffFilter.DeployableTeam = AIMGR_GetEnemyTeam(pBot->Player->GetTeam()); EnemyStuffFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; EnemyStuffFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; EnemyStuffFilter.DeployableTypes = (STRUCTURE_MARINE_COMMCHAIR | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY | STRUCTURE_MARINE_PHASEGATE | STRUCTURE_ALIEN_OFFENCECHAMBER); EnemyStuffFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); if (!AITAC_DeployableExistsAtLocation(ThisHive->FloorLocation, &EnemyStuffFilter)) { AICOMM_AddNewBase(pBot, ThisPlayer->pev->origin, MARINE_BASE_SIEGE); } } } } } } } } void AICOMM_ManageActiveBases(AvHAIPlayer* pBot) { AvHTeamNumber BotTeam = pBot->Player->GetTeam(); AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam); for (auto it = pBot->Bases.begin(); it != pBot->Bases.end();) { AvHAIMarineBase* ThisBase = &(*it); if (!ThisBase->bBaseInitialised) { ThisBase->bBaseInitialised = it->PlacedStructures.size() > 0; } for (auto structIt = it->PlacedStructures.begin(); structIt != it->PlacedStructures.end();) { AvHAIBuildableStructure StructureRef = AITAC_GetDeployableStructureByEntIndex(BotTeam, (*structIt)); if (!StructureRef.IsValid() || (StructureRef.StructureStatusFlags & STRUCTURE_STATUS_RECYCLING)) { structIt = it->PlacedStructures.erase(structIt); } else { structIt++; } } if (!AICOMM_IsMarineBaseValid(&(*it))) { it = pBot->Bases.erase(it); } else { AICOMM_UpdateBaseStatus(pBot, ThisBase); it++; } } } bool AICOMM_GetRelocationMessage(Vector RelocationPoint, char* MessageBuffer) { const AvHAIHiveDefinition* NearestHive = AITAC_GetHiveNearestLocation(RelocationPoint); string LocationName; if (NearestHive && vDist2DSq(RelocationPoint, NearestHive->FloorLocation) < sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) { LocationName = string(NearestHive->HiveName); } else { LocationName = AITAC_GetLocationName(RelocationPoint); } if (LocationName.empty()) { sprintf(MessageBuffer, "Get ready to relocate"); return true; } int MsgIndex = irandrange(0, 2); switch (MsgIndex) { case 0: sprintf(MessageBuffer, "We're relocating to %s, lads", LocationName.c_str()); return true; case 1: sprintf(MessageBuffer, "Relocate to %s, go go go", LocationName.c_str()); return true; case 2: sprintf(MessageBuffer, "I'm relocating to %s", LocationName.c_str()); return true; default: sprintf(MessageBuffer, "We're relocating, get ready"); return true; } return false; } bool AICOMM_ShouldCommanderLeaveChair(AvHAIPlayer* pBot) { if (pBot->BotRole != BOT_ROLE_COMMAND) { return true; } if (AIMGR_GetCommanderMode() == COMMANDERMODE_DISABLED) { return true; } if (AIMGR_GetCommanderMode() == COMMANDERMODE_IFNOHUMAN) { if (AIMGR_GetNumHumanPlayersOnTeam(pBot->Player->GetTeam()) > 0) { return true;} } if (IsPlayerCommander(pBot->Edict) && AIMGR_GetCommanderAllowedTime(pBot->Player->GetTeam()) > gpGlobals->time) { return true; } int NumAliveMarinesInBase = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), AITAC_GetCommChairLocation(pBot->Player->GetTeam()), UTIL_MetresToGoldSrcUnits(30.0f), true, pBot->Edict, AVH_USER3_NONE); if (NumAliveMarinesInBase > 0) { return false; } DeployableSearchFilter StructureFilter; StructureFilter.DeployableTypes = SEARCH_ALL_STRUCTURES; StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(20.0f); StructureFilter.DeployableTeam = pBot->Player->GetTeam(); StructureFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; StructureFilter.ReachabilityTeam = pBot->Player->GetTeam(); StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_COMPLETED | STRUCTURE_STATUS_RECYCLING; int NumUnbuiltStructuresInBase = AITAC_GetNumDeployablesNearLocation(AITAC_GetCommChairLocation(pBot->Player->GetTeam()), &StructureFilter); if (NumUnbuiltStructuresInBase == 0) { return false; } StructureFilter.DeployableTypes = STRUCTURE_MARINE_INFANTRYPORTAL; StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; StructureFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; int NumInfantryPortals = AITAC_GetNumDeployablesNearLocation(AITAC_GetCommChairLocation(pBot->Player->GetTeam()), &StructureFilter); if (NumInfantryPortals == 0) { return true; } if (AITAC_GetNumDeadPlayersOnTeam(pBot->Player->GetTeam()) == 0) { return true; } return false; } const AvHAIHiveDefinition* AICOMM_GetEmptyHiveOpportunityNearestLocation(AvHAIPlayer* CommanderBot, const Vector SearchLocation) { AvHTeamNumber CommanderTeam = CommanderBot->Player->GetTeam(); AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(CommanderTeam); const AvHAIHiveDefinition* Result = nullptr; float MinDist = 0.0f; 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); if (Hive->Status != HIVE_STATUS_UNBUILT) { continue; } 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; StructureFilter.DeployableTeam = CommanderTeam; StructureFilter.ReachabilityTeam = CommanderTeam; StructureFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; StructureFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY; StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); AvHAIBuildableStructure ExistingStructure = AITAC_FindClosestDeployableToLocation(Hive->FloorLocation, &StructureFilter); if (ExistingStructure.IsValid() && (ExistingStructure.Purpose == STRUCTURE_PURPOSE_FORTIFY || ExistingStructure.Purpose == STRUCTURE_PURPOSE_BASE)) { SecureLocation = ExistingStructure.Location; } float MarineDist = (ExistingStructure.IsValid()) ? UTIL_MetresToGoldSrcUnits(5.0f) : UTIL_MetresToGoldSrcUnits(10.0f); if (AITAC_GetNearestHiddenPlayerInLocation(CommanderTeam, SecureLocation, MarineDist) == nullptr) { continue; } int NumEnemiesNearby = AITAC_GetNumPlayersOnTeamWithLOS(EnemyTeam, SecureLocation + Vector(0.0f, 0.0f, 10.0f), UTIL_MetresToGoldSrcUnits(15.0f), nullptr); if (NumEnemiesNearby > 0) { continue; } DeployableSearchFilter EnemyStuff; EnemyStuff.DeployableTeam = EnemyTeam; EnemyStuff.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; EnemyStuff.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; EnemyStuff.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f); if (AIMGR_GetTeamType(EnemyTeam) == AVH_CLASS_TYPE_MARINE) { EnemyStuff.DeployableTypes = (STRUCTURE_MARINE_PHASEGATE | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY); } else { EnemyStuff.DeployableTypes = STRUCTURE_ALIEN_OFFENCECHAMBER; } if (AITAC_DeployableExistsAtLocation(SecureLocation, &EnemyStuff)) { continue; } float ThisDist = vDist2DSq(Hive->FloorLocation, SearchLocation); if (!Result || ThisDist < MinDist) { Result = Hive; MinDist = ThisDist; } } return Result; } bool AICOMM_IsHiveFullySecured(AvHAIPlayer* CommanderBot, const AvHAIHiveDefinition* Hive, bool bIncludeElectrical) { AvHTeamNumber CommanderTeam = CommanderBot->Player->GetTeam(); bool bPhaseGatesAvailable = AITAC_PhaseGatesAvailable(CommanderTeam); bool bHasPhaseGate = false; bool bHasTurretFactory = false; bool bTurretFactoryElectrified = false; int NumTurrets = 0; DeployableSearchFilter SearchFilter; SearchFilter.DeployableTypes = (STRUCTURE_MARINE_PHASEGATE | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY); SearchFilter.DeployableTeam = CommanderTeam; SearchFilter.ReachabilityTeam = CommanderTeam; SearchFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; SearchFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; SearchFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; SearchFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); vector HiveStructures = AITAC_FindAllDeployables(Hive->FloorLocation, &SearchFilter); for (auto it = HiveStructures.begin(); it != HiveStructures.end(); it++) { AvHAIBuildableStructure Structure = (*it); if (Structure.StructureType == STRUCTURE_MARINE_PHASEGATE) { bHasPhaseGate = true; } if (Structure.StructureType == STRUCTURE_MARINE_TURRETFACTORY || Structure.StructureType == STRUCTURE_MARINE_ADVTURRETFACTORY) { bHasTurretFactory = true; bTurretFactoryElectrified = (Structure.StructureStatusFlags & STRUCTURE_STATUS_ELECTRIFIED); SearchFilter.DeployableTypes = STRUCTURE_MARINE_TURRET; SearchFilter.MaxSearchRadius = BALANCE_VAR(kTurretFactoryBuildDistance); NumTurrets = AITAC_GetNumDeployablesNearLocation(Structure.Location, &SearchFilter); } } const AvHAIResourceNode* ResNode = Hive->HiveResNodeRef; bool bSecuredResNode = (!ResNode || (ResNode->bIsOccupied && ResNode->OwningTeam == CommanderTeam && UTIL_StructureIsFullyBuilt(ResNode->ActiveTowerEntity))); return ((!bPhaseGatesAvailable || bHasPhaseGate) && bHasTurretFactory && (!bIncludeElectrical || bTurretFactoryElectrified) && NumTurrets >= 5 && bSecuredResNode); } bool AICOMM_IsMainBaseInTrouble(AvHAIPlayer* pBot, AvHAIMarineBase* Base) { AvHTeamNumber BotTeam = pBot->Player->GetTeam(); AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam); Vector BaseLocation = AITAC_GetTeamStartingLocation(BotTeam); vector BaseStructures = AICOMM_GetBaseStructures(Base); bool bHasInfantryPortals = false; bool bCriticalStructureUnderAttack = false; bool bAnyStructureUnderAttack = false; for (auto it = BaseStructures.begin(); it != BaseStructures.end(); it++) { AvHAIBuildableStructure ThisStructure = (*it); if (ThisStructure.StructureStatusFlags & STRUCTURE_STATUS_UNDERATTACK) { bAnyStructureUnderAttack = true; if (ThisStructure.StructureType == STRUCTURE_MARINE_COMMCHAIR || ThisStructure.StructureType == STRUCTURE_MARINE_INFANTRYPORTAL) { bCriticalStructureUnderAttack = true; } } } if (!bAnyStructureUnderAttack) { return false; } float EnemyStrength = 0.0f; float DefenderStrength = 0.0f; vector DefendingPlayers = AITAC_GetAllPlayersOfTeamInArea(BotTeam, Base->BaseLocation, UTIL_MetresToGoldSrcUnits(20.0f), true, nullptr, AVH_USER3_COMMANDER_PLAYER); for (auto it = DefendingPlayers.begin(); it != DefendingPlayers.end(); it++) { AvHPlayer* ThisPlayer = (*it); edict_t* PlayerEdict = ThisPlayer->edict(); float ThisPlayerValue = 1.0f; if (PlayerHasHeavyArmour(PlayerEdict)) { ThisPlayerValue += 0.5f; } if (PlayerHasSpecialWeapon(ThisPlayer)) { ThisPlayerValue += 1.0f; } DefenderStrength += ThisPlayerValue; } if (AIMGR_GetTeamType(EnemyTeam) == AVH_CLASS_TYPE_MARINE) { vector AttackingPlayers = AITAC_GetAllPlayersOfTeamInArea(EnemyTeam, Base->BaseLocation, UTIL_MetresToGoldSrcUnits(10.0f), false, nullptr, AVH_USER3_COMMANDER_PLAYER); for (auto it = AttackingPlayers.begin(); it != AttackingPlayers.end(); it++) { AvHPlayer* ThisPlayer = (*it); edict_t* PlayerEdict = ThisPlayer->edict(); float ThisPlayerValue = 1.0f; if (PlayerHasHeavyArmour(PlayerEdict)) { ThisPlayerValue += 0.5f; } if (PlayerHasSpecialWeapon(ThisPlayer)) { ThisPlayerValue += 1.0f; } DefenderStrength += ThisPlayerValue; } } else { int NumSkulks = AITAC_GetNumPlayersOfTeamAndClassInArea(EnemyTeam, BaseLocation, UTIL_MetresToGoldSrcUnits(10.0f), false, nullptr, AVH_USER3_ALIEN_PLAYER1); int NumFades = AITAC_GetNumPlayersOfTeamAndClassInArea(EnemyTeam, BaseLocation, UTIL_MetresToGoldSrcUnits(10.0f), false, nullptr, AVH_USER3_ALIEN_PLAYER4); int NumOnos = AITAC_GetNumPlayersOfTeamAndClassInArea(EnemyTeam, BaseLocation, UTIL_MetresToGoldSrcUnits(10.0f), false, nullptr, AVH_USER3_ALIEN_PLAYER5); EnemyStrength = (NumSkulks * 0.8f) + (NumFades * 2) + (NumOnos * 2.5f); } if (EnemyStrength >= 3.0f && EnemyStrength >= DefenderStrength * 3.0f) { return true; } return false; } bool AICOMM_ShouldBeacon(AvHAIPlayer* pBot) { if (pBot->Player->GetResources() < BALANCE_VAR(kDistressBeaconCost)) { return false; } AvHTeamNumber BotTeam = pBot->Player->GetTeam(); DeployableSearchFilter ObservatoryFilter; ObservatoryFilter.DeployableTypes = STRUCTURE_MARINE_OBSERVATORY; ObservatoryFilter.DeployableTeam = BotTeam; ObservatoryFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; ObservatoryFilter.ExcludeStatusFlags = (STRUCTURE_STATUS_RECYCLING | STRUCTURE_STATUS_RESEARCHING); AvHAIBuildableStructure Observatory = AITAC_FindClosestDeployableToLocation(ZERO_VECTOR, &ObservatoryFilter); if (!Observatory.IsValid()) { return false; } for (auto it = pBot->Bases.begin(); it != pBot->Bases.end(); it++) { if (it->BaseType == MARINE_BASE_MAINBASE) { return AICOMM_IsMainBaseInTrouble(pBot, &(*it)); } } return false; } void AICOMM_ReceiveChatRequest(AvHAIPlayer* Commander, edict_t* Requestor, const char* Request) { AvHMessageID NewRequestType = MESSAGE_NULL; if (!stricmp(Request, "shotgun") || !stricmp(Request, "sg") || !stricmp(Request, "shotty")) { NewRequestType = BUILD_SHOTGUN; } else if (!stricmp(Request, "welder")) { NewRequestType = BUILD_WELDER; } else if (!stricmp(Request, "HMG")) { NewRequestType = BUILD_HMG; } else if (!stricmp(Request, "gl")) { NewRequestType = BUILD_GRENADE_GUN; } else if (!stricmp(Request, "mines")) { NewRequestType = BUILD_MINES; } else if (!stricmp(Request, "ha") || !stricmp(Request, "heavy") || !stricmp(Request, "heavyarmor") || !stricmp(Request, "heavy armor")) { NewRequestType = BUILD_HEAVY; } else if (!stricmp(Request, "jp") || !stricmp(Request, "jetpack") || !stricmp(Request, "jet pack")) { NewRequestType = BUILD_JETPACK; } else if (!stricmp(Request, "cat") || !stricmp(Request, "cats") || !stricmp(Request, "catalysts")) { NewRequestType = BUILD_CAT; } else if (!stricmp(Request, "pg") || !stricmp(Request, "phase") || !stricmp(Request, "phasegate")) { NewRequestType = BUILD_PHASEGATE; } else if (!stricmp(Request, "TF") || !stricmp(Request, "turretfactory")) { NewRequestType = BUILD_TURRET_FACTORY; } else if (!stricmp(Request, "turret")) { NewRequestType = BUILD_TURRET; } else if (!stricmp(Request, "armory") || !stricmp(Request, "armoury")) { NewRequestType = BUILD_ARMORY; } else if (!stricmp(Request, "scan")) { NewRequestType = BUILD_SCAN; } else if (!stricmp(Request, "cc") || !stricmp(Request, "chair") || !stricmp(Request, "command chair")) { NewRequestType = BUILD_COMMANDSTATION; } else if (!stricmp(Request, "obs") || !stricmp(Request, "observatory")) { NewRequestType = BUILD_OBSERVATORY; } else if (!stricmp(Request, "pl") || !stricmp(Request, "protolab") || !stricmp(Request, "proto lab") || !stricmp(Request, "prototype lab")) { NewRequestType = BUILD_PROTOTYPE_LAB; } else if (!stricmp(Request, "ip") || !stricmp(Request, "portal") || !stricmp(Request, "inf portal") || !stricmp(Request, "infantry portal")) { NewRequestType = BUILD_INFANTRYPORTAL; } else if (!stricmp(Request, "al") || !stricmp(Request, "armslab") || !stricmp(Request, "arms lab")) { NewRequestType = BUILD_ARMSLAB; } else if (!stricmp(Request, "sc") || !stricmp(Request, "st") || !stricmp(Request, "siegeturret") || !stricmp(Request, "siege turret") || !stricmp(Request, "siegecannon") || !stricmp(Request, "siege cannon")) { NewRequestType = BUILD_SIEGE; } if (NewRequestType == MESSAGE_NULL) { return; } ai_commander_request* ExistingRequest = AICOMM_GetExistingRequestForPlayer(Commander, Requestor); ai_commander_request NewRequest; ai_commander_request* RequestRef = (ExistingRequest) ? ExistingRequest : &NewRequest; RequestRef->bNewRequest = true; RequestRef->bResponded = false; RequestRef->RequestTime = gpGlobals->time; RequestRef->RequestType = NewRequestType; RequestRef->Requestor = Requestor; RequestRef->RequestLocation = UTIL_GetFloorUnderEntity(Requestor) + (UTIL_GetForwardVector2D(Requestor->v.angles) * 75.0f); if (!ExistingRequest) { Commander->ActiveRequests.push_back(NewRequest); } } bool AICOMM_ShouldCommanderRelocate(AvHAIPlayer* pBot) { if (!CONFIG_IsRelocationAllowed()) { return false; } AvHTeamNumber Team = pBot->Player->GetTeam(); AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(Team); edict_t* CurrentCommChair = AITAC_GetCommChair(Team); if (FNullEnt(CurrentCommChair)) { return false; } AvHAIMarineBase* CurrentMainBase = nullptr; for (auto it = pBot->Bases.begin(); it != pBot->Bases.end(); it++) { if (it->BaseType == MARINE_BASE_MAINBASE) { CurrentMainBase = &(*it); break; } } if (!CurrentMainBase) { return true; } if (!CurrentMainBase->bIsActive || CurrentMainBase->bRecycleBase) { return true; } bool bMainBaseIsAtChair = vDist2DSq(CurrentCommChair->v.origin, CurrentMainBase->BaseLocation) < sqrf(UTIL_MetresToGoldSrcUnits(10.0f)); // We're not able to relocate after 90 seconds, best find somewhere else or we're in trouble if (AIMGR_GetMatchLength() > 90.0f && !CurrentMainBase->bIsBaseEstablished && !bMainBaseIsAtChair) { return (CurrentMainBase->NumBuilders == 0); } if (AITAC_IsRelocationAtStartEnabled() && AIMGR_GetMatchLength() < 90.0f) { // We've not tried relocating yet if (bMainBaseIsAtChair) { return true; } const AvHAIHiveDefinition* RelocationHive = AITAC_GetHiveNearestLocation(CurrentMainBase->BaseLocation); // The hive we were planning to relocate to has somehow started being built within 90 seconds of start. Not sure how, but we better not move there now if (RelocationHive && vDist2DSq(RelocationHive->FloorLocation, CurrentMainBase->BaseLocation) < sqrf(UTIL_MetresToGoldSrcUnits(15.0f))) { if (RelocationHive->Status != HIVE_STATUS_UNBUILT) { return true; } } // The enemy beat us to it and has started setting up shop in that location DeployableSearchFilter EnemyStuffFilter; EnemyStuffFilter.DeployableTeam = EnemyTeam; EnemyStuffFilter.DeployableTypes = (STRUCTURE_MARINE_COMMCHAIR | STRUCTURE_MARINE_INFANTRYPORTAL | STRUCTURE_MARINE_TURRETFACTORY); EnemyStuffFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; EnemyStuffFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; EnemyStuffFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f); // Those marine bastards stole our spot! if (AITAC_DeployableExistsAtLocation(CurrentMainBase->BaseLocation, &EnemyStuffFilter)) { return true; } EnemyStuffFilter.DeployableTypes = STRUCTURE_ALIEN_OFFENCECHAMBER; // Those alien bastards stole our spot! if (AITAC_GetNumDeployablesNearLocation(CurrentMainBase->BaseLocation, &EnemyStuffFilter) > 1) { return true; } } // If we have an established main base, then only relocate if it's fucked. This means: // Base is not at the original start, or we can't beacon to save the base // Critical structures are under attack (comm chair or infantry portals) // The number of enemies is overwhelming and we're doomed if (CurrentMainBase->bIsBaseEstablished) { if (CurrentMainBase->NumBuilders > 0) { return false; } DeployableSearchFilter ObsFilter; ObsFilter.DeployableTeam = Team; ObsFilter.DeployableTypes = STRUCTURE_MARINE_OBSERVATORY; ObsFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; ObsFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; bool bCanBeacon = AITAC_DeployableExistsAtLocation(ZERO_VECTOR, &ObsFilter); bool bCriticalStructureAttacked = false; bool bBaseIsAtStart = vDist2DSq(CurrentMainBase->BaseLocation, AITAC_GetTeamOriginalStartLocation(Team)) < sqrf(UTIL_MetresToGoldSrcUnits(10.0f)); vector BaseStructures = AICOMM_GetBaseStructures(CurrentMainBase); for (auto structIt = BaseStructures.begin(); structIt != BaseStructures.end(); structIt++) { AvHAIBuildableStructure* ThisStructure = &(*structIt); if ((ThisStructure->StructureStatusFlags & STRUCTURE_STATUS_RECYCLING) || !(ThisStructure->StructureStatusFlags & STRUCTURE_STATUS_COMPLETED)) { continue; } if (ThisStructure->StructureStatusFlags & STRUCTURE_STATUS_UNDERATTACK) { if (ThisStructure->StructureType == STRUCTURE_MARINE_COMMCHAIR || ThisStructure->StructureType == STRUCTURE_MARINE_INFANTRYPORTAL) { bCriticalStructureAttacked = true; } } } if (!bCriticalStructureAttacked) { return false; } if (bBaseIsAtStart && bCanBeacon) { return false; } return AICOMM_IsMainBaseInTrouble(pBot, CurrentMainBase); } return false; } 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) { AvHAIBuildableStructure* DeployedStructure = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_RESTOWER, CappableNode->Location); if (DeployedStructure || 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 = AITAC_GetRandomBuildHintInLocation(STRUCTURE_MARINE_COMMCHAIR, pBot->RelocationSpot, UTIL_MetresToGoldSrcUnits(10.0f)); if (!vIsZero(BuildPoint)) { AvHAIBuildableStructure* DeployedStructure = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_COMMCHAIR, BuildPoint, STRUCTURE_PURPOSE_BASE); if (DeployedStructure) { return true; } } BuildPoint = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), pBot->RelocationSpot, UTIL_MetresToGoldSrcUnits(2.0f)); if (!vIsZero(BuildPoint)) { AvHAIBuildableStructure* DeployedStructure = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_COMMCHAIR, BuildPoint, STRUCTURE_PURPOSE_BASE); if (DeployedStructure) { return true; } } BuildPoint = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), pBot->RelocationSpot, UTIL_MetresToGoldSrcUnits(10.0f)); if (!vIsZero(BuildPoint)) { AvHAIBuildableStructure* DeployedStructure = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_COMMCHAIR, BuildPoint, STRUCTURE_PURPOSE_BASE); if (DeployedStructure) { return true; } } 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; } bool AICOMM_BuildOutBase(AvHAIPlayer* pBot, AvHAIMarineBase* BaseToBuildOut) { if (!pBot || !BaseToBuildOut) { return false; } switch (BaseToBuildOut->BaseType) { case MARINE_BASE_SIEGE: return AICOMM_BuildOutSiege(pBot, BaseToBuildOut); case MARINE_BASE_OUTPOST: return AICOMM_BuildOutOutpost(pBot, BaseToBuildOut); case MARINE_BASE_MAINBASE: return AICOMM_BuildOutMainBase(pBot, BaseToBuildOut); default: return false; } return false; } bool AICOMM_BuildOutMainBase(AvHAIPlayer* pBot, AvHAIMarineBase* BaseToBuildOut) { if (!BaseToBuildOut) { return false; } AvHTeamNumber BotTeam = pBot->Player->GetTeam(); AvHAIBuildableStructure CommChair; AvHAIBuildableStructure ArmsLab; AvHAIBuildableStructure ProtoLab; AvHAIBuildableStructure PhaseGate; AvHAIBuildableStructure Armoury; AvHAIBuildableStructure TurretFactory; AvHAIBuildableStructure Observatory; int NumInfPortals = 0; int NumTurrets = 0; int NumIncomplete = 0; int DesiredInfPortals = (int)ceilf((float)AIMGR_GetNumPlayersOnTeam(BotTeam) / 4.0f); for (auto it = BaseToBuildOut->PlacedStructures.begin(); it != BaseToBuildOut->PlacedStructures.end(); it++) { AvHAIBuildableStructure StructureRef = AITAC_GetDeployableStructureByEntIndex(BaseToBuildOut->BaseTeam, *it); if (!StructureRef.IsCompleted()) { NumIncomplete++; } switch (StructureRef.StructureType) { case STRUCTURE_MARINE_COMMCHAIR: CommChair = StructureRef; break; case STRUCTURE_MARINE_INFANTRYPORTAL: NumInfPortals++; break; case STRUCTURE_MARINE_ARMSLAB: { // We do this in case we have more than one arms lab. This ensures we always pick // the complete one (if it exists) and don't accidentally pick up an unfinished one if (!ArmsLab.IsValid() || !ArmsLab.IsCompleted()) { ArmsLab = StructureRef; } } break; case STRUCTURE_MARINE_PROTOTYPELAB: { if (!ProtoLab.IsValid() || !ProtoLab.IsCompleted()) { ProtoLab = StructureRef; } } break; case STRUCTURE_MARINE_PHASEGATE: { if (!PhaseGate.IsValid() || !PhaseGate.IsCompleted()) { PhaseGate = StructureRef; } } break; case STRUCTURE_MARINE_TURRETFACTORY: { if (!TurretFactory.IsValid() || !TurretFactory.IsCompleted()) { TurretFactory = StructureRef; } } break; case STRUCTURE_MARINE_ADVTURRETFACTORY: TurretFactory = StructureRef; break; case STRUCTURE_MARINE_ARMOURY: { if (!Armoury.IsValid() || !Armoury.IsCompleted()) { Armoury = StructureRef; } } break; case STRUCTURE_MARINE_ADVARMOURY: Armoury = StructureRef; break; case STRUCTURE_MARINE_OBSERVATORY: { if (!Observatory.IsValid() || !Observatory.IsCompleted()) { Observatory = StructureRef; } } break; case STRUCTURE_MARINE_TURRET: NumTurrets++; break; default: break; } } if (NumIncomplete > 0) { int NumBuilders = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), BaseToBuildOut->BaseLocation, UTIL_MetresToGoldSrcUnits(10.0f), false, nullptr, AVH_USER3_COMMANDER_PLAYER); // Don't spam too many structures if (NumIncomplete > NumBuilders) { return false; } } if (!CommChair.IsValid()) { if (pBot->Player->GetResources() < BALANCE_VAR(kCommandStationCost)) { return true; } Vector BuildLocation = AITAC_GetRandomBuildHintInLocation(STRUCTURE_MARINE_COMMCHAIR, BaseToBuildOut->BaseLocation, UTIL_MetresToGoldSrcUnits(10.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_COMMCHAIR, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } int NumAttempts = 0; while (NumAttempts < 5) { BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), BaseToBuildOut->BaseLocation, UTIL_MetresToGoldSrcUnits(5.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_COMMCHAIR, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(ONOS_BASE_NAV_PROFILE), BaseToBuildOut->BaseLocation, UTIL_MetresToGoldSrcUnits(10.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_COMMCHAIR, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } NumAttempts++; } return pBot->Player->GetResources() <= (BALANCE_VAR(kCommandStationCost) * 1.5f); } if (!CommChair.IsCompleted()) { return false; } if (NumInfPortals < DesiredInfPortals) { if (pBot->Player->GetResources() < BALANCE_VAR(kInfantryPortalCost)) { return true; } Vector BuildLocation = AITAC_GetRandomBuildHintInLocation(STRUCTURE_MARINE_INFANTRYPORTAL, CommChair.Location, BALANCE_VAR(kCommandStationBuildDistance)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_INFANTRYPORTAL, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } int NumAttempts = 0; while (NumAttempts < 5) { BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), CommChair.Location, BALANCE_VAR(kCommandStationBuildDistance) * 0.8f); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_INFANTRYPORTAL, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(ONOS_BASE_NAV_PROFILE), CommChair.Location, BALANCE_VAR(kCommandStationBuildDistance) * 0.8f); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_INFANTRYPORTAL, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } NumAttempts++; } return pBot->Player->GetResources() <= (BALANCE_VAR(kInfantryPortalCost) * 1.5f); } if (!Armoury.IsValid()) { if (pBot->Player->GetResources() < BALANCE_VAR(kArmoryCost)) { return true; } Vector BuildLocation = AITAC_GetRandomBuildHintInLocation(STRUCTURE_MARINE_ARMOURY, CommChair.Location, UTIL_MetresToGoldSrcUnits(15.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_ARMOURY, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } int NumAttempts = 0; while (NumAttempts < 5) { BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), CommChair.Location, UTIL_MetresToGoldSrcUnits(5.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_ARMOURY, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(ONOS_BASE_NAV_PROFILE), CommChair.Location, UTIL_MetresToGoldSrcUnits(10.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_ARMOURY, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } NumAttempts++; } return pBot->Player->GetResources() <= (BALANCE_VAR(kInfantryPortalCost) * 1.5f); } if (!TurretFactory.IsValid()) { if (pBot->Player->GetResources() < BALANCE_VAR(kTurretFactoryCost)) { return true; } Vector BuildLocation = AITAC_GetRandomBuildHintInLocation(STRUCTURE_MARINE_TURRETFACTORY, CommChair.Location, UTIL_MetresToGoldSrcUnits(15.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_TURRETFACTORY, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } int NumAttempts = 0; while (NumAttempts < 5) { BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), CommChair.Location, UTIL_MetresToGoldSrcUnits(10.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_TURRETFACTORY, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(ONOS_BASE_NAV_PROFILE), CommChair.Location, UTIL_MetresToGoldSrcUnits(10.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_TURRETFACTORY, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } NumAttempts++; } return pBot->Player->GetResources() <= (BALANCE_VAR(kTurretFactoryCost) * 1.5f); } if (!TurretFactory.IsCompleted()) { return false; } if (NumTurrets < 5) { if (pBot->Player->GetResources() < BALANCE_VAR(kSentryCost)) { return true; } int NumAttempts = 0; while (NumAttempts < 5) { Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), TurretFactory.Location, BALANCE_VAR(kTurretFactoryBuildDistance) * 0.8f); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_TURRET, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(ONOS_BASE_NAV_PROFILE), TurretFactory.Location, BALANCE_VAR(kTurretFactoryBuildDistance) * 0.8f); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_TURRET, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } NumAttempts++; } return pBot->Player->GetResources() <= (BALANCE_VAR(kSentryCost) * 1.5f); } if (!Armoury.IsCompleted()) { return false; } if (AICOMM_ShouldCommanderPrioritiseNodes(pBot) && pBot->Player->GetResources() < 30) { return false; } if (!ArmsLab.IsValid()) { if (pBot->Player->GetResources() < BALANCE_VAR(kArmsLabCost)) { return true; } Vector BuildLocation = AITAC_GetRandomBuildHintInLocation(STRUCTURE_MARINE_ARMSLAB, CommChair.Location, UTIL_MetresToGoldSrcUnits(15.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_ARMSLAB, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } int NumAttempts = 0; while (NumAttempts < 5) { BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), CommChair.Location, UTIL_MetresToGoldSrcUnits(10.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_ARMSLAB, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(ONOS_BASE_NAV_PROFILE), CommChair.Location, UTIL_MetresToGoldSrcUnits(10.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_ARMSLAB, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } NumAttempts++; } return pBot->Player->GetResources() <= (BALANCE_VAR(kArmsLabCost) * 1.5f); } if (!Observatory.IsValid()) { if (pBot->Player->GetResources() < BALANCE_VAR(kObservatoryCost)) { return true; } Vector BuildLocation = AITAC_GetRandomBuildHintInLocation(STRUCTURE_MARINE_OBSERVATORY, CommChair.Location, UTIL_MetresToGoldSrcUnits(15.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_OBSERVATORY, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } int NumAttempts = 0; while (NumAttempts < 5) { BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), CommChair.Location, UTIL_MetresToGoldSrcUnits(10.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_OBSERVATORY, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(ONOS_BASE_NAV_PROFILE), CommChair.Location, UTIL_MetresToGoldSrcUnits(10.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_OBSERVATORY, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } NumAttempts++; } return pBot->Player->GetResources() <= (BALANCE_VAR(kObservatoryCost) * 1.5f); } if (!AITAC_ResearchIsComplete(BotTeam, TECH_RESEARCH_PHASETECH)) { return false; } if (!PhaseGate.IsValid()) { if (pBot->Player->GetResources() < BALANCE_VAR(kPhaseGateCost)) { return true; } Vector BuildLocation = AITAC_GetRandomBuildHintInLocation(STRUCTURE_MARINE_PHASEGATE, CommChair.Location, UTIL_MetresToGoldSrcUnits(15.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_PHASEGATE, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } int NumAttempts = 0; while (NumAttempts < 5) { BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), CommChair.Location, UTIL_MetresToGoldSrcUnits(10.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_PHASEGATE, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(ONOS_BASE_NAV_PROFILE), CommChair.Location, UTIL_MetresToGoldSrcUnits(10.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_PHASEGATE, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } NumAttempts++; } return pBot->Player->GetResources() <= (BALANCE_VAR(kPhaseGateCost) * 1.5f); } if (Armoury.StructureType != STRUCTURE_MARINE_ADVARMOURY) { if (pBot->Player->GetResources() < BALANCE_VAR(kArmoryUpgradeCost)) { return true; } bool bSuccess = AICOMM_UpgradeStructure(pBot, &Armoury); if (bSuccess) { return true; } return pBot->Player->GetResources() <= (BALANCE_VAR(kArmoryUpgradeCost) * 1.5f); } if (!ProtoLab.IsValid()) { if (pBot->Player->GetResources() < BALANCE_VAR(kPrototypeLabCost)) { return true; } Vector BuildLocation = AITAC_GetRandomBuildHintInLocation(STRUCTURE_MARINE_PROTOTYPELAB, CommChair.Location, UTIL_MetresToGoldSrcUnits(15.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_PROTOTYPELAB, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } int NumAttempts = 0; while (NumAttempts < 5) { BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), Armoury.Location, UTIL_MetresToGoldSrcUnits(5.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_PROTOTYPELAB, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(ONOS_BASE_NAV_PROFILE), CommChair.Location, UTIL_MetresToGoldSrcUnits(10.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_PROTOTYPELAB, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } NumAttempts++; } return pBot->Player->GetResources() <= (BALANCE_VAR(kPrototypeLabCost) * 1.5f); } return false; } bool AICOMM_BuildOutOutpost(AvHAIPlayer* pBot, AvHAIMarineBase* BaseToBuildOut) { AvHAIDeployableStructureType StructureToDeploy = STRUCTURE_NONE; AvHAIBuildableStructure PhaseGate; AvHAIBuildableStructure Armoury; AvHAIBuildableStructure TurretFactory; AvHAIBuildableStructure Observatory; int NumTurrets = 0; int NumIncomplete = 0; for (auto it = BaseToBuildOut->PlacedStructures.begin(); it != BaseToBuildOut->PlacedStructures.end(); it++) { AvHAIBuildableStructure StructureRef = AITAC_GetDeployableStructureByEntIndex(BaseToBuildOut->BaseTeam, *it); if (!StructureRef.IsCompleted()) { NumIncomplete++; } switch (StructureRef.StructureType) { case STRUCTURE_MARINE_PHASEGATE: PhaseGate = StructureRef; break; case STRUCTURE_MARINE_TURRETFACTORY: case STRUCTURE_MARINE_ADVTURRETFACTORY: TurretFactory = StructureRef; break; case STRUCTURE_MARINE_ARMOURY: case STRUCTURE_MARINE_ADVARMOURY: Armoury = StructureRef; break; case STRUCTURE_MARINE_OBSERVATORY: Observatory = StructureRef; break; case STRUCTURE_MARINE_TURRET: NumTurrets++; break; default: break; } } if (NumIncomplete > 0) { int NumBuilders = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), BaseToBuildOut->BaseLocation, UTIL_MetresToGoldSrcUnits(10.0f), false, nullptr, AVH_USER3_COMMANDER_PLAYER); // Don't spam too many structures if (NumIncomplete >= NumBuilders) { return false; } } if (PhaseGate.IsValid() && !PhaseGate.IsCompleted()) { return false; } if (TurretFactory.IsValid() && !TurretFactory.IsCompleted()) { return false; } if (!PhaseGate.IsValid() && AITAC_ResearchIsComplete(pBot->Player->GetTeam(), TECH_RESEARCH_PHASETECH)) { StructureToDeploy = STRUCTURE_MARINE_PHASEGATE; } else if (!TurretFactory.IsValid()) { StructureToDeploy = STRUCTURE_MARINE_TURRETFACTORY; } else if (TurretFactory.IsCompleted() && NumTurrets < 5) { StructureToDeploy = STRUCTURE_MARINE_TURRET; } else if (!Armoury.IsValid()) { StructureToDeploy = STRUCTURE_MARINE_ARMOURY; } else if (!Observatory.IsValid()) { StructureToDeploy = STRUCTURE_MARINE_OBSERVATORY; } if (StructureToDeploy == STRUCTURE_NONE) { return false; } int ResourcesRequired = UTIL_GetCostOfStructureType(StructureToDeploy); if (pBot->Player->GetResources() < ResourcesRequired) { return true; } if (StructureToDeploy == STRUCTURE_MARINE_TURRET) { int NumAttempts = 0; while (NumAttempts < 5) { Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), TurretFactory.Location, (BALANCE_VAR(kTurretFactoryBuildDistance) * 0.8f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, StructureToDeploy, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), TurretFactory.Location, (BALANCE_VAR(kTurretFactoryBuildDistance) * 0.8f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, StructureToDeploy, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } NumAttempts++; } return pBot->Player->GetResources() <= 15; } Vector BuildLocation = AITAC_GetRandomBuildHintInLocation(StructureToDeploy, BaseToBuildOut->BaseLocation, UTIL_MetresToGoldSrcUnits(10.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, StructureToDeploy, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } int NumAttempts = 0; while (NumAttempts < 5) { BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), BaseToBuildOut->BaseLocation, UTIL_MetresToGoldSrcUnits(8.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, StructureToDeploy, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(ONOS_BASE_NAV_PROFILE), BaseToBuildOut->BaseLocation, UTIL_MetresToGoldSrcUnits(8.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, StructureToDeploy, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } NumAttempts++; } return pBot->Player->GetResources() <= 15; } bool AICOMM_BuildOutSiege(AvHAIPlayer* pBot, AvHAIMarineBase* BaseToBuildOut) { AvHAIDeployableStructureType StructureToDeploy = STRUCTURE_NONE; AvHAIBuildableStructure PhaseGate; AvHAIBuildableStructure Armoury; AvHAIBuildableStructure TurretFactory; AvHAIBuildableStructure Observatory; int NumTurrets = 0; int NumIncomplete = 0; for (auto it = BaseToBuildOut->PlacedStructures.begin(); it != BaseToBuildOut->PlacedStructures.end(); it++) { AvHAIBuildableStructure StructureRef = AITAC_GetDeployableStructureByEntIndex(BaseToBuildOut->BaseTeam, *it); if (!StructureRef.IsCompleted()) { NumIncomplete++; } switch (StructureRef.StructureType) { case STRUCTURE_MARINE_PHASEGATE: PhaseGate = StructureRef; break; case STRUCTURE_MARINE_TURRETFACTORY: case STRUCTURE_MARINE_ADVTURRETFACTORY: TurretFactory = StructureRef; break; case STRUCTURE_MARINE_ARMOURY: case STRUCTURE_MARINE_ADVARMOURY: Armoury = StructureRef; break; case STRUCTURE_MARINE_OBSERVATORY: Observatory = StructureRef; break; case STRUCTURE_MARINE_SIEGETURRET: NumTurrets++; break; default: break; } } if (NumIncomplete > 0) { // Don't spam too many structures if (NumIncomplete >= BaseToBuildOut->NumBuilders) { return false; } } // Don't place any more structures until we have the phase gate up if (PhaseGate.IsValid() && !PhaseGate.IsCompleted()) { return false; } if (!PhaseGate.IsValid() && AITAC_ResearchIsComplete(BaseToBuildOut->BaseTeam, TECH_RESEARCH_PHASETECH)) { StructureToDeploy = STRUCTURE_MARINE_PHASEGATE; } else if (!TurretFactory.IsValid()) { StructureToDeploy = STRUCTURE_MARINE_TURRETFACTORY; } else if (TurretFactory.IsCompleted() && TurretFactory.IsIdle() && TurretFactory.StructureType != STRUCTURE_MARINE_ADVTURRETFACTORY) { return AICOMM_UpgradeStructure(pBot, &TurretFactory); } else if (TurretFactory.StructureType == STRUCTURE_MARINE_ADVTURRETFACTORY && NumTurrets < 3) { StructureToDeploy = STRUCTURE_MARINE_SIEGETURRET; } else if (!Armoury.IsValid()) { StructureToDeploy = STRUCTURE_MARINE_ARMOURY; } else if (!Observatory.IsValid()) { StructureToDeploy = STRUCTURE_MARINE_OBSERVATORY; } if (StructureToDeploy == STRUCTURE_NONE) { return false; } int ResourcesRequired = UTIL_GetCostOfStructureType(StructureToDeploy); if (pBot->Player->GetResources() < ResourcesRequired) { return true; } if (StructureToDeploy == STRUCTURE_MARINE_SIEGETURRET) { Vector BuildLocation = AITAC_GetRandomBuildHintInLocation(StructureToDeploy, BaseToBuildOut->BaseLocation, UTIL_MetresToGoldSrcUnits(10.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, StructureToDeploy, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } int NumAttempts = 0; while (NumAttempts < 5) { Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), TurretFactory.Location, (BALANCE_VAR(kTurretFactoryBuildDistance) * 0.4f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, StructureToDeploy, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), TurretFactory.Location, (BALANCE_VAR(kTurretFactoryBuildDistance) * 0.6f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, StructureToDeploy, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } NumAttempts++; } return (pBot->Player->GetResources() <= BALANCE_VAR(kSiegeCost) + 5); } Vector BuildLocation = AITAC_GetRandomBuildHintInLocation(StructureToDeploy, BaseToBuildOut->BaseLocation, UTIL_MetresToGoldSrcUnits(10.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, StructureToDeploy, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } int NumAttempts = 0; while (NumAttempts < 5) { Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), BaseToBuildOut->BaseLocation, UTIL_MetresToGoldSrcUnits(3.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, StructureToDeploy, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), BaseToBuildOut->BaseLocation, UTIL_MetresToGoldSrcUnits(5.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, StructureToDeploy, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } NumAttempts++; } return (pBot->Player->GetResources() <= 15); } bool AICOMM_BuildOutGuardPost(AvHAIPlayer* pBot, AvHAIMarineBase* BaseToBuildOut) { AvHAIBuildableStructure TurretFactory; AvHAIBuildableStructure Observatory; int NumTurrets = 0; int NumIncomplete = 0; for (auto it = BaseToBuildOut->PlacedStructures.begin(); it != BaseToBuildOut->PlacedStructures.end(); it++) { AvHAIBuildableStructure StructureRef = AITAC_GetDeployableStructureByEntIndex(BaseToBuildOut->BaseTeam, *it); if (!StructureRef.IsCompleted()) { NumIncomplete++; } if (StructureRef.StructureType & (STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY)) { if (!TurretFactory.IsValid() || !TurretFactory.IsCompleted()) { TurretFactory = StructureRef; } } else if (StructureRef.StructureType == STRUCTURE_MARINE_TURRET) { NumTurrets++; } else if (StructureRef.StructureType == STRUCTURE_MARINE_OBSERVATORY) { Observatory = StructureRef; } } if (NumIncomplete > 0) { int NumBuilders = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), BaseToBuildOut->BaseLocation, UTIL_MetresToGoldSrcUnits(10.0f), false, nullptr, AVH_USER3_COMMANDER_PLAYER); // Don't spam too many structures if (NumIncomplete >= NumBuilders) { return false; } } AvHAIDeployableStructureType StructureToDeploy = STRUCTURE_NONE; if (!TurretFactory.IsValid()) { StructureToDeploy = STRUCTURE_MARINE_TURRETFACTORY; } else { if (!TurretFactory.IsCompleted()) { return false; } if (NumTurrets < 5) { StructureToDeploy = STRUCTURE_MARINE_TURRET; } else if (!Observatory.IsValid()) { StructureToDeploy = STRUCTURE_MARINE_OBSERVATORY; } } if (StructureToDeploy == STRUCTURE_NONE) { return false; } int ResourcesRequired = UTIL_GetCostOfStructureType(StructureToDeploy); if (pBot->Player->GetResources() < ResourcesRequired) { return true; } if (StructureToDeploy == STRUCTURE_MARINE_TURRET) { int NumAttempts = 0; while (NumAttempts < 5) { Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), TurretFactory.Location, (BALANCE_VAR(kTurretFactoryBuildDistance) * 0.8f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, StructureToDeploy, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), TurretFactory.Location, (BALANCE_VAR(kTurretFactoryBuildDistance) * 0.8f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, StructureToDeploy, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } NumAttempts++; } return pBot->Player->GetResources() <= 15; } Vector BuildLocation = AITAC_GetRandomBuildHintInLocation(StructureToDeploy, BaseToBuildOut->BaseLocation, UTIL_MetresToGoldSrcUnits(5.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, StructureToDeploy, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } int NumAttempts = 0; while (NumAttempts < 5) { BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), BaseToBuildOut->BaseLocation, UTIL_MetresToGoldSrcUnits(5.0f)); if (!vIsZero(BuildLocation)) { bool bSuccess = AICOMM_AddStructureToBase(pBot, StructureToDeploy, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } NumAttempts++; } return pBot->Player->GetResources() <= 15; } AvHAIMarineBase* AICOMM_AddNewBase(AvHAIPlayer* pBot, Vector NewBaseLocation, MarineBaseType NewBaseType) { AvHAIMarineBase NewBase; NewBase.BaseLocation = NewBaseLocation; NewBase.BaseType = NewBaseType; NewBase.BaseTeam = pBot->Player->GetTeam(); pBot->Bases.push_back(NewBase); return &pBot->Bases.back(); } bool AICOMM_AddStructureToBase(AvHAIPlayer* pBot, AvHAIDeployableStructureType StructureToDeploy, Vector BuildLocation, AvHAIMarineBase* BaseToAdd) { if (vIsZero(BuildLocation)) { return false; } AvHAIBuildableStructure* DeployedStructure = AICOMM_DeployStructure(pBot, StructureToDeploy, BuildLocation); if (DeployedStructure) { BaseToAdd->PlacedStructures.push_back(DeployedStructure->EntIndex); BaseToAdd->bBaseInitialised = true; return true; } return false; }