Marine Relocation

* The AI Commander can now relocate to a nearby empty hive at the start of a match, or if the current base is almost lost
This commit is contained in:
RGreenlees 2024-05-22 16:21:40 +01:00 committed by pierow
parent c2e41c2011
commit 91231ac069
12 changed files with 602 additions and 18 deletions

View file

@ -469,6 +469,32 @@ void AICOMM_UpdatePlayerOrders(AvHAIPlayer* pBot)
} }
int NumPlayersOnTeam = AITAC_GetNumActivePlayersOnTeam(pBot->Player->GetTeam()); int NumPlayersOnTeam = AITAC_GetNumActivePlayersOnTeam(pBot->Player->GetTeam());
if (AICOMM_ShouldCommanderRelocate(pBot))
{
Vector RelocationPoint = pBot->RelocationSpot;
int DesiredRelocationPlayers = imini(3, (int)ceilf((float)NumPlayersOnTeam * 0.5f));
const AvHAIHiveDefinition* RelocationHive = AITAC_GetHiveNearestLocation(RelocationPoint);
if (RelocationHive)
{
int NumAssignedPlayers = AICOMM_GetNumPlayersAssignedToOrder(pBot, RelocationHive->HiveEdict, ORDERPURPOSE_SECURE_HIVE);
if (NumAssignedPlayers < DesiredRelocationPlayers)
{
edict_t* NewAssignee = AICOMM_GetPlayerWithNoOrderNearestLocation(pBot, RelocationHive->FloorLocation);
if (!FNullEnt(NewAssignee))
{
AICOMM_AssignNewPlayerOrder(pBot, NewAssignee, RelocationHive->HiveEdict, ORDERPURPOSE_SECURE_HIVE);
return;
}
}
}
}
int DesiredPlayers = imini(2, (int)ceilf((float)NumPlayersOnTeam *0.5f)); int DesiredPlayers = imini(2, (int)ceilf((float)NumPlayersOnTeam *0.5f));
AvHTeamNumber BotTeam = pBot->Player->GetTeam(); AvHTeamNumber BotTeam = pBot->Player->GetTeam();
@ -498,8 +524,6 @@ void AICOMM_UpdatePlayerOrders(AvHAIPlayer* pBot)
if (AICOMM_ShouldCommanderPrioritiseNodes(pBot)) if (AICOMM_ShouldCommanderPrioritiseNodes(pBot))
{ {
DeployableSearchFilter ResNodeFilter; DeployableSearchFilter ResNodeFilter;
ResNodeFilter.ReachabilityTeam = pBot->Player->GetTeam(); ResNodeFilter.ReachabilityTeam = pBot->Player->GetTeam();
ResNodeFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; ResNodeFilter.ReachabilityFlags = AI_REACHABILITY_MARINE;
@ -2164,7 +2188,7 @@ bool AICOMM_PerformNextSecureHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefini
AvHAIBuildableStructure ExistingPG; AvHAIBuildableStructure ExistingPG;
AvHAIBuildableStructure ExistingTF; AvHAIBuildableStructure ExistingTF;
Vector OutpostLocation = (ExistingStructure.IsValid() && ExistingStructure.Purpose == STRUCTURE_PURPOSE_FORTIFY) ? ExistingStructure.Location : HiveToSecure->FloorLocation; Vector OutpostLocation = (ExistingStructure.IsValid() && (ExistingStructure.Purpose == STRUCTURE_PURPOSE_FORTIFY || ExistingStructure.Purpose == STRUCTURE_PURPOSE_BASE)) ? ExistingStructure.Location : HiveToSecure->FloorLocation;
if (HiveToSecure->HiveResNodeRef && HiveToSecure->HiveResNodeRef->OwningTeam == TEAM_IND) if (HiveToSecure->HiveResNodeRef && HiveToSecure->HiveResNodeRef->OwningTeam == TEAM_IND)
{ {
@ -2375,6 +2399,23 @@ bool AICOMM_BuildInfantryPortal(AvHAIPlayer* pBot, edict_t* CommChair)
bool AICOMM_CheckForNextRecycleAction(AvHAIPlayer* pBot) bool AICOMM_CheckForNextRecycleAction(AvHAIPlayer* pBot)
{ {
DeployableSearchFilter OldStuffFilter;
OldStuffFilter.DeployableTeam = pBot->Player->GetTeam();
OldStuffFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
OldStuffFilter.DeployableTypes = SEARCH_ALL_STRUCTURES;
OldStuffFilter.MinSearchRadius = UTIL_MetresToGoldSrcUnits(20.0f);
OldStuffFilter.PurposeFlags = STRUCTURE_PURPOSE_BASE;
Vector BaseLocation = (!vIsZero(pBot->RelocationSpot)) ? pBot->RelocationSpot : AITAC_GetTeamStartingLocation(pBot->Player->GetTeam());
AvHAIBuildableStructure OldBaseStructure = AITAC_FindFurthestDeployableFromLocation(BaseLocation, &OldStuffFilter);
if (OldBaseStructure.IsValid())
{
return AICOMM_RecycleStructure(pBot, &OldBaseStructure);
}
DeployableSearchFilter UnreachableFilter; DeployableSearchFilter UnreachableFilter;
UnreachableFilter.DeployableTypes = SEARCH_ALL_STRUCTURES; UnreachableFilter.DeployableTypes = SEARCH_ALL_STRUCTURES;
UnreachableFilter.DeployableTeam = pBot->Player->GetTeam(); UnreachableFilter.DeployableTeam = pBot->Player->GetTeam();
@ -3252,8 +3293,36 @@ void AICOMM_CommanderThink(AvHAIPlayer* pBot)
AICOMM_UpdatePlayerOrders(pBot); AICOMM_UpdatePlayerOrders(pBot);
if (AICOMM_CheckForNextRecycleAction(pBot)) { return; }
if (AICOMM_CheckForNextSupportAction(pBot)) { return; } if (AICOMM_CheckForNextSupportAction(pBot)) { return; }
if (AICOMM_ShouldCommanderRelocate(pBot))
{
if (vIsZero(pBot->RelocationSpot) || !AITAC_IsRelocationPointStillValid(pBot->Player->GetTeam(), pBot->RelocationSpot))
{
pBot->RelocationSpot = AITAC_FindNewTeamRelocationPoint(pBot->Player->GetTeam());
if (!vIsZero(pBot->RelocationSpot))
{
const AvHAIHiveDefinition* RelocationHive = AITAC_GetHiveNearestLocation(pBot->RelocationSpot);
if (RelocationHive)
{
char msg[128];
sprintf(msg, "We're relocating to %s, lads", RelocationHive->HiveName);
BotSay(pBot, true, 1.0f, msg);
}
}
}
if (!vIsZero(pBot->RelocationSpot))
{
if (AICOMM_CheckForNextRelocationAction(pBot)) { return; }
return;
}
}
if (AICOMM_CheckForNextRecycleAction(pBot)) { return; }
if (AICOMM_CheckForNextBuildAction(pBot)) { return; } if (AICOMM_CheckForNextBuildAction(pBot)) { return; }
if (AICOMM_CheckForNextResearchAction(pBot)) { return; } if (AICOMM_CheckForNextResearchAction(pBot)) { return; }
if (AICOMM_CheckForNextSupplyAction(pBot)) { return; } if (AICOMM_CheckForNextSupplyAction(pBot)) { return; }
@ -3343,6 +3412,9 @@ const AvHAIHiveDefinition* AICOMM_GetEmptyHiveOpportunityNearestLocation(AvHAIPl
const vector<AvHAIHiveDefinition*> Hives = AITAC_GetAllHives(); const vector<AvHAIHiveDefinition*> Hives = AITAC_GetAllHives();
Vector TeamStartLocation = AITAC_GetTeamStartingLocation(CommanderTeam);
Vector RelocationPoint = CommanderBot->RelocationSpot;
for (auto it = Hives.begin(); it != Hives.end(); it++) for (auto it = Hives.begin(); it != Hives.end(); it++)
{ {
const AvHAIHiveDefinition* Hive = (*it); const AvHAIHiveDefinition* Hive = (*it);
@ -3351,6 +3423,16 @@ const AvHAIHiveDefinition* AICOMM_GetEmptyHiveOpportunityNearestLocation(AvHAIPl
if (AICOMM_IsHiveFullySecured(CommanderBot, Hive, false)) { 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; Vector SecureLocation = Hive->FloorLocation;
DeployableSearchFilter StructureFilter; DeployableSearchFilter StructureFilter;
@ -3364,7 +3446,7 @@ const AvHAIHiveDefinition* AICOMM_GetEmptyHiveOpportunityNearestLocation(AvHAIPl
AvHAIBuildableStructure ExistingStructure = AITAC_FindClosestDeployableToLocation(Hive->FloorLocation, &StructureFilter); AvHAIBuildableStructure ExistingStructure = AITAC_FindClosestDeployableToLocation(Hive->FloorLocation, &StructureFilter);
if (ExistingStructure.IsValid() && ExistingStructure.Purpose == STRUCTURE_PURPOSE_FORTIFY) if (ExistingStructure.IsValid() && (ExistingStructure.Purpose == STRUCTURE_PURPOSE_FORTIFY || ExistingStructure.Purpose == STRUCTURE_PURPOSE_BASE))
{ {
SecureLocation = ExistingStructure.Location; SecureLocation = ExistingStructure.Location;
} }
@ -3615,4 +3697,230 @@ void AICOMM_ReceiveChatRequest(AvHAIPlayer* Commander, edict_t* Requestor, const
{ {
Commander->ActiveRequests.push_back(NewRequest); Commander->ActiveRequests.push_back(NewRequest);
} }
}
bool AICOMM_ShouldCommanderRelocate(AvHAIPlayer* pBot)
{
AvHTeamNumber Team = pBot->Player->GetTeam();
AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(Team);
edict_t* CurrentCommChair = AITAC_GetCommChair(Team);
if (FNullEnt(CurrentCommChair)) { return false; }
Vector CurrentTeamStartLocation = CurrentCommChair->v.origin;
DeployableSearchFilter BaseStructureFilter;
BaseStructureFilter.DeployableTeam = Team;
BaseStructureFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
BaseStructureFilter.DeployableTypes = (STRUCTURE_MARINE_OBSERVATORY | STRUCTURE_MARINE_ARMSLAB | STRUCTURE_MARINE_ADVARMOURY | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY);
BaseStructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
BaseStructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f);
vector<AvHAIBuildableStructure> AllBaseStructures = AITAC_FindAllDeployables(CurrentTeamStartLocation, &BaseStructureFilter);
bool bBaseWellEstablished = false;
bool bObservatoryExists = false;
for (auto it = AllBaseStructures.begin(); it != AllBaseStructures.end(); it++)
{
if (it->StructureType == STRUCTURE_MARINE_OBSERVATORY)
{
bObservatoryExists = true;
}
bBaseWellEstablished = true;
}
// If we can beacon then don't relocate
if (bBaseWellEstablished && bObservatoryExists) { return false; }
// If our base is well established, then relocate if the base is overrun and lost
if (bBaseWellEstablished)
{
DeployableSearchFilter BaseStructureFilter;
BaseStructureFilter.DeployableTypes = (STRUCTURE_MARINE_INFANTRYPORTAL | STRUCTURE_MARINE_COMMCHAIR);
BaseStructureFilter.DeployableTeam = Team;
BaseStructureFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
BaseStructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
vector<AvHAIBuildableStructure> BaseStructures = AITAC_FindAllDeployables(CurrentTeamStartLocation, &BaseStructureFilter);
bool bHasInfantryPortals = false;
bool bBaseUnderAttack = false;
for (auto it = BaseStructures.begin(); it != BaseStructures.end(); it++)
{
AvHAIBuildableStructure ThisStructure = (*it);
if (ThisStructure.StructureStatusFlags & STRUCTURE_STATUS_UNDERATTACK)
{
bBaseUnderAttack = true;
}
if (ThisStructure.StructureType == STRUCTURE_MARINE_INFANTRYPORTAL)
{
bHasInfantryPortals = true;
}
}
if (!bBaseUnderAttack && bHasInfantryPortals) { return false; }
int EnemyStrength = 0;
int DefenderStrength = AITAC_GetNumPlayersOfTeamInArea(Team, CurrentTeamStartLocation, UTIL_MetresToGoldSrcUnits(10.0f), false, nullptr, AVH_USER3_COMMANDER_PLAYER);
if (AIMGR_GetTeamType(EnemyTeam) == AVH_CLASS_TYPE_MARINE)
{
EnemyStrength = AITAC_GetNumPlayersOfTeamInArea(EnemyTeam, CurrentTeamStartLocation, UTIL_MetresToGoldSrcUnits(10.0f), false, nullptr, AVH_USER3_COMMANDER_PLAYER);
}
else
{
int NumSkulks = AITAC_GetNumPlayersOfTeamAndClassInArea(EnemyTeam, CurrentTeamStartLocation, UTIL_MetresToGoldSrcUnits(10.0f), false, nullptr, AVH_USER3_ALIEN_PLAYER1);
int NumFades = AITAC_GetNumPlayersOfTeamAndClassInArea(EnemyTeam, CurrentTeamStartLocation, UTIL_MetresToGoldSrcUnits(10.0f), false, nullptr, AVH_USER3_ALIEN_PLAYER4);
int NumOnos = AITAC_GetNumPlayersOfTeamAndClassInArea(EnemyTeam, CurrentTeamStartLocation, UTIL_MetresToGoldSrcUnits(10.0f), false, nullptr, AVH_USER3_ALIEN_PLAYER5);
EnemyStrength = NumSkulks + (NumFades * 2) + (NumOnos * 2);
}
if (EnemyStrength >= 3 && EnemyStrength >= DefenderStrength * 3)
{
return true;
}
return false;
}
// Don't relocate if we're already located in a hive
const AvHAIHiveDefinition* NearestHive = AITAC_GetHiveNearestLocation(CurrentTeamStartLocation);
if (NearestHive && NearestHive->Status == HIVE_STATUS_UNBUILT && vDist2DSq(NearestHive->FloorLocation, CurrentTeamStartLocation) < sqrf(UTIL_MetresToGoldSrcUnits(10.0f)))
{
return false;
}
if (!vIsZero(pBot->RelocationSpot))
{
if (AITAC_IsRelocationCompleted(Team, pBot->RelocationSpot))
{
return false;
}
DeployableSearchFilter InfPortalFilter;
InfPortalFilter.DeployableTeam = Team;
InfPortalFilter.DeployableTypes = STRUCTURE_MARINE_INFANTRYPORTAL;
InfPortalFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
InfPortalFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
InfPortalFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f);
if (AITAC_DeployableExistsAtLocation(pBot->RelocationSpot, &InfPortalFilter))
{
return true;
}
// If the match has been going on for a minute and we haven't made any progress in relocation then give up, or we risk losing everything
if (AIMGR_GetMatchLength() > 60.0f)
{
return AITAC_GetNumPlayersOfTeamInArea(Team, pBot->RelocationSpot, UTIL_MetresToGoldSrcUnits(10.0f), false, nullptr, AVH_USER3_COMMANDER_PLAYER) > 0;
}
}
return true;
}
bool AICOMM_CheckForNextRelocationAction(AvHAIPlayer* pBot)
{
AvHTeamNumber BotTeam = pBot->Player->GetTeam();
AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam);
Vector RelocationPoint = pBot->RelocationSpot;
if (vIsZero(RelocationPoint)) { return false; }
edict_t* CurrentCommChair = AITAC_GetCommChair(BotTeam);
if (FNullEnt(CurrentCommChair)) { return false; }
DeployableSearchFilter OrigInfPortalFilter;
OrigInfPortalFilter.DeployableTeam = BotTeam;
OrigInfPortalFilter.DeployableTypes = STRUCTURE_MARINE_INFANTRYPORTAL;
OrigInfPortalFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f);
// First ensure we have one infantry portal in our starting location, in case it goes horribly wrong
if (!AITAC_DeployableExistsAtLocation(CurrentCommChair->v.origin, &OrigInfPortalFilter))
{
return AICOMM_BuildInfantryPortal(pBot, CurrentCommChair);
}
// Don't do anything more if we don't have anyone at the relocation point yet, but we can drop RTs if needed
if (AITAC_GetNumPlayersOfTeamInArea(BotTeam, RelocationPoint, UTIL_MetresToGoldSrcUnits(10.0f), false, nullptr, AVH_USER3_COMMANDER_PLAYER) == 0)
{
const AvHAIResourceNode* CappableNode = AICOMM_GetNearestResourceNodeCapOpportunity(BotTeam, CurrentCommChair->v.origin);
if (CappableNode)
{
bool bSuccess = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_RESTOWER, CappableNode->Location);
if (bSuccess || pBot->Player->GetResources() <= BALANCE_VAR(kResourceTowerCost) + 10) { return true; }
}
return false;
}
DeployableSearchFilter NewBaseFilter;
NewBaseFilter.DeployableTeam = BotTeam;
NewBaseFilter.DeployableTypes = STRUCTURE_MARINE_COMMCHAIR;
NewBaseFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
NewBaseFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f);
AvHAIBuildableStructure RelocationCommChair = AITAC_FindClosestDeployableToLocation(RelocationPoint, &NewBaseFilter);
if (!RelocationCommChair.IsValid())
{
Vector BuildPoint = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), RelocationPoint, UTIL_MetresToGoldSrcUnits(2.0f));
if (vIsZero(BuildPoint))
{
BuildPoint = UTIL_ProjectPointToNavmesh(RelocationPoint, Vector(500.0f, 500.0f, 500.0f), GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE));
}
if (!vIsZero(BuildPoint))
{
return AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_COMMCHAIR, BuildPoint, STRUCTURE_PURPOSE_BASE);
}
return false;
}
if (!(RelocationCommChair.StructureStatusFlags & STRUCTURE_STATUS_COMPLETED)) { return false; }
NewBaseFilter.DeployableTypes = STRUCTURE_MARINE_INFANTRYPORTAL;
NewBaseFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(8.0f);
NewBaseFilter.IncludeStatusFlags = STRUCTURE_STATUS_NONE;
int NumInfPortals = AITAC_GetNumDeployablesNearLocation(RelocationCommChair.Location, &NewBaseFilter);
if (NumInfPortals < 2)
{
return AICOMM_BuildInfantryPortal(pBot, RelocationCommChair.edict);
}
DeployableSearchFilter OldStuffFilter;
OldStuffFilter.DeployableTeam = BotTeam;
OldStuffFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
OldStuffFilter.DeployableTypes = SEARCH_ALL_STRUCTURES;
OldStuffFilter.MinSearchRadius = UTIL_MetresToGoldSrcUnits(20.0f);
OldStuffFilter.PurposeFlags = STRUCTURE_PURPOSE_BASE;
vector<AvHAIBuildableStructure> 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;
} }

View file

@ -41,6 +41,7 @@ bool AICOMM_CheckForNextSupportAction(AvHAIPlayer* pBot);
bool AICOMM_CheckForNextRecycleAction(AvHAIPlayer* pBot); bool AICOMM_CheckForNextRecycleAction(AvHAIPlayer* pBot);
bool AICOMM_CheckForNextResearchAction(AvHAIPlayer* pBot); bool AICOMM_CheckForNextResearchAction(AvHAIPlayer* pBot);
bool AICOMM_CheckForNextSupplyAction(AvHAIPlayer* pBot); bool AICOMM_CheckForNextSupplyAction(AvHAIPlayer* pBot);
bool AICOMM_CheckForNextRelocationAction(AvHAIPlayer* pBot);
void AICOMM_SetDropHealthAction(AvHAIPlayer* pBot, commander_action* Action, edict_t* Recipient); void AICOMM_SetDropHealthAction(AvHAIPlayer* pBot, commander_action* Action, edict_t* Recipient);
void AICOMM_SetDropAmmoAction(AvHAIPlayer* pBot, commander_action* Action, edict_t* Recipient); void AICOMM_SetDropAmmoAction(AvHAIPlayer* pBot, commander_action* Action, edict_t* Recipient);
@ -71,4 +72,6 @@ bool AICOMM_ShouldBeacon(AvHAIPlayer* pBot);
void AICOMM_ReceiveChatRequest(AvHAIPlayer* Commander, edict_t* Requestor, const char* Request); void AICOMM_ReceiveChatRequest(AvHAIPlayer* Commander, edict_t* Requestor, const char* Request);
bool AICOMM_ShouldCommanderRelocate(AvHAIPlayer* pBot);
#endif // AVH_AI_COMMANDER_H #endif // AVH_AI_COMMANDER_H

View file

@ -12,6 +12,8 @@ BotFillTiming CurrentBotFillTiming = FILLTIMING_ALLHUMANS;
float MaxAIMatchTimeMinutes = 90.0f; float MaxAIMatchTimeMinutes = 90.0f;
bool bRelocationAllowed = false;
std::unordered_map<std::string, TeamSizeDefinitions> TeamSizeMap; std::unordered_map<std::string, TeamSizeDefinitions> TeamSizeMap;
bot_skill BotSkillLevels[4]; bot_skill BotSkillLevels[4];
@ -90,6 +92,11 @@ bool CONFIG_IsOnosAllowed()
return avh_botallowonos.value > 0; return avh_botallowonos.value > 0;
} }
bool CONFIG_IsRelocationAllowed()
{
return bRelocationAllowed;
}
float CONFIG_GetMaxStuckTime() float CONFIG_GetMaxStuckTime()
{ {
return avh_botmaxstucktime.value; return avh_botmaxstucktime.value;
@ -328,6 +335,14 @@ void CONFIG_ParseConfigFile()
continue; continue;
} }
if (!stricmp(keyChar, "AllowRelocation"))
{
const char* ValueChar = value.c_str();
bRelocationAllowed = (!stricmp(ValueChar, "True") || !stricmp(ValueChar, "T"));
continue;
}
if (!stricmp(keyChar, "MaxAIMatchTime")) if (!stricmp(keyChar, "MaxAIMatchTime"))
{ {
float MaxMinutes = std::stof(value.c_str()); float MaxMinutes = std::stof(value.c_str());

View file

@ -43,6 +43,8 @@ bool CONFIG_IsLerkAllowed();
bool CONFIG_IsFadeAllowed(); bool CONFIG_IsFadeAllowed();
bool CONFIG_IsOnosAllowed(); bool CONFIG_IsOnosAllowed();
bool CONFIG_IsRelocationAllowed();
// Returns the max time a bot is allowed to be stuck before suiciding (0 means forever) // Returns the max time a bot is allowed to be stuck before suiciding (0 means forever)
float CONFIG_GetMaxStuckTime(); float CONFIG_GetMaxStuckTime();

View file

@ -814,6 +814,8 @@ typedef struct AVH_AI_PLAYER
int DebugValue = 0; // Used for debugging the bot int DebugValue = 0; // Used for debugging the bot
Vector RelocationSpot = ZERO_VECTOR; // If the bot is commanding and wants to relocate, then this is where they plan to go
} AvHAIPlayer; } AvHAIPlayer;
typedef struct _AVH_AI_SQUAD typedef struct _AVH_AI_SQUAD

View file

@ -5010,7 +5010,6 @@ void WallClimbMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndP
} }
BotMoveLookAt(pBot, LookLocation, true); BotMoveLookAt(pBot, LookLocation, true);
//BotDirectLookAt(pBot, LookLocation);
} }

View file

@ -1926,6 +1926,11 @@ void SetNewAIPlayerRole(AvHAIPlayer* pBot, AvHAIBotRole NewRole)
AITASK_ClearBotTask(pBot, &pBot->SecondaryBotTask); AITASK_ClearBotTask(pBot, &pBot->SecondaryBotTask);
pBot->BotRole = NewRole; pBot->BotRole = NewRole;
if (NewRole != BOT_ROLE_COMMAND && IsPlayerCommander(pBot->Edict))
{
BotStopCommanderMode(pBot);
}
} }
} }
@ -2072,10 +2077,37 @@ bool ShouldAIPlayerTakeCommand(AvHAIPlayer* pBot)
AvHPlayer* CurrentCommander = BotTeam->GetCommanderPlayer(); AvHPlayer* CurrentCommander = BotTeam->GetCommanderPlayer();
// Don't go commander if we already have one, and it's not us
if (CurrentCommander) if (CurrentCommander)
{ {
return CurrentCommander == pBot->Player; // Don't go commander if we already have one, and it's not us
if (CurrentCommander != pBot->Player) { return false; }
if (!AICOMM_ShouldCommanderRelocate(pBot))
{
// If it is us, then check if we're relocating. If we are, and we're in the old chair, then we should stop commanding
Vector TeamRelocationPoint = pBot->RelocationSpot;
if (vIsZero(TeamRelocationPoint)) { return true; }
if (AITAC_IsRelocationCompleted(BotTeamNumber, TeamRelocationPoint))
{
DeployableSearchFilter CommChairFilter;
CommChairFilter.DeployableTeam = BotTeamNumber;
CommChairFilter.DeployableTypes = STRUCTURE_MARINE_COMMCHAIR;
CommChairFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
CommChairFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f);
AvHAIBuildableStructure RelocationCommChair = AITAC_FindClosestDeployableToLocation(pBot->RelocationSpot, &CommChairFilter);
// Only command if we're in the relocation chair, otherwise don't
return !RelocationCommChair.IsValid() || RelocationCommChair.edict == AITAC_GetCommChair(BotTeamNumber);
}
else
{
return true;
}
}
} }
// Don't go commander if there is another bot already taking command // Don't go commander if there is another bot already taking command
@ -6793,6 +6825,8 @@ void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task
vector<AvHAIBuildableStructure> EnemyStructures = AITAC_FindAllDeployables(ThisHive->FloorLocation, &EnemyStuffFilter); vector<AvHAIBuildableStructure> EnemyStructures = AITAC_FindAllDeployables(ThisHive->FloorLocation, &EnemyStuffFilter);
bool bIsRelocationHive = false; // If true, the marines have relocated here so don't try to retake it: requires a base attack task instead
// Enemy hasn't built anything here, so doesn't need clearing // Enemy hasn't built anything here, so doesn't need clearing
if (ThisHive->OwningTeam != EnemyTeam && EnemyStructures.size() == 0) { continue; } if (ThisHive->OwningTeam != EnemyTeam && EnemyStructures.size() == 0) { continue; }
@ -6802,6 +6836,9 @@ void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task
{ {
switch (StructureIt->StructureType) switch (StructureIt->StructureType)
{ {
case STRUCTURE_MARINE_INFANTRYPORTAL:
bIsRelocationHive = true;
break;
case STRUCTURE_MARINE_PHASEGATE: case STRUCTURE_MARINE_PHASEGATE:
ThisStrength += 2; ThisStrength += 2;
break; break;
@ -6819,7 +6856,7 @@ void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task
} }
} }
if (!HiveToSecure || ThisStrength < MaxHiveStrength) if (!bIsRelocationHive && (!HiveToSecure || ThisStrength < MaxHiveStrength))
{ {
HiveToSecure = ThisHive; HiveToSecure = ThisHive;
MaxHiveStrength = ThisStrength; MaxHiveStrength = ThisStrength;
@ -6853,8 +6890,7 @@ void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task
return; return;
} }
} }
} }
// FIND ANY LAST ENEMIES TO KILL AND END GAME // FIND ANY LAST ENEMIES TO KILL AND END GAME
@ -7415,19 +7451,19 @@ void AIPlayerSetWantsAndNeedsAlienTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
{ {
if (!PlayerHasAlienUpgradeOfType(pBot->Edict, HIVE_TECH_DEFENCE) && AITAC_IsAlienUpgradeAvailableForTeam(pBot->Player->GetTeam(), HIVE_TECH_DEFENCE)) if (!PlayerHasAlienUpgradeOfType(pBot->Edict, HIVE_TECH_DEFENCE) && AITAC_IsAlienUpgradeAvailableForTeam(pBot->Player->GetTeam(), HIVE_TECH_DEFENCE))
{ {
AITASK_SetEvolveTask(pBot, Task, pBot->CurrentFloorPosition, AlienGetDesiredUpgrade(pBot, HIVE_TECH_DEFENCE), true); pBot->Edict->v.impulse = AlienGetDesiredUpgrade(pBot, HIVE_TECH_DEFENCE);
return; return;
} }
if (!PlayerHasAlienUpgradeOfType(pBot->Edict, HIVE_TECH_MOVEMENT) && AITAC_IsAlienUpgradeAvailableForTeam(pBot->Player->GetTeam(), HIVE_TECH_MOVEMENT)) if (!PlayerHasAlienUpgradeOfType(pBot->Edict, HIVE_TECH_MOVEMENT) && AITAC_IsAlienUpgradeAvailableForTeam(pBot->Player->GetTeam(), HIVE_TECH_MOVEMENT))
{ {
AITASK_SetEvolveTask(pBot, Task, pBot->CurrentFloorPosition, AlienGetDesiredUpgrade(pBot, HIVE_TECH_MOVEMENT), true); pBot->Edict->v.impulse = AlienGetDesiredUpgrade(pBot, HIVE_TECH_MOVEMENT);
return; return;
} }
if (!PlayerHasAlienUpgradeOfType(pBot->Edict, HIVE_TECH_SENSORY) && AITAC_IsAlienUpgradeAvailableForTeam(pBot->Player->GetTeam(), HIVE_TECH_SENSORY)) if (!PlayerHasAlienUpgradeOfType(pBot->Edict, HIVE_TECH_SENSORY) && AITAC_IsAlienUpgradeAvailableForTeam(pBot->Player->GetTeam(), HIVE_TECH_SENSORY))
{ {
AITASK_SetEvolveTask(pBot, Task, pBot->CurrentFloorPosition, AlienGetDesiredUpgrade(pBot, HIVE_TECH_SENSORY), true); pBot->Edict->v.impulse = AlienGetDesiredUpgrade(pBot, HIVE_TECH_SENSORY);
return; return;
} }
} }

View file

@ -1590,4 +1590,9 @@ void AIMGR_SetFrameDelta(float NewValue)
float AIMGR_GetFrameDelta() float AIMGR_GetFrameDelta()
{ {
return CurrentFrameDelta; return CurrentFrameDelta;
}
float AIMGR_GetMatchLength()
{
return (gpGlobals->time - GetGameRules()->GetTimeGameStarted());
} }

View file

@ -97,6 +97,8 @@ AvHClassType AIMGR_GetTeamType(const AvHTeamNumber Team);
AvHTeamNumber AIMGR_GetTeamANumber(); AvHTeamNumber AIMGR_GetTeamANumber();
AvHTeamNumber AIMGR_GetTeamBNumber(); AvHTeamNumber AIMGR_GetTeamBNumber();
float AIMGR_GetMatchLength();
AvHTeam* AIMGR_GetTeamRef(const AvHTeamNumber Team); AvHTeam* AIMGR_GetTeamRef(const AvHTeamNumber Team);
// Returns all NS AI players. Does not include third-party bots // Returns all NS AI players. Does not include third-party bots

View file

@ -18,6 +18,7 @@
#include "AvHAIConstants.h" #include "AvHAIConstants.h"
#include "AvHAIPlayerManager.h" #include "AvHAIPlayerManager.h"
#include "AvHAIConfig.h" #include "AvHAIConfig.h"
#include "AvHAICommander.h"
#include "AvHGamerules.h" #include "AvHGamerules.h"
#include "AvHServerUtil.h" #include "AvHServerUtil.h"
@ -54,6 +55,9 @@ unsigned int ItemRefreshFrame = 0;
Vector TeamAStartingLocation = ZERO_VECTOR; Vector TeamAStartingLocation = ZERO_VECTOR;
Vector TeamBStartingLocation = ZERO_VECTOR; Vector TeamBStartingLocation = ZERO_VECTOR;
Vector TeamARelocationPoint = ZERO_VECTOR;
Vector TeamBRelocationPoint = ZERO_VECTOR;
extern nav_mesh NavMeshes[MAX_NAV_MESHES]; // Array of nav meshes. Currently only 3 are used (building, onos, and regular) extern nav_mesh NavMeshes[MAX_NAV_MESHES]; // Array of nav meshes. Currently only 3 are used (building, onos, and regular)
extern nav_profile BaseNavProfiles[MAX_NAV_PROFILES]; // Array of nav profiles extern nav_profile BaseNavProfiles[MAX_NAV_PROFILES]; // Array of nav profiles
@ -1184,6 +1188,27 @@ void AITAC_RefreshHiveData()
} }
} }
Vector AITAC_GetTeamRelocationPoint(AvHTeamNumber Team)
{
Vector CurrentRelocationPoint = (Team == GetGameRules()->GetTeamANumber()) ? TeamARelocationPoint : TeamBRelocationPoint;
if (!AITAC_IsRelocationPointStillValid(Team, CurrentRelocationPoint))
{
CurrentRelocationPoint = AITAC_FindNewTeamRelocationPoint(Team);
}
if (Team == GetGameRules()->GetTeamANumber())
{
TeamARelocationPoint = CurrentRelocationPoint;
}
else
{
TeamBRelocationPoint = CurrentRelocationPoint;
}
return (Team == GetGameRules()->GetTeamANumber()) ? TeamARelocationPoint : TeamBRelocationPoint;
}
Vector AITAC_GetTeamStartingLocation(AvHTeamNumber Team) Vector AITAC_GetTeamStartingLocation(AvHTeamNumber Team)
{ {
if (vIsZero(TeamAStartingLocation) || vIsZero(TeamBStartingLocation)) if (vIsZero(TeamAStartingLocation) || vIsZero(TeamBStartingLocation))
@ -4379,7 +4404,6 @@ edict_t* AITAC_GetCommChair(AvHTeamNumber Team)
ChairFilter.DeployableTypes = STRUCTURE_MARINE_COMMCHAIR; ChairFilter.DeployableTypes = STRUCTURE_MARINE_COMMCHAIR;
ChairFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; ChairFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
ChairFilter.DeployableTeam = Team; ChairFilter.DeployableTeam = Team;
ChairFilter.ReachabilityTeam = TEAM_IND;
vector<AvHAIBuildableStructure> CommChairs = AITAC_FindAllDeployables(ZERO_VECTOR, &ChairFilter); vector<AvHAIBuildableStructure> CommChairs = AITAC_FindAllDeployables(ZERO_VECTOR, &ChairFilter);
@ -4391,8 +4415,10 @@ edict_t* AITAC_GetCommChair(AvHTeamNumber Team)
{ {
AvHCommandStation* ChairRef = dynamic_cast<AvHCommandStation*>((*it).EntityRef); AvHCommandStation* ChairRef = dynamic_cast<AvHCommandStation*>((*it).EntityRef);
if (!ChairRef) { continue; }
// Idle animation will be 3 if the chair is in use (closed animation). See AvHCommandStation::GetIdleAnimation // Idle animation will be 3 if the chair is in use (closed animation). See AvHCommandStation::GetIdleAnimation
if (ChairRef && ChairRef->GetIdleAnimation() == 3) if (ChairRef->GetIdleAnimation() == 3)
{ {
MainCommChair = ChairRef->edict(); MainCommChair = ChairRef->edict();
} }
@ -5750,4 +5776,157 @@ Vector AITAC_GetGatherLocationForSquad(AvHAISquad* Squad)
} }
return ZERO_VECTOR; return ZERO_VECTOR;
}
Vector AITAC_FindNewTeamRelocationPoint(AvHTeamNumber Team)
{
if (!CONFIG_IsRelocationAllowed()) { return ZERO_VECTOR; }
AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(Team);
// Only relocate if:
// There is a hive to relocate to with a marine ready to build
// The current base is overrun and lost
// Or we decide we want to, and the current base isn't too built up
Vector CurrentTeamStartLocation = AITAC_GetTeamStartingLocation(Team);
const AvHAIHiveDefinition* RelocationHive = nullptr;
float MinDist = 0.0f;
vector<AvHAIHiveDefinition*> AllHives = AITAC_GetAllHives();
for (auto it = AllHives.begin(); it != AllHives.end(); it++)
{
const AvHAIHiveDefinition* ThisHive = (*it);
// Obviously don't relocate to an active enemy hive...
if (ThisHive->Status != HIVE_STATUS_UNBUILT) { continue; }
// Don't relocate if we're already located close to this hive
if (vDist2DSq(CurrentTeamStartLocation, ThisHive->FloorLocation) < sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) { continue; }
// Don't relocate if the enemy has a foothold here
DeployableSearchFilter EnemyStuff;
EnemyStuff.DeployableTeam = EnemyTeam;
EnemyStuff.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
EnemyStuff.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
EnemyStuff.DeployableTypes = (STRUCTURE_MARINE_PHASEGATE | STRUCTURE_MARINE_COMMCHAIR | STRUCTURE_MARINE_INFANTRYPORTAL | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY | STRUCTURE_ALIEN_OFFENCECHAMBER);
EnemyStuff.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f);
if (AITAC_DeployableExistsAtLocation(ThisHive->FloorLocation, &EnemyStuff)) { continue; }
const AvHAIHiveDefinition* NearestEnemyHive = AITAC_GetActiveHiveNearestLocation(EnemyTeam, ThisHive->FloorLocation);
float ThisDist = 0.0f;
// Either pick an empty hive furthest from the nearest enemy hive (if they have one)
// Or the closest one to us if the enemy don't (e.g. it's MvM)
if (NearestEnemyHive)
{
ThisDist = vDist2DSq(NearestEnemyHive->FloorLocation, ThisHive->FloorLocation);
if (!RelocationHive || ThisDist > MinDist)
{
RelocationHive = ThisHive;
MinDist = ThisDist;
}
}
else
{
ThisDist = vDist2DSq(CurrentTeamStartLocation, ThisHive->FloorLocation);
if (!RelocationHive || ThisDist < MinDist)
{
RelocationHive = ThisHive;
MinDist = ThisDist;
}
}
}
// No hives to relocate to
if (!RelocationHive) { return ZERO_VECTOR; }
return RelocationHive->FloorLocation;
}
bool AITAC_IsRelocationPointStillValid(AvHTeamNumber RelocationTeam, Vector RelocationPoint)
{
if (vIsZero(RelocationPoint)) { return false; }
const AvHAIHiveDefinition* ThisHive = AITAC_GetHiveNearestLocation(RelocationPoint);
// Obviously don't relocate to an active enemy hive...
if (ThisHive->Status != HIVE_STATUS_UNBUILT) { return false; }
AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(RelocationTeam);
// Don't relocate if the enemy has a foothold here
DeployableSearchFilter EnemyStuff;
EnemyStuff.DeployableTeam = EnemyTeam;
EnemyStuff.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
EnemyStuff.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
EnemyStuff.DeployableTypes = (STRUCTURE_MARINE_PHASEGATE | STRUCTURE_MARINE_COMMCHAIR | STRUCTURE_MARINE_INFANTRYPORTAL | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY | STRUCTURE_ALIEN_OFFENCECHAMBER);
EnemyStuff.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f);
if (AITAC_DeployableExistsAtLocation(ThisHive->FloorLocation, &EnemyStuff)) { return false; }
return true;
}
bool AITAC_IsRelocationCompleted(AvHTeamNumber RelocationTeam, Vector RelocationPoint)
{
if (vIsZero(RelocationPoint)) { return true; }
// Don't relocate if the enemy has a foothold here
DeployableSearchFilter BaseStuffFilter;
BaseStuffFilter.DeployableTeam = RelocationTeam;
BaseStuffFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
BaseStuffFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
BaseStuffFilter.DeployableTypes = (STRUCTURE_MARINE_COMMCHAIR | STRUCTURE_MARINE_INFANTRYPORTAL);
BaseStuffFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f);
edict_t* RelocationChair = nullptr;
edict_t* CurrentCommChair = AITAC_GetCommChair(RelocationTeam);
int NumInfPortals = 0;
vector<AvHAIBuildableStructure> RelocationStructures = AITAC_FindAllDeployables(RelocationPoint, &BaseStuffFilter);
for (auto it = RelocationStructures.begin(); it != RelocationStructures.end(); it++)
{
if (it->StructureType == STRUCTURE_MARINE_COMMCHAIR)
{
RelocationChair = it->edict;
}
if (it->StructureType == STRUCTURE_MARINE_INFANTRYPORTAL)
{
NumInfPortals++;
}
}
if (FNullEnt(RelocationChair) || NumInfPortals < 2) { return false; }
DeployableSearchFilter OldStuffFilter;
OldStuffFilter.DeployableTeam = RelocationTeam;
OldStuffFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
OldStuffFilter.DeployableTypes = SEARCH_ALL_STRUCTURES;
OldStuffFilter.MinSearchRadius = UTIL_MetresToGoldSrcUnits(20.0f);
OldStuffFilter.PurposeFlags = STRUCTURE_PURPOSE_BASE;
vector<AvHAIBuildableStructure> AllOldStructures = AITAC_FindAllDeployables(RelocationPoint, &OldStuffFilter);
for (auto it = AllOldStructures.begin(); it != AllOldStructures.end(); it++)
{
if (it->edict != CurrentCommChair) { return false; }
}
return true;
} }

View file

@ -63,6 +63,7 @@ Vector AITAC_GetCommChairLocation(AvHTeamNumber Team);
edict_t* AITAC_GetCommChair(AvHTeamNumber Team); edict_t* AITAC_GetCommChair(AvHTeamNumber Team);
Vector AITAC_GetTeamStartingLocation(AvHTeamNumber Team); Vector AITAC_GetTeamStartingLocation(AvHTeamNumber Team);
Vector AITAC_GetTeamRelocationPoint(AvHTeamNumber Team);
AvHAIResourceNode* AITAC_GetRandomResourceNode(AvHTeamNumber SearchingTeam, const unsigned int ReachabilityFlags); AvHAIResourceNode* AITAC_GetRandomResourceNode(AvHTeamNumber SearchingTeam, const unsigned int ReachabilityFlags);
@ -211,4 +212,8 @@ AvHAISquad* AITAC_GetSquadForObjective(AvHAIPlayer* pBot, edict_t* TaskTarget, B
AvHAISquad* AITAC_GetSquadForObjective(AvHAIPlayer* pBot, Vector TaskLocation, BotTaskType ObjectiveType); AvHAISquad* AITAC_GetSquadForObjective(AvHAIPlayer* pBot, Vector TaskLocation, BotTaskType ObjectiveType);
Vector AITAC_GetGatherLocationForSquad(AvHAISquad* Squad); Vector AITAC_GetGatherLocationForSquad(AvHAISquad* Squad);
Vector AITAC_FindNewTeamRelocationPoint(AvHTeamNumber Team);
bool AITAC_IsRelocationPointStillValid(AvHTeamNumber RelocationTeam, Vector RelocationPoint);
bool AITAC_IsRelocationCompleted(AvHTeamNumber RelocationTeam, Vector RelocationPoint);
#endif #endif

View file

@ -1928,9 +1928,34 @@ void BotProgressTakeCommandTask(AvHAIPlayer* pBot)
// Don't take command if we already have a commander // Don't take command if we already have a commander
if (pBot->Player->GetCommander()) { return; } if (pBot->Player->GetCommander()) { return; }
edict_t* CommChair = AITAC_GetCommChair(pBot->Player->GetTeam()); edict_t* CommChair = nullptr;
if (!CommChair) { return; } AvHTeamNumber BotTeam = pBot->Player->GetTeam();
Vector RelocationPoint = pBot->RelocationSpot;
if (!vIsZero(RelocationPoint) && AITAC_IsRelocationCompleted(BotTeam, RelocationPoint))
{
DeployableSearchFilter RelocationChairFilter;
RelocationChairFilter.DeployableTeam = BotTeam;
RelocationChairFilter.DeployableTypes = STRUCTURE_MARINE_COMMCHAIR;
RelocationChairFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
RelocationChairFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
RelocationChairFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f);
AvHAIBuildableStructure RelocationChair = AITAC_FindClosestDeployableToLocation(RelocationPoint, &RelocationChairFilter);
if (RelocationChair.IsValid())
{
CommChair = RelocationChair.edict;
}
}
else
{
CommChair = AITAC_GetCommChair(BotTeam);
}
if (FNullEnt(CommChair)) { return; }
float DistFromChair = vDist2DSq(pBot->Edict->v.origin, CommChair->v.origin); float DistFromChair = vDist2DSq(pBot->Edict->v.origin, CommChair->v.origin);
@ -2719,6 +2744,9 @@ void AlienProgressSecureHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
if (PhaseGate.IsValid()) if (PhaseGate.IsValid())
{ {
// If the phase gate is next to an electrified structure, and we are a skulk or lerk, then attack
// The electrified structure instead. I might change this to avoid it altogether, but there's nothing
// wrong with trying to chip away at the TF if you're a skulk
if (bAvoidElectrified) if (bAvoidElectrified)
{ {
EnemyStuffFilter.DeployableTypes = SEARCH_ALL_STRUCTURES; EnemyStuffFilter.DeployableTypes = SEARCH_ALL_STRUCTURES;