Reimplemented regular marine AI

This commit is contained in:
RGreenlees 2024-01-05 16:55:38 +00:00 committed by pierow
parent 32e7a74db5
commit 933ed063f0
15 changed files with 1651 additions and 216 deletions

View file

@ -7,6 +7,7 @@
#include "AvHAINavigation.h"
#include "AvHAITask.h"
#include "AvHAIHelper.h"
#include "AvHAIPlayerManager.h"
#include "../AvHSharedUtil.h"
#include "../AvHServerUtil.h"
@ -170,6 +171,373 @@ bool AICOMM_IssueBuildOrder(AvHAIPlayer* pBot, edict_t* Recipient, edict_t* Targ
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.OrderPurpose = OrderPurpose;
AICOMM_IssueOrderForAssignedJob(pBot, &NewOrder);
pBot->ActiveOrders.push_back(NewOrder);
}
void AICOMM_IssueOrderForAssignedJob(AvHAIPlayer* pBot, ai_commander_order* Order)
{
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)
{
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(25.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_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;
}
bool AICOMM_IsOrderStillValid(AvHAIPlayer* pBot, ai_commander_order* Order)
{
if (FNullEnt(Order->Assignee) || FNullEnt(Order->OrderTarget) || !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
if (!Hive || Hive->Status == HIVE_STATUS_UNBUILT) { return false; }
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 = UTIL_MetresToGoldSrcUnits(25.0f);
// Check that any siege structure exists. This will avoid situations where commander keeps ordering marines to a hive that is too well defended
bool bSiegeStructureExists = AITAC_DeployableExistsAtLocation(Hive->Location, &StructureFilter);
return bSiegeStructureExists;
}
break;
case ORDERPURPOSE_SECURE_RESNODE:
{
const AvHAIResourceNode* ResNode = AITAC_GetResourceNodeFromEdict(Order->OrderTarget);
if (!ResNode) { return false; }
return (ResNode->OwningTeam != pBot->Player->GetTeam() || !ResNode->ActiveTowerEntity || !UTIL_StructureIsFullyBuilt(ResNode->ActiveTowerEntity));
}
break;
default:
return false;
}
return false;
}
bool AICOMM_DoesPlayerOrderNeedReminder(AvHAIPlayer* pBot, ai_commander_order* Order)
{
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;
}
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++;
}
}
int NumPlayersOnTeam = AITAC_GetNumActivePlayersOnTeam(pBot->Player->GetTeam());
int DesiredPlayers = imini(2, (int)ceilf((float)NumPlayersOnTeam *0.5f));
const AvHAIHiveDefinition* SiegedHive = AITAC_GetNearestHiveUnderActiveSiege(pBot->Player->GetTeam(), AITAC_GetCommChairLocation(pBot->Player->GetTeam()));
if (SiegedHive)
{
int NumAssignedPlayers = AICOMM_GetNumPlayersAssignedToOrder(pBot, SiegedHive->HiveEntity->edict(), ORDERPURPOSE_SIEGE_HIVE);
int NumSiegingPlayers = AICOMM_GetNumPlayersAssignedToOrder(pBot, SiegedHive->HiveEntity->edict(), ORDERPURPOSE_SIEGE_HIVE);
if ((NumAssignedPlayers + NumSiegingPlayers) < DesiredPlayers)
{
for (int i = 0; i < DesiredPlayers - (NumAssignedPlayers + NumSiegingPlayers); i++)
{
edict_t* NewAssignee = AICOMM_GetPlayerWithNoOrderNearestLocation(pBot, SiegedHive->FloorLocation);
if (!FNullEnt(NewAssignee))
{
AICOMM_AssignNewPlayerOrder(pBot, NewAssignee, SiegedHive->HiveEntity->edict(), ORDERPURPOSE_SIEGE_HIVE);
}
}
}
}
vector<AvHAIHiveDefinition*> Hives = AITAC_GetAllHives();
AvHAIHiveDefinition* EmptyHive = nullptr;
float MinDist = 0.0f;
for (auto it = Hives.begin(); it != Hives.end(); it++)
{
AvHAIHiveDefinition* ThisHive = (*it);
if (ThisHive->Status != HIVE_STATUS_UNBUILT) { continue; }
if (AICOMM_IsHiveFullySecured(pBot, ThisHive, false)) { continue; }
int NumPlayersSecuring = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), ThisHive->FloorLocation, UTIL_MetresToGoldSrcUnits(10.0f), false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER);
int NumAssignedPlayers = AICOMM_GetNumPlayersAssignedToOrder(pBot, ThisHive->HiveEntity->edict(), ORDERPURPOSE_SECURE_HIVE);
if ((NumPlayersSecuring + NumAssignedPlayers) < DesiredPlayers)
{
float ThisDist = vDist2DSq(AITAC_GetCommChairLocation(pBot->Player->GetTeam()), ThisHive->Location);
if (!EmptyHive || ThisDist < MinDist)
{
EmptyHive = ThisHive;
MinDist = ThisDist;
}
}
}
if (EmptyHive)
{
edict_t* NewAssignee = AICOMM_GetPlayerWithNoOrderNearestLocation(pBot, EmptyHive->FloorLocation);
if (!FNullEnt(NewAssignee))
{
AICOMM_AssignNewPlayerOrder(pBot, NewAssignee, EmptyHive->HiveEntity->edict(), ORDERPURPOSE_SECURE_HIVE);
}
}
DeployableSearchFilter ResNodeFilter;
ResNodeFilter.DeployableTeam = TEAM_IND;
ResNodeFilter.ReachabilityTeam = pBot->Player->GetTeam();
ResNodeFilter.ReachabilityFlags = AI_REACHABILITY_MARINE;
const AvHAIResourceNode* ResNode = AITAC_FindNearestResourceNodeToLocation(AITAC_GetCommChairLocation(pBot->Player->GetTeam()), &ResNodeFilter);
if (ResNode)
{
int NumPlayersSecuring = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), ResNode->Location, UTIL_MetresToGoldSrcUnits(5.0f), false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER);
int NumAssignedPlayers = AICOMM_GetNumPlayersAssignedToOrder(pBot, ResNode->ResourceEntity->edict(), ORDERPURPOSE_SECURE_RESNODE);
if ((NumPlayersSecuring + NumAssignedPlayers) < 1)
{
edict_t* NewAssignee = AICOMM_GetPlayerWithNoOrderNearestLocation(pBot, ResNode->Location);
if (!FNullEnt(NewAssignee))
{
AICOMM_AssignNewPlayerOrder(pBot, NewAssignee, ResNode->ResourceEntity->edict(), ORDERPURPOSE_SECURE_RESNODE);
}
}
}
}
edict_t* AICOMM_GetPlayerWithNoOrderNearestLocation(AvHAIPlayer* pBot, Vector SearchLocation)
{
edict_t* Result = nullptr;
float MinDist = 0.0f;
vector<AvHPlayer*> 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);
if (!IsPlayerActiveInGame(PlayerRef->edict()))
{
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<AvHPlayer*>(CBaseEntity::Instance(it->Assignee));
if (!ThisPlayer) { continue; }
std::vector<AvHPlayer*>::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; }
@ -510,6 +878,8 @@ bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot, commander_action* Action)
}
}
if (pBot->Player->GetResources() < 30) { return false; }
StructureFilter.DeployableTypes = STRUCTURE_MARINE_ARMSLAB;
StructureFilter.MaxSearchRadius = 0.0f;
@ -944,6 +1314,12 @@ bool AICOMM_PerformNextSiegeHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinit
if (vIsZero(NextBuildPosition))
{
NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), SiegeLocation, UTIL_MetresToGoldSrcUnits(3.0f));
if (vIsZero(NextBuildPosition))
{
// Fall-back, this could end up putting the structure in dodgy spots but better than not placing it at all
NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), SiegeLocation, UTIL_MetresToGoldSrcUnits(3.0f));
}
}
if (!ExistingPG)
@ -953,6 +1329,7 @@ bool AICOMM_PerformNextSiegeHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinit
if (!ExistingTF)
{
if (vDist2DSq(NextBuildPosition, HiveToSiege->Location) > sqrf(UTIL_MetresToGoldSrcUnits(20.0f))) { return true; }
return AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_TURRETFACTORY, NextBuildPosition, STRUCTURE_PURPOSE_SIEGE);
}
@ -980,13 +1357,22 @@ bool AICOMM_PerformNextSiegeHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinit
{
SiegeLocation = ExistingTF->Location;
NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), SiegeLocation, UTIL_MetresToGoldSrcUnits(3.0f));
NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), SiegeLocation, UTIL_MetresToGoldSrcUnits(5.0f));
if (vIsZero(NextBuildPosition))
{
// Reduce radius to avoid putting it on the other side of a wall or something
NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), SiegeLocation, UTIL_MetresToGoldSrcUnits(3.0f));
if (vIsZero(NextBuildPosition))
{
// Fall-back, this could end up putting the structure in dodgy spots but better than not placing it at all
NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), SiegeLocation, UTIL_MetresToGoldSrcUnits(5.0f));
}
}
// Don't put the turret out of siege range
if (vDist2DSq(NextBuildPosition, HiveToSiege->Location) > sqrf(kSiegeTurretRange)) { return true; }
return AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_SIEGETURRET, NextBuildPosition, STRUCTURE_PURPOSE_SIEGE);
}
@ -1037,7 +1423,12 @@ bool AICOMM_PerformNextSecureHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefini
if (!ExistingPG)
{
Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), OutpostLocation, UTIL_MetresToGoldSrcUnits(5.0f));
Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), OutpostLocation, UTIL_MetresToGoldSrcUnits(5.0f));
if (vIsZero(BuildLocation))
{
BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), OutpostLocation, UTIL_MetresToGoldSrcUnits(5.0f));
}
if (!vIsZero(BuildLocation))
{
@ -1057,7 +1448,12 @@ bool AICOMM_PerformNextSecureHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefini
if (!ExistingTF)
{
Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), OutpostLocation, UTIL_MetresToGoldSrcUnits(5.0f));
Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), OutpostLocation, UTIL_MetresToGoldSrcUnits(3.0f));
if (vIsZero(BuildLocation))
{
BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), OutpostLocation, UTIL_MetresToGoldSrcUnits(5.0f));
}
if (!vIsZero(BuildLocation))
{
@ -1440,6 +1836,8 @@ void AICOMM_CommanderThink(AvHAIPlayer* pBot)
if (gpGlobals->time < pBot->next_commander_action_time) { return; }
AICOMM_UpdatePlayerOrders(pBot);
if (AICOMM_CheckForNextRecycleAction(pBot)) { return; }
if (AICOMM_CheckForNextSupportAction(pBot)) { return; }
if (AICOMM_CheckForNextBuildAction(pBot, &pBot->BuildAction)) { return; }
@ -1479,6 +1877,15 @@ bool AICOMM_IsCommanderActionValid(AvHAIPlayer* pBot, commander_action* Action)
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;}
}
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; }
@ -1524,48 +1931,30 @@ const AvHAIHiveDefinition* AICOMM_GetEmptyHiveOpportunityNearestLocation(AvHAIPl
if (Hive->Status != HIVE_STATUS_UNBUILT) { continue; }
if (AICOMM_IsHiveFullySecured(CommanderBot, Hive)) { continue; }
if (AICOMM_IsHiveFullySecured(CommanderBot, Hive, true)) { continue; }
if (AITAC_GetNearestHiddenPlayerInLocation(CommanderTeam, Hive->Location, UTIL_MetresToGoldSrcUnits(10.0f)) == nullptr) { continue; }
Vector SecureLocation = Hive->FloorLocation;
if (AITAC_AnyPlayerOnTeamWithLOS(CommanderTeam, Hive->Location, UTIL_MetresToGoldSrcUnits(10.0f)))
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(10.0f);
AvHAIBuildableStructure* ExistingStructure = AITAC_FindClosestDeployableToLocation(Hive->FloorLocation, &StructureFilter);
if (ExistingStructure && UTIL_QuickTrace(nullptr, UTIL_GetCentreOfEntity(ExistingStructure->edict), Hive->Location))
{
DeployableSearchFilter StructureFilter;
StructureFilter.DeployableTeam = CommanderTeam;
StructureFilter.ReachabilityTeam = CommanderTeam;
StructureFilter.ReachabilityFlags = AI_REACHABILITY_MARINE;
StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
StructureFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE;
StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f);
AvHAIBuildableStructure* PG = AITAC_FindClosestDeployableToLocation(Hive->FloorLocation, &StructureFilter);
bool bCanSeePG = (!PG || AITAC_AnyPlayerOnTeamWithLOS(CommanderTeam, UTIL_GetCentreOfEntity(PG->edict), UTIL_MetresToGoldSrcUnits(10.0f)));
if (!bCanSeePG)
{
StructureFilter.DeployableTypes = STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY;
AvHAIBuildableStructure* TF = AITAC_FindClosestDeployableToLocation(Hive->FloorLocation, &StructureFilter);
bool bNeedsElectrifying = false;
if (TF)
{
StructureFilter.DeployableTypes = STRUCTURE_MARINE_TURRET;
bNeedsElectrifying = (UTIL_StructureIsFullyBuilt(TF->edict) && !UTIL_IsStructureElectrified(TF->edict) && AITAC_DeployableExistsAtLocation(TF->Location, &StructureFilter));
}
bool bCanSeeTF = (!TF || AITAC_AnyPlayerOnTeamWithLOS(CommanderTeam, UTIL_GetCentreOfEntity(TF->edict), UTIL_MetresToGoldSrcUnits(10.0f)));
if (!bNeedsElectrifying && !bCanSeePG && !bCanSeeTF) { continue; }
}
SecureLocation = ExistingStructure->Location;
}
float MarineDist = (ExistingStructure) ? UTIL_MetresToGoldSrcUnits(5.0f) : UTIL_MetresToGoldSrcUnits(10.0f);
if (AITAC_GetNearestHiddenPlayerInLocation(CommanderTeam, SecureLocation, MarineDist) == nullptr) { continue; }
float ThisDist = vDist2DSq(Hive->FloorLocation, SearchLocation);
if (!Result || ThisDist < MinDist)
@ -1579,7 +1968,7 @@ const AvHAIHiveDefinition* AICOMM_GetEmptyHiveOpportunityNearestLocation(AvHAIPl
return Result;
}
bool AICOMM_IsHiveFullySecured(AvHAIPlayer* CommanderBot, const AvHAIHiveDefinition* Hive)
bool AICOMM_IsHiveFullySecured(AvHAIPlayer* CommanderBot, const AvHAIHiveDefinition* Hive, bool bIncludeElectrical)
{
AvHTeamNumber CommanderTeam = CommanderBot->Player->GetTeam();
@ -1595,6 +1984,7 @@ bool AICOMM_IsHiveFullySecured(AvHAIPlayer* CommanderBot, const AvHAIHiveDefinit
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);
@ -1604,7 +1994,7 @@ bool AICOMM_IsHiveFullySecured(AvHAIPlayer* CommanderBot, const AvHAIHiveDefinit
{
AvHAIBuildableStructure* Structure = (*it);
if (Structure->StructureType == STRUCTURE_MARINE_TURRETFACTORY)
if (Structure->StructureType == STRUCTURE_MARINE_PHASEGATE)
{
bHasPhaseGate = true;
}
@ -1619,16 +2009,13 @@ bool AICOMM_IsHiveFullySecured(AvHAIPlayer* CommanderBot, const AvHAIHiveDefinit
NumTurrets = AITAC_GetNumDeployablesNearLocation(Structure->Location, &SearchFilter);
}
}
const AvHAIResourceNode* ResNode = Hive->HiveResNodeRef;
bool bSecuredResNode = (!ResNode || (ResNode->bIsOccupied && ResNode->OwningTeam == CommanderTeam));
bool bSecuredResNode = (!ResNode || (ResNode->bIsOccupied && ResNode->OwningTeam == CommanderTeam && UTIL_StructureIsFullyBuilt(ResNode->ActiveTowerEntity)));
bool bShouldElectrifyResNode = (ResNode && bSecuredResNode && CommanderBot->Player->GetResources() > 100 && AITAC_ElectricalResearchIsAvailable(ResNode->ActiveTowerEntity));
return ((!bPhaseGatesAvailable || bHasPhaseGate) && bHasTurretFactory && bTurretFactoryElectrified && NumTurrets >= 5 && bSecuredResNode && !bShouldElectrifyResNode);
return ((!bPhaseGatesAvailable || bHasPhaseGate) && bHasTurretFactory && (!bIncludeElectrical || bTurretFactoryElectrified) && NumTurrets >= 5 && bSecuredResNode);
}

View file

@ -12,6 +12,8 @@
#include "AvHAIConstants.h"
static const float MIN_COMMANDER_REMIND_TIME = 20.0f; // How frequently the commander can nag a player to do something, if they don't think they're doing it
bool AICOMM_DeployStructure(AvHAIPlayer* pBot, const AvHAIDeployableStructureType StructureToDeploy, const Vector Location, StructurePurpose Purpose = STRUCTURE_PURPOSE_NONE);
bool AICOMM_DeployItem(AvHAIPlayer* pBot, const AvHAIDeployableItemType ItemToDeploy, const Vector Location);
bool AICOMM_UpgradeStructure(AvHAIPlayer* pBot, AvHAIBuildableStructure* StructureToUpgrade);
@ -19,11 +21,19 @@ bool AICOMM_ResearchTech(AvHAIPlayer* pBot, AvHAIBuildableStructure* StructureTo
bool AICOMM_RecycleStructure(AvHAIPlayer* pBot, AvHAIBuildableStructure* StructureToRecycle);
bool AICOMM_IssueMovementOrder(AvHAIPlayer* pBot, edict_t* Recipient, const Vector MoveLocation);
bool AICOMM_IssueBuildOrder(AvHAIPlayer* pBot, edict_t* Recipient, edict_t* TargetStructure);
bool AICOMM_IssueBuildOrder(AvHAIPlayer* pBot, edict_t* Recipient, edict_t* TargetStructuree);
bool AICOMM_IssueSecureHiveOrder(AvHAIPlayer* pBot, edict_t* Recipient, const AvHAIHiveDefinition* HiveToSecure);
bool AICOMM_IssueSiegeHiveOrder(AvHAIPlayer* pBot, edict_t* Recipient, const AvHAIHiveDefinition* HiveToSiege, const Vector SiegePosition);
bool AICOMM_IssueSecureResNodeOrder(AvHAIPlayer* pBot, edict_t* Recipient, const AvHAIResourceNode* ResNode);
void AICOMM_AssignNewPlayerOrder(AvHAIPlayer* pBot, edict_t* Assignee, edict_t* TargetEntity, AvHAIOrderPurpose OrderPurpose);
int AICOMM_GetNumPlayersAssignedToOrder(AvHAIPlayer* pBot, edict_t* TargetEntity, AvHAIOrderPurpose OrderPurpose);
bool AICOMM_IsOrderStillValid(AvHAIPlayer* pBot, ai_commander_order* Order);
void AICOMM_UpdatePlayerOrders(AvHAIPlayer* pBot);
edict_t* AICOMM_GetPlayerWithNoOrderNearestLocation(AvHAIPlayer* pBot, Vector SearchLocation);
bool AICOMM_DoesPlayerOrderNeedReminder(AvHAIPlayer* pBot, ai_commander_order* Order);
void AICOMM_IssueOrderForAssignedJob(AvHAIPlayer* pBot, ai_commander_order* Order);
void AICOMM_ClearAction(commander_action* Action);
bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot, commander_action* Action);
bool AICOMM_CheckForNextSupportAction(AvHAIPlayer* pBot);
@ -46,7 +56,7 @@ ai_commander_request* AICOMM_GetExistingRequestForPlayer(AvHAIPlayer* pBot, edic
void AICOMM_CheckNewRequests(AvHAIPlayer* pBot);
bool AICOMM_IsRequestValid(ai_commander_request* Request);
bool AICOMM_IsHiveFullySecured(AvHAIPlayer* CommanderBot, const AvHAIHiveDefinition* Hive);
bool AICOMM_IsHiveFullySecured(AvHAIPlayer* CommanderBot, const AvHAIHiveDefinition* Hive, bool bIncludeElectrical);
bool AICOMM_ShouldCommanderLeaveChair(AvHAIPlayer* pBot);

View file

@ -14,15 +14,6 @@ typedef enum _BOTFILLMODE
} BotFillMode;
// Bot commander mode, should the bot go commander and when
typedef enum _COMMANDERMODE
{
COMMANDERMODE_NEVER = 0, // Bot never tries to command
COMMANDERMODE_IFNOHUMAN, // Bot only commands if no human is on the marine team
COMMANDERMODE_ALWAYS // Bot will always take command if no human does after CommanderWaitTime expires
} CommanderMode;
// Each map can have a desired marine and alien team size
typedef struct _TEAMSIZEDEFINITIONS
{

View file

@ -171,6 +171,36 @@ typedef enum _STRUCTUREPURPOSE
} StructurePurpose;
typedef enum _AVHAICOMMANDERMODE
{
COMMANDERMODE_DISABLED, // AI Commander not allowed
COMMANDERMODE_IFNOHUMAN, // AI Commander only allowed if no humans are on the marine team
COMMANDERMODE_ENABLED // AI Commander allowed if no human takes charge (following grace period)
} AvHAICommanderMode;
// Bot's role on the team. For marines, this only governs what they do when left to their own devices.
// Marine bots will always listen to orders from the commander regardless of role.
typedef enum _AVHAIBOTROLE
{
BOT_ROLE_NONE, // No defined role
// General Roles
BOT_ROLE_FIND_RESOURCES, // Will hunt for uncapped resource nodes and cap them. Will attack enemy resource towers
BOT_ROLE_SWEEPER, // Defensive role to protect infrastructure and build at base. Will patrol to keep outposts secure
BOT_ROLE_ASSAULT, // Will go to attack the hive and other alien structures
// Marine-only Roles
BOT_ROLE_COMMAND, // Will attempt to take command
BOT_ROLE_BOMBARDIER, // Bot is armed with a GL and wants to wreck your shit
// Alien-only roles
BOT_ROLE_BUILDER, // Will focus on building chambers and hives. Stays gorge most of the time
BOT_ROLE_HARASS // Focuses on taking down enemy resource nodes and hunting the enemy
} AvHAIBotRole;
typedef struct _OFF_MESH_CONN
{
unsigned int ConnectionRefs[2];
@ -349,27 +379,6 @@ typedef enum
}
BotAttackResult;
// Bot's role on the team. For marines, this only governs what they do when left to their own devices.
// Marine bots will always listen to orders from the commander regardless of role.
enum BotRole
{
BOT_ROLE_NONE, // No defined role
// Marine Roles
BOT_ROLE_COMMAND, // Will attempt to take command
BOT_ROLE_FIND_RESOURCES, // Will hunt for uncapped resource nodes and cap them. Will attack enemy resource towers
BOT_ROLE_SWEEPER, // Defensive role to protect infrastructure and build at base. Will patrol to keep outposts secure
BOT_ROLE_ASSAULT, // Will go to attack the hive and other alien structures
BOT_ROLE_BOMBARDIER, // Bot is armed with a GL and wants to wreck your shit
// Alien roles
BOT_ROLE_RES_CAPPER, // Will hunt for uncapped nodes or ones held by the enemy and cap them
BOT_ROLE_BUILDER, // Will focus on building chambers and hives. Stays gorge most of the time
BOT_ROLE_HARASS, // Focuses on taking down enemy resource nodes and hunting the enemy
BOT_ROLE_DESTROYER // Will go fade/onos when it can, focuses on attacking critical infrastructure
};
// Bot path node. A path will be several of these strung together to lead the bot to its destination
typedef struct _BOT_PATH_NODE
@ -541,6 +550,24 @@ typedef struct _COMMANDER_ACTION
} commander_action;
typedef enum
{
ORDERPURPOSE_NONE,
ORDERPURPOSE_SECURE_HIVE,
ORDERPURPOSE_SIEGE_HIVE,
ORDERPURPOSE_SECURE_RESNODE
} AvHAIOrderPurpose;
typedef struct _AI_COMMANDER_ORDER
{
edict_t* Assignee = nullptr;
AvHAIOrderPurpose OrderPurpose = ORDERPURPOSE_NONE;
edict_t* OrderTarget = nullptr;
Vector OrderLocation = g_vecZero;
float LastReminderTime = 0.0f;
float LastPlayerDistance = 0.0f;
} ai_commander_order;
typedef struct _AI_COMMANDER_REQUEST
{
bool bNewRequest = false; // Is this a new request just come in?
@ -593,6 +620,8 @@ typedef struct AVH_AI_PLAYER
AvHAIPlayerTask WantsAndNeedsTask;
AvHAIPlayerTask CommanderTask; // Task assigned by the commander
float BotNextTaskEvaluationTime = 0.0f;
bot_skill BotSkillSettings;
char PathStatus[128]; // Debug used to help figure out what's going on with a bot's path finding
@ -607,6 +636,7 @@ typedef struct AVH_AI_PLAYER
commander_action* CurrentAction;
vector<ai_commander_request> ActiveRequests;
vector<ai_commander_order> ActiveOrders;
float next_commander_action_time = 0.0f;
@ -633,6 +663,9 @@ typedef struct AVH_AI_PLAYER
Vector ViewForwardVector = g_vecZero; // Bot's current forward unit vector
Vector LastSafeLocation = g_vecZero;
AvHAIBotRole BotRole = BOT_ROLE_NONE;
} AvHAIPlayer;

View file

@ -426,4 +426,47 @@ void UTIL_DrawLine(edict_t* pEntity, Vector start, Vector end, int r, int g, int
WRITE_BYTE(250); // brightness
WRITE_BYTE(5); // speed
MESSAGE_END();
}
void UTIL_DrawHUDText(edict_t* pEntity, char channel, float x, float y, unsigned char r, unsigned char g, unsigned char b, const char* string)
{
// higher level wrapper for hudtextparms TE_TEXTMESSAGEs. This function is meant to be called
// every frame, since the duration of the display is roughly worth the duration of a video
// frame. The X and Y coordinates are unary fractions which are bound to this rule:
// 0: top of the screen (Y) or left of the screen (X), left aligned text
// 1: bottom of the screen (Y) or right of the screen (X), right aligned text
// -1(only one negative value possible): center of the screen (X and Y), centered text
// Any value ranging from 0 to 1 will represent a valid position on the screen.
//static short duration;
if (FNullEnt(pEntity)) { return; }
//duration = (int)GAME_GetServerMSecVal() * 256 / 750; // compute text message duration
//if (duration < 5)
// duration = 5;
MESSAGE_BEGIN(MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, NULL, pEntity);
WRITE_BYTE(TE_TEXTMESSAGE);
WRITE_BYTE(channel); // channel
WRITE_SHORT((int)(x * 8192.0f)); // x coordinates * 8192
WRITE_SHORT((int)(y * 8192.0f)); // y coordinates * 8192
WRITE_BYTE(0); // effect (fade in/out)
WRITE_BYTE(r); // initial RED
WRITE_BYTE(g); // initial GREEN
WRITE_BYTE(b); // initial BLUE
WRITE_BYTE(1); // initial ALPHA
WRITE_BYTE(r); // effect RED
WRITE_BYTE(g); // effect GREEN
WRITE_BYTE(b); // effect BLUE
WRITE_BYTE(1); // effect ALPHA
WRITE_SHORT(0); // fade-in time in seconds * 256
WRITE_SHORT(0); // fade-out time in seconds * 256
WRITE_SHORT(1); // hold time in seconds * 256
WRITE_STRING(string);//string); // send the string
MESSAGE_END(); // end
return;
}

View file

@ -44,4 +44,6 @@ void UTIL_DrawLine(edict_t* pEntity, Vector start, Vector end, int r, int g, int
// Draws a coloured line using RGB input, between start and end for the given player (pEntity) for given number of seconds
void UTIL_DrawLine(edict_t* pEntity, Vector start, Vector end, float drawTimeSeconds, int r, int g, int b);
void UTIL_DrawHUDText(edict_t* pEntity, char channel, float x, float y, unsigned char r, unsigned char g, unsigned char b, const char* string);
#endif

View file

@ -468,7 +468,7 @@ void UTIL_AddStructureTemporaryObstacles(AvHAIBuildableStructure* Structure)
}
// Always cut a hole in the building nav mesh so we don't try to place anything on top of this structure in future
unsigned int NewObstacleRef = UTIL_AddTemporaryObstacle(BUILDING_NAV_MESH, UTIL_GetCentreOfEntity(Structure->edict), Radius * 1.5f, 100.0f, DT_TILECACHE_NULL_AREA);
unsigned int NewObstacleRef = UTIL_AddTemporaryObstacle(BUILDING_NAV_MESH, UTIL_GetCentreOfEntity(Structure->edict), Radius * 1.1f, 100.0f, DT_TILECACHE_NULL_AREA);
if (NewObstacleRef > 0)
{

View file

@ -9,7 +9,9 @@
#include "AvHAITactical.h"
#include "AvHAITask.h"
#include "AvHAICommander.h"
#include "AvHAIPlayerManager.h"
#include "../AvHGamerules.h"
#include "../AvHMessage.h"
extern nav_mesh NavMeshes[MAX_NAV_MESHES]; // Array of nav meshes. Currently only 3 are used (building, onos, and regular)
@ -1511,6 +1513,546 @@ void DroneThink(AvHAIPlayer* pBot)
//AIDEBUG_DrawBotPath(pBot);
}
void SetNewAIPlayerRole(AvHAIPlayer* pBot, AvHAIBotRole NewRole)
{
if (NewRole != pBot->BotRole)
{
AITASK_ClearBotTask(pBot, &pBot->PrimaryBotTask);
AITASK_ClearBotTask(pBot, &pBot->SecondaryBotTask);
pBot->BotRole = NewRole;
}
}
void UpdateAIPlayerCORole(AvHAIPlayer* pBot)
{
}
void UpdateAIPlayerDMRole(AvHAIPlayer* pBot)
{
}
void UpdateAIAlienPlayerNSRole(AvHAIPlayer* pBot)
{
}
bool ShouldAIPlayerTakeCommand(AvHAIPlayer* pBot)
{
AvHAICommanderMode CurrentCommanderMode = AIMGR_GetCommanderMode();
// Don't go commander if bots are not allowed to
if (CurrentCommanderMode == COMMANDERMODE_DISABLED) { return false; }
AvHTeamNumber BotTeamNumber = pBot->Player->GetTeam();
AvHTeam* BotTeam = GetGameRules()->GetTeam(BotTeamNumber);
// Don't go commander if we're not an alien. You never know with the way I structure my logic...
if (!BotTeam || BotTeam->GetTeamType() != AVH_CLASS_TYPE_MARINE) { return false; }
// Don't go commander if we're only supposed to command when there aren't any humans and we have one
if (CurrentCommanderMode == COMMANDERMODE_IFNOHUMAN && AIMGR_GetNumHumanPlayersOnTeam(BotTeamNumber) > 0) { return false; }
AvHPlayer* CurrentCommander = BotTeam->GetCommanderPlayer();
// Don't go commander if we already have one, and it's not us
if (CurrentCommander)
{
return CurrentCommander == pBot->Player;
}
// Don't go commander if there is another bot already taking command
if (AIMGR_GetNumAIPlayersWithRoleOnTeam(BotTeamNumber, BOT_ROLE_COMMAND, pBot) > 0) { return false; }
float ThisBotDist = vDist2DSq(pBot->Edict->v.origin, AITAC_GetCommChairLocation(BotTeamNumber));
// Only go commander if we're the closest bot to the chair
vector <AvHAIPlayer*> BotList = AIMGR_GetAIPlayersOnTeam(BotTeamNumber);
for (auto it = BotList.begin(); it != BotList.end(); it++)
{
AvHAIPlayer* OtherBot = (*it);
float OtherBotDist = vDist2DSq(OtherBot->Edict->v.origin, AITAC_GetCommChairLocation(BotTeamNumber));
if (OtherBot != pBot && IsPlayerActiveInGame(pBot->Edict) && OtherBotDist < ThisBotDist)
{
// We aren't the closest, let the other guy take command
return false;
}
}
// We must be the closest!
return true;
}
void UpdateAIMarinePlayerNSRole(AvHAIPlayer* pBot)
{
AvHTeamNumber BotTeamNumber = pBot->Player->GetTeam();
if (BotTeamNumber == TEAM_IND)
{
SetNewAIPlayerRole(pBot, BOT_ROLE_NONE);
return;
}
if (ShouldAIPlayerTakeCommand(pBot))
{
// We're going to go commander!
SetNewAIPlayerRole(pBot, BOT_ROLE_COMMAND);
return;
}
int NumSweeperBots = AIMGR_GetNumAIPlayersWithRoleOnTeam(BotTeamNumber, BOT_ROLE_SWEEPER, pBot);
// Always have a sweeper
if (NumSweeperBots < 1)
{
SetNewAIPlayerRole(pBot, BOT_ROLE_SWEEPER);
return;
}
// Always go bombardier if we have a grenade launcher
if (PlayerHasWeapon(pBot->Player, WEAPON_MARINE_GL))
{
SetNewAIPlayerRole(pBot, BOT_ROLE_BOMBARDIER);
return;
}
// If we own less than half the res nodes in the map, then we want 2 marines to cap them. Otherwise, have 1
float ResNodeOwnership = AITAC_GetTeamResNodeOwnership(BotTeamNumber);
int DesiredResCappers = (ResNodeOwnership < 0.5f) ? 2 : 1;
int NumCappers = AIMGR_GetNumAIPlayersWithRoleOnTeam(BotTeamNumber, BOT_ROLE_FIND_RESOURCES, pBot);
if (NumCappers < DesiredResCappers)
{
SetNewAIPlayerRole(pBot, BOT_ROLE_FIND_RESOURCES);
return;
}
// Everyone else goes assault
SetNewAIPlayerRole(pBot, BOT_ROLE_ASSAULT);
}
void AIPlayerNSThink(AvHAIPlayer* pBot)
{
AvHTeam* BotTeam = GetGameRules()->GetTeam(pBot->Player->GetTeam());
if (!BotTeam) { return; }
if (BotTeam->GetTeamType() == AVH_CLASS_TYPE_MARINE)
{
AIPlayerNSMarineThink(pBot);
}
else
{
AIPlayerNSAlienThink(pBot);
}
}
AvHAIPlayerTask* AIPlayerGetNextTask(AvHAIPlayer* pBot)
{
// Any orders issued by the commander take priority over everything else
if (pBot->CommanderTask.TaskType != TASK_NONE)
{
if (pBot->SecondaryBotTask.bTaskIsUrgent)
{
return &pBot->SecondaryBotTask;
}
else
{
return &pBot->CommanderTask;
}
}
// Prioritise healing our friends (heal tasks are only valid if the target is close by anyway)
if (pBot->SecondaryBotTask.TaskType == TASK_HEAL)
{
return &pBot->SecondaryBotTask;
}
if (AITASK_IsTaskUrgent(pBot, &pBot->WantsAndNeedsTask))
{
return &pBot->WantsAndNeedsTask;
}
if (AITASK_IsTaskUrgent(pBot, &pBot->PrimaryBotTask))
{
return &pBot->PrimaryBotTask;
}
if (AITASK_IsTaskUrgent(pBot, &pBot->SecondaryBotTask))
{
return &pBot->SecondaryBotTask;
}
if (pBot->WantsAndNeedsTask.TaskType != TASK_NONE)
{
return &pBot->WantsAndNeedsTask;
}
if (pBot->SecondaryBotTask.TaskType != TASK_NONE)
{
return &pBot->SecondaryBotTask;
}
return &pBot->PrimaryBotTask;
}
void AIPlayerNSMarineThink(AvHAIPlayer* pBot)
{
UpdateAIMarinePlayerNSRole(pBot);
if (pBot->BotRole == BOT_ROLE_COMMAND)
{
AICOMM_CommanderThink(pBot);
return;
}
if (!pBot->CurrentTask) { pBot->CurrentTask = &pBot->PrimaryBotTask; }
if (gpGlobals->time < pBot->BotNextTaskEvaluationTime)
{
if (pBot->CurrentTask && pBot->CurrentTask->TaskType != TASK_NONE)
{
BotProgressTask(pBot, pBot->CurrentTask);
return;
}
}
pBot->BotNextTaskEvaluationTime = gpGlobals->time + frandrange(0.2f, 0.5f);
AITASK_BotUpdateAndClearTasks(pBot);
AIPlayerSetPrimaryMarineTask(pBot, &pBot->PrimaryBotTask);
AIPlayerSetSecondaryMarineTask(pBot, &pBot->SecondaryBotTask);
pBot->CurrentTask = AIPlayerGetNextTask(pBot);
if (pBot->CurrentTask && pBot->CurrentTask->TaskType != TASK_NONE)
{
BotProgressTask(pBot, pBot->CurrentTask);
}
if (pBot->DesiredCombatWeapon == WEAPON_NONE)
{
pBot->DesiredCombatWeapon = BotMarineChooseBestWeapon(pBot, nullptr);
}
if (pBot->CommanderTask.TaskType != TASK_NONE)
{
UTIL_DrawLine(INDEXENT(1), pBot->Edict->v.origin, pBot->CommanderTask.TaskLocation);
}
}
void AIPlayerSetPrimaryMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
{
switch (pBot->BotRole)
{
case BOT_ROLE_SWEEPER:
AIPlayerSetMarineSweeperPrimaryTask(pBot, Task);
return;
case BOT_ROLE_FIND_RESOURCES:
AIPlayerSetMarineCapperPrimaryTask(pBot, Task);
return;
case BOT_ROLE_ASSAULT:
AIPlayerSetMarineAssaultPrimaryTask(pBot, Task);
return;
case BOT_ROLE_BOMBARDIER:
AIPlayerSetMarineBombardierPrimaryTask(pBot, Task);
return;
default:
return;
}
}
void AIPlayerSetMarineSweeperPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
{
if (Task->TaskType == TASK_GUARD) { return; }
AvHTeamNumber BotTeam = pBot->Player->GetTeam();
Vector CommChairLocation = AITAC_GetCommChairLocation(BotTeam);
DeployableSearchFilter StructureFilter;
StructureFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE;
StructureFilter.DeployableTeam = BotTeam;
StructureFilter.ReachabilityTeam = BotTeam;
StructureFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
StructureFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
if (AITAC_GetNumDeployablesNearLocation(CommChairLocation, &StructureFilter) < 2)
{
Task->TaskType = TASK_GUARD;
Task->TaskLocation = UTIL_GetRandomPointOnNavmeshInRadius(pBot->BotNavInfo.NavProfile, CommChairLocation, UTIL_MetresToGoldSrcUnits(10.0f));
Task->bTaskIsUrgent = false;
Task->TaskLength = frandrange(20.0f, 30.0f);
return;
}
AvHAIBuildableStructure* NearestPG = AITAC_FindClosestDeployableToLocation(pBot->Edict->v.origin, &StructureFilter);
vector<AvHAIBuildableStructure*> AllPG = AITAC_FindAllDeployables(pBot->Edict->v.origin, &StructureFilter);
AvHAIBuildableStructure* RandomPG = nullptr;
int HighestRand = 0;
for (auto it = AllPG.begin(); it != AllPG.end(); it++)
{
AvHAIBuildableStructure* ThisStruct = (*it);
if (ThisStruct == NearestPG) { continue; }
int ThisRand = irandrange(0, 100);
if (!RandomPG || ThisRand > HighestRand)
{
RandomPG = ThisStruct;
HighestRand = ThisRand;
}
}
if (RandomPG)
{
Task->TaskType = TASK_GUARD;
Task->TaskLocation = UTIL_GetRandomPointOnNavmeshInRadius(pBot->BotNavInfo.NavProfile, RandomPG->Location, UTIL_MetresToGoldSrcUnits(5.0f));
Task->bTaskIsUrgent = false;
Task->TaskLength = frandrange(20.0f, 30.0f);
return;
}
}
void AIPlayerSetMarineCapperPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
{
DeployableSearchFilter NodeFilter;
NodeFilter.DeployableTeam = TEAM_IND;
NodeFilter.ReachabilityTeam = pBot->Player->GetTeam();
NodeFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
AvHAIResourceNode* NearestNode = nullptr;
float MinDist = 0.0f;
vector<AvHAIResourceNode*> UnclaimedResourceNodes = AITAC_GetAllMatchingResourceNodes(pBot->Edict->v.origin, &NodeFilter);
for (auto it = UnclaimedResourceNodes.begin(); it != UnclaimedResourceNodes.end(); it++)
{
AvHAIResourceNode* ResNode = (*it);
int NumCappers = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), ResNode->Location, UTIL_MetresToGoldSrcUnits(4.0), false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER);
// Only want one capper to grab an empty one
if (NumCappers == 0)
{
float ThisDist = vDist2DSq(pBot->Edict->v.origin, ResNode->Location);
if (!NearestNode || ThisDist < MinDist)
{
NearestNode = ResNode;
MinDist = ThisDist;
}
}
}
if (NearestNode)
{
AITASK_SetCapResNodeTask(pBot, Task, NearestNode, false);
return;
}
MinDist = 0.0f;
NodeFilter.DeployableTeam = AIMGR_GetEnemyTeam(pBot->Player->GetTeam());
vector<AvHAIResourceNode*> EnemyResourceNodes = AITAC_GetAllMatchingResourceNodes(pBot->Edict->v.origin, &NodeFilter);
for (auto it = EnemyResourceNodes.begin(); it != EnemyResourceNodes.end(); it++)
{
AvHAIResourceNode* ResNode = (*it);
int NumCappers = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), ResNode->Location, UTIL_MetresToGoldSrcUnits(4.0), false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER);
// Allow for 2 cappers to attack an enemy resource node
if (NumCappers < 2)
{
float ThisDist = vDist2DSq(pBot->Edict->v.origin, ResNode->Location);
if (!NearestNode || ThisDist < MinDist)
{
NearestNode = ResNode;
MinDist = ThisDist;
}
}
}
if (NearestNode)
{
AITASK_SetCapResNodeTask(pBot, Task, NearestNode, false);
return;
}
// No res nodes to cap, go do assault stuff
AIPlayerSetMarineAssaultPrimaryTask(pBot, Task);
}
void AIPlayerSetMarineAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
{
// Go attack sieged hive
const AvHAIHiveDefinition* ActiveSiegeHive = AITAC_GetNearestHiveUnderActiveSiege(pBot->Player->GetTeam(), pBot->Edict->v.origin);
if (ActiveSiegeHive)
{
AITASK_SetAttackTask(pBot, Task, ActiveSiegeHive->HiveEntity->edict(), false);
return;
}
// Go to empty hive without other marines in it
vector<AvHAIHiveDefinition*> AllHives = AITAC_GetAllHives();
AvHAIHiveDefinition* NearestEmptyHive = nullptr;
float MinDist = 0.0f;
for (auto it = AllHives.begin(); it != AllHives.end(); it++)
{
AvHAIHiveDefinition* ThisHive = (*it);
if (ThisHive->Status != HIVE_STATUS_UNBUILT) { continue; }
int NumMarinesSecuring = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), ThisHive->Location, UTIL_MetresToGoldSrcUnits(15.0f), false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER);
if (NumMarinesSecuring < 2)
{
float ThisDist = vDist2DSq(ThisHive->Location, pBot->Edict->v.origin);
if (!NearestEmptyHive || ThisDist < MinDist)
{
NearestEmptyHive = ThisHive;
MinDist = ThisDist;
}
}
}
if (NearestEmptyHive)
{
AITASK_SetSecureHiveTask(pBot, Task, NearestEmptyHive->HiveEntity->edict(), NearestEmptyHive->FloorLocation, false);
return;
}
// Go to a good siege location if phase gates available
if (AITAC_PhaseGatesAvailable(pBot->Player->GetTeam()))
{
const AvHAIHiveDefinition* ActiveHive = AITAC_GetActiveHiveNearestLocation(pBot->Edict->v.origin);
if (ActiveHive)
{
if (Task->TaskType != TASK_MOVE)
{
AITASK_SetMoveTask(pBot, Task, UTIL_GetRandomPointOnNavmeshInDonut(pBot->BotNavInfo.NavProfile, ActiveHive->FloorLocation, UTIL_MetresToGoldSrcUnits(10.0f), UTIL_MetresToGoldSrcUnits(20.0f)), false);
}
return;
}
}
}
void AIPlayerSetMarineBombardierPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
{
// Go attack sieged hive
// Go clear res nodes
}
void AIPlayerSetSecondaryMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
{
// Find any nearby unbuilt structures
DeployableSearchFilter UnbuiltFilter;
UnbuiltFilter.DeployableTypes = SEARCH_ALL_MARINE_STRUCTURES;
UnbuiltFilter.DeployableTeam = pBot->Player->GetTeam();
UnbuiltFilter.ReachabilityTeam = pBot->Player->GetTeam();
UnbuiltFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
UnbuiltFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING | STRUCTURE_STATUS_COMPLETED;
UnbuiltFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(20.0f);
vector <AvHAIBuildableStructure*> BuildableStructures = AITAC_FindAllDeployables(pBot->Edict->v.origin, &UnbuiltFilter);
AvHAIBuildableStructure* NearestStructure = nullptr;
float MinDist = 0.0f;
for (auto it = BuildableStructures.begin(); it != BuildableStructures.end(); it++)
{
int NumBuilders = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), (*it)->Location, UTIL_MetresToGoldSrcUnits(5.0f), false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER);
int NumDesiredBuilders = (vDist2DSq((*it)->Location, AITAC_GetCommChairLocation(pBot->Player->GetTeam())) < sqrf(UTIL_MetresToGoldSrcUnits(15.0f))) ? 1 : 2;
if (NumBuilders < NumDesiredBuilders)
{
float ThisDist = vDist2DSq((*it)->Location, pBot->Edict->v.origin);
if (!NearestStructure || ThisDist < MinDist)
{
NearestStructure = (*it);
MinDist = ThisDist;
}
}
}
if (NearestStructure)
{
AITASK_SetBuildTask(pBot, Task, NearestStructure->edict, false);
return;
}
}
void AIPlayerNSAlienThink(AvHAIPlayer* pBot)
{
UpdateAIAlienPlayerNSRole(pBot);
}
void AIPlayerCOThink(AvHAIPlayer* pBot)
{
}
void AIPlayerDMThink(AvHAIPlayer* pBot)
{
}
void AIPlayerThink(AvHAIPlayer* pBot)
{
switch (GetGameRules()->GetMapMode())
{
case MAP_MODE_NS:
AIPlayerNSThink(pBot);
break;
case MAP_MODE_CO:
AIPlayerCOThink(pBot);
break;
default:
AIPlayerDMThink(pBot);
break;
}
AvHAIWeapon DesiredWeapon = (pBot->DesiredMoveWeapon != WEAPON_NONE) ? pBot->DesiredMoveWeapon : pBot->DesiredCombatWeapon;
if (DesiredWeapon != WEAPON_NONE && GetPlayerCurrentWeapon(pBot->Player) != DesiredWeapon)
{
BotSwitchToWeapon(pBot, DesiredWeapon);
}
}
void TestNavThink(AvHAIPlayer* pBot)
{
AITASK_BotUpdateAndClearTasks(pBot);
@ -1579,10 +2121,10 @@ void UpdateCommanderOrders(AvHAIPlayer* pBot)
switch (it->GetOrderType())
{
case ORDERTYPEL_MOVE:
AITASK_SetMoveTask(pBot, &pBot->CommanderTask, OrderLocation, true);
AIPlayerReceiveMoveOrder(pBot, OrderLocation);
break;
case ORDERTYPET_BUILD:
AITASK_SetBuildTask(pBot, &pBot->CommanderTask, INDEXENT(it->GetTargetIndex()), true);
AIPlayerReceiveBuildOrder(pBot, INDEXENT(it->GetTargetIndex()));
break;
default:
break;
@ -1591,6 +2133,47 @@ void UpdateCommanderOrders(AvHAIPlayer* pBot)
}
}
void AIPlayerReceiveBuildOrder(AvHAIPlayer* pBot, edict_t* BuildTarget)
{
AITASK_SetBuildTask(pBot, &pBot->CommanderTask, BuildTarget, true);
}
void AIPlayerReceiveMoveOrder(AvHAIPlayer* pBot, Vector Destination)
{
const AvHAIResourceNode* ResNodeRef = AITAC_GetNearestResourceNodeToLocation(Destination);
// We've been asked to go to a resource node if the movement order is near it
if (ResNodeRef && vDist2DSq(ResNodeRef->Location, Destination) < sqrf(UTIL_MetresToGoldSrcUnits(5.0f)))
{
// If this resource node doesn't belong to us, or the tower isn't fully built, interpret the order as a "cap this node" order
if (ResNodeRef->OwningTeam != pBot->Player->GetTeam() || FNullEnt(ResNodeRef->ActiveTowerEntity) || !UTIL_StructureIsFullyBuilt(ResNodeRef->ActiveTowerEntity))
{
AITASK_SetCapResNodeTask(pBot, &pBot->CommanderTask, ResNodeRef, false);
pBot->CommanderTask.bIssuedByCommander = true;
return;
}
}
const AvHAIHiveDefinition* HiveRef = AITAC_GetHiveNearestLocation(Destination);
// Have we been asked to go to an empty hive? If so, then treat the order as a "help secure this hive" command
if (HiveRef && HiveRef->Status == HIVE_STATUS_UNBUILT && vDist2DSq(HiveRef->Location, Destination) < sqrf(UTIL_MetresToGoldSrcUnits(15.0f)))
{
if (!AICOMM_IsHiveFullySecured(pBot, HiveRef, false))
{
AITASK_SetSecureHiveTask(pBot, &pBot->CommanderTask, HiveRef->HiveEntity->edict(), Destination, false);
pBot->CommanderTask.bIssuedByCommander = true;
return;
}
}
// Otherwise, treat as a normal move order. Go there and wait a bit to see what the commander wants to do next
AITASK_SetMoveTask(pBot, &pBot->CommanderTask, Destination, true);
pBot->CommanderTask.bIssuedByCommander = true;
}
void BotStopCommanderMode(AvHAIPlayer* pBot)
{
// Thanks EterniumDev (Alien) for logic to allow commander AI to leave the chair and build structures when needed

View file

@ -53,10 +53,29 @@ void UpdateBotChat(AvHAIPlayer* pBot);
void ClearBotInputs(AvHAIPlayer* pBot);
void StartNewBotFrame(AvHAIPlayer* pBot);
void AIPlayerThink(AvHAIPlayer* pBot);
// Think routine for regular NS game mode
void AIPlayerNSThink(AvHAIPlayer* pBot);
void AIPlayerNSMarineThink(AvHAIPlayer* pBot);
void AIPlayerNSAlienThink(AvHAIPlayer* pBot);
// Think routine for the combat game mode
void AIPlayerCOThink(AvHAIPlayer* pBot);
// Think routine for the deathmatch game mode (e.g. when playing CS maps)
void AIPlayerDMThink(AvHAIPlayer* pBot);
void TestNavThink(AvHAIPlayer* pBot);
void DroneThink(AvHAIPlayer* pBot);
void CustomThink(AvHAIPlayer* pBot);
AvHAIPlayerTask* AIPlayerGetNextTask(AvHAIPlayer* pBot);
void AIPlayerSetPrimaryMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
void AIPlayerSetMarineSweeperPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
void AIPlayerSetMarineCapperPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
void AIPlayerSetMarineAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
void AIPlayerSetMarineBombardierPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
void AIPlayerSetSecondaryMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
void BotSwitchToWeapon(AvHAIPlayer* pBot, AvHAIWeapon NewWeaponSlot);
bool ShouldBotThink(AvHAIPlayer* pBot);
@ -64,7 +83,17 @@ bool ShouldBotThink(AvHAIPlayer* pBot);
void BotResumePlay(AvHAIPlayer* pBot);
void UpdateCommanderOrders(AvHAIPlayer* pBot);
void AIPlayerReceiveMoveOrder(AvHAIPlayer* pBot, Vector Destination);
void AIPlayerReceiveBuildOrder(AvHAIPlayer* pBot, edict_t* BuildTarget);
void BotStopCommanderMode(AvHAIPlayer* pBot);
void SetNewAIPlayerRole(AvHAIPlayer* pBot, AvHAIBotRole NewRole);
void UpdateAIMarinePlayerNSRole(AvHAIPlayer* pBot);
void UpdateAIAlienPlayerNSRole(AvHAIPlayer* pBot);
void UpdateAIPlayerCORole(AvHAIPlayer* pBot);
void UpdateAIPlayerDMRole(AvHAIPlayer* pBot);
bool ShouldAIPlayerTakeCommand(AvHAIPlayer* pBot);
#endif

View file

@ -68,6 +68,21 @@ string BotNames[MAX_PLAYERS] = { "MrRobot",
"TerminalFerocity"
};
AvHAICommanderMode AIMGR_GetCommanderMode()
{
if (avh_botcommandermode.value == 0)
{
return COMMANDERMODE_DISABLED;
}
if (avh_botcommandermode.value == 1)
{
return COMMANDERMODE_IFNOHUMAN;
}
return COMMANDERMODE_ENABLED;
}
void AIMGR_UpdateAIPlayerCounts()
{
@ -472,7 +487,10 @@ byte BotThrottledMsec(AvHAIPlayer* inAIPlayer)
if (newmsec > 255)
{
newmsec = 255;
}
}
// save the command time
inAIPlayer->f_previous_command_time = gpGlobals->time;
return (byte)newmsec;
}
@ -547,17 +565,10 @@ void AIMGR_UpdateAIPlayers()
UpdateBotChat(bot);
CustomThink(bot);
AIPlayerThink(bot);
AIDEBUG_DrawPath(DebugPath, 0.0f);
AvHAIWeapon DesiredWeapon = (bot->DesiredMoveWeapon != WEAPON_NONE) ? bot->DesiredMoveWeapon : bot->DesiredCombatWeapon;
if (DesiredWeapon != WEAPON_NONE && GetPlayerCurrentWeapon(bot->Player) != DesiredWeapon)
{
BotSwitchToWeapon(bot, DesiredWeapon);
}
BotUpdateDesiredViewRotation(bot);
}
else
@ -567,10 +578,7 @@ void AIMGR_UpdateAIPlayers()
}
// Needed to correctly handle client prediction and physics calculations
byte adjustedmsec = BotThrottledMsec(bot);
// save the command time
bot->f_previous_command_time = gpGlobals->time;
byte adjustedmsec = BotThrottledMsec(bot);
// Simulate PM_PlayerMove so client prediction and stuff can be executed correctly.
RUN_AI_MOVE(bot->Edict, bot->Edict->v.v_angle, bot->ForwardMove,
@ -596,6 +604,29 @@ int AIMGR_GetNumAIPlayers()
return ActiveAIPlayers.size();
}
vector<AvHPlayer*> AIMGR_GetAllPlayersOnTeam(AvHTeamNumber Team)
{
vector<AvHPlayer*> Result;
for (int i = 1; i <= gpGlobals->maxClients; i++)
{
edict_t* PlayerEdict = INDEXENT(i);
if (!FNullEnt(PlayerEdict) && PlayerEdict->v.team == Team)
{
AvHPlayer* PlayerRef = dynamic_cast<AvHPlayer*>(CBaseEntity::Instance(PlayerEdict));
if (PlayerRef)
{
Result.push_back(PlayerRef);
}
}
}
return Result;
}
int AIMGR_GetNumAIPlayersOnTeam(AvHTeamNumber Team)
{
int Result = 0;
@ -611,6 +642,46 @@ int AIMGR_GetNumAIPlayersOnTeam(AvHTeamNumber Team)
return Result;
}
int AIMGR_GetNumHumanPlayersOnTeam(AvHTeamNumber Team)
{
int Result = 0;
vector<AvHPlayer*> TeamPlayers = AIMGR_GetAllPlayersOnTeam(Team);
for (auto it = TeamPlayers.begin(); it != TeamPlayers.end(); it++)
{
AvHPlayer* ThisPlayer = (*it);
edict_t* PlayerEdict = ThisPlayer->edict();
if (!(PlayerEdict->v.flags & FL_FAKECLIENT))
{
Result++;
}
}
return Result;
}
int AIMGR_GetNumAIPlayersWithRoleOnTeam(AvHTeamNumber Team, AvHAIBotRole Role, AvHAIPlayer* IgnoreAIPlayer)
{
int Result = 0;
for (auto it = ActiveAIPlayers.begin(); it != ActiveAIPlayers.end(); it++)
{
if (&(*it) == IgnoreAIPlayer) { continue; }
if (it->Player->GetTeam() == Team)
{
if (it->BotRole == Role)
{
Result++;
}
}
}
return Result;
}
int AIMGR_AIPlayerExistsOnTeam(AvHTeamNumber Team)
{
for (auto it = ActiveAIPlayers.begin(); it != ActiveAIPlayers.end(); it++)
@ -662,11 +733,15 @@ void AIMGR_ResetRound()
void AIMGR_RoundStarted()
{
AITAC_PopulateResourceNodes();
AITAC_PopulateHiveData();
AITAC_RefreshResourceNodes();
AITAC_RefreshHiveData();
UTIL_UpdateTileCache();
AITAC_RefreshResourceNodes();
}
void AIMGR_ClearBotData()

View file

@ -36,6 +36,8 @@ void AIMGR_UpdateTeamBalance();
// Called by UpdateAIPlayerCounts. If auto-mode is fill teams, will add/remove bots needed to maintain minimum player counts and balance
void AIMGR_UpdateFillTeams();
vector<AvHPlayer*> AIMGR_GetAllPlayersOnTeam(AvHTeamNumber Team);
// How many AI players are in the game (does not include third-party bots like RCBot/Whichbot)
int AIMGR_GetNumAIPlayers();
// Returns true if an AI player is on the requested team (does NOT include third-party bots like RCBot/Whichbot)
@ -43,11 +45,16 @@ int AIMGR_AIPlayerExistsOnTeam(AvHTeamNumber Team);
void AIMGR_UpdateAIMapData();
AvHAICommanderMode AIMGR_GetCommanderMode();
void AIDEBUG_SetDebugVector1(const Vector NewVector);
void AIDEBUG_SetDebugVector2(const Vector NewVector);
void AIDEBUG_TestPathFind();
int AIMGR_GetNumAIPlayersOnTeam(AvHTeamNumber Team);
int AIMGR_GetNumHumanPlayersOnTeam(AvHTeamNumber Team);
int AIMGR_GetNumAIPlayersWithRoleOnTeam(AvHTeamNumber Team, AvHAIBotRole Role, AvHAIPlayer* IgnoreAIPlayer);
AvHAIPlayer* AIMGR_GetAICommander(AvHTeamNumber Team);

View file

@ -653,22 +653,28 @@ Vector AITAC_GetFloorLocationForHive(const AvHAIHiveDefinition* Hive)
}
}
void AITAC_PopulateHiveData()
{
Hives.clear();
FOR_ALL_ENTITIES(kesTeamHive, AvHHive*)
AvHAIHiveDefinition NewHive;
NewHive.HiveEntity = theEntity;
NewHive.Location = theEntity->pev->origin;
NewHive.HiveResNodeRef = AITAC_GetNearestResourceNodeToLocation(theEntity->pev->origin);
NewHive.FloorLocation = UTIL_GetFloorUnderEntity(theEntity->edict()); // Some hives are suspended in the air, this is the floor location directly beneath it
Hives.push_back(NewHive);
END_FOR_ALL_ENTITIES(kesTeamHive)
}
void AITAC_RefreshHiveData()
{
if (Hives.size() == 0)
{
FOR_ALL_ENTITIES(kesTeamHive, AvHHive*)
AvHAIHiveDefinition NewHive;
NewHive.HiveEntity = theEntity;
NewHive.Location = theEntity->pev->origin;
NewHive.HiveResNodeRef = AITAC_GetNearestResourceNodeToLocation(theEntity->pev->origin);
NewHive.FloorLocation = UTIL_GetFloorUnderEntity(theEntity->edict()); // Some hives are suspended in the air, this is the floor location directly beneath it
Hives.push_back(NewHive);
END_FOR_ALL_ENTITIES(kesTeamHive)
AITAC_PopulateHiveData();
}
int NextRefresh = 0;
@ -1024,23 +1030,30 @@ void AITAC_RefreshReachabilityForResNode(AvHAIResourceNode* ResNode)
}
void AITAC_PopulateResourceNodes()
{
ResourceNodes.clear();
FOR_ALL_ENTITIES(kesFuncResource, AvHFuncResource*)
AvHAIResourceNode NewResNode;
NewResNode.ResourceEntity = theEntity;
NewResNode.Location = theEntity->pev->origin;
NewResNode.TeamAReachabilityFlags = AI_REACHABILITY_NONE;
NewResNode.TeamBReachabilityFlags = AI_REACHABILITY_NONE;
NewResNode.bReachabilityMarkedDirty = true;
NewResNode.NextReachabilityRefreshTime = 0.0f;
ResourceNodes.push_back(NewResNode);
END_FOR_ALL_ENTITIES(kesFuncResource)
}
void AITAC_RefreshResourceNodes()
{
if (ResourceNodes.size() == 0)
{
FOR_ALL_ENTITIES(kesFuncResource, AvHFuncResource*)
AvHAIResourceNode NewResNode;
NewResNode.ResourceEntity = theEntity;
NewResNode.Location = theEntity->pev->origin;
NewResNode.TeamAReachabilityFlags = AI_REACHABILITY_NONE;
NewResNode.TeamBReachabilityFlags = AI_REACHABILITY_NONE;
NewResNode.bReachabilityMarkedDirty = true;
NewResNode.NextReachabilityRefreshTime = 0.0f;
ResourceNodes.push_back(NewResNode);
END_FOR_ALL_ENTITIES(kesFuncResource)
AITAC_PopulateResourceNodes();
}
for (auto it = ResourceNodes.begin(); it != ResourceNodes.end(); it++)
@ -2259,6 +2272,19 @@ AvHAIHiveDefinition* AITAC_GetHiveFromEdict(const edict_t* Edict)
return nullptr;
}
AvHAIResourceNode* AITAC_GetResourceNodeFromEdict(const edict_t* Edict)
{
for (auto it = ResourceNodes.begin(); it != ResourceNodes.end(); it++)
{
if (it->ResourceEntity->edict() == Edict)
{
return &(*it);
}
}
return nullptr;
}
const AvHAIHiveDefinition* AITAC_GetHiveNearestLocation(const Vector SearchLocation)
{
AvHAIHiveDefinition* Result = nullptr;
@ -2339,6 +2365,116 @@ AvHAIResourceNode* AITAC_GetNearestResourceNodeToLocation(const Vector Location)
return Result;
}
float AITAC_GetTeamResNodeOwnership(const AvHTeamNumber Team)
{
int NumViableResNodes = 0;
int NumOwnedResNodes = 0;
for (auto it = ResourceNodes.begin(); it != ResourceNodes.end(); it++)
{
unsigned int CheckReachabilityFlags = (it->TeamAReachabilityFlags | it->TeamBReachabilityFlags);
if (Team != TEAM_IND)
{
CheckReachabilityFlags = (Team == GetGameRules()->GetTeamANumber()) ? it->TeamAReachabilityFlags : it->TeamBReachabilityFlags;
}
if (CheckReachabilityFlags == AI_REACHABILITY_UNREACHABLE) { continue; }
NumViableResNodes++;
if (it->OwningTeam == Team)
{
NumOwnedResNodes++;
}
}
// If there are no viable resource nodes, then report we own them all to avoid divide by zero
if (NumViableResNodes == 0) { return 1.0f; }
return (float)NumOwnedResNodes / (float)NumViableResNodes;
}
int AITAC_GetNumResourceNodesNearLocation(const Vector Location, const DeployableSearchFilter* Filter)
{
int Result = 0;
float MinDistSq = sqrf(Filter->MinSearchRadius);
float MaxDistSq = sqrf(Filter->MaxSearchRadius);
bool bUseMinDist = MinDistSq > 0.1f;
bool bUseMaxDist = MaxDistSq > 0.1f;
float CurrMinDist = 0;
for (auto it = ResourceNodes.begin(); it != ResourceNodes.end(); it++)
{
if (Filter->ReachabilityFlags != AI_REACHABILITY_NONE)
{
unsigned int CheckReachabilityFlags = (it->TeamAReachabilityFlags | it->TeamBReachabilityFlags);
if (Filter->ReachabilityTeam != TEAM_IND)
{
CheckReachabilityFlags = (Filter->ReachabilityTeam == GetGameRules()->GetTeamANumber()) ? it->TeamAReachabilityFlags : it->TeamBReachabilityFlags;
}
if (!(CheckReachabilityFlags & Filter->ReachabilityFlags)) { continue; }
}
if (it->OwningTeam != Filter->DeployableTeam) { continue; }
float DistSq = (Filter->bConsiderPhaseDistance) ? sqrf(AITAC_GetPhaseDistanceBetweenPoints(it->Location, Location)) : vDist2DSq(it->Location, Location);
if ((!bUseMinDist || DistSq >= MinDistSq) && (!bUseMaxDist || DistSq <= MaxDistSq) && (!Result || DistSq < CurrMinDist))
{
Result++;
}
}
return Result;
}
vector<AvHAIResourceNode*> AITAC_GetAllMatchingResourceNodes(const Vector Location, const DeployableSearchFilter* Filter)
{
vector<AvHAIResourceNode*> Results;
float MinDistSq = sqrf(Filter->MinSearchRadius);
float MaxDistSq = sqrf(Filter->MaxSearchRadius);
bool bUseMinDist = MinDistSq > 0.1f;
bool bUseMaxDist = MaxDistSq > 0.1f;
float CurrMinDist = 0;
for (auto it = ResourceNodes.begin(); it != ResourceNodes.end(); it++)
{
if (Filter->ReachabilityFlags != AI_REACHABILITY_NONE)
{
unsigned int CheckReachabilityFlags = (it->TeamAReachabilityFlags | it->TeamBReachabilityFlags);
if (Filter->ReachabilityTeam != TEAM_IND)
{
CheckReachabilityFlags = (Filter->ReachabilityTeam == GetGameRules()->GetTeamANumber()) ? it->TeamAReachabilityFlags : it->TeamBReachabilityFlags;
}
if (!(CheckReachabilityFlags & Filter->ReachabilityFlags)) { continue; }
}
if (it->OwningTeam != Filter->DeployableTeam) { continue; }
float DistSq = (Filter->bConsiderPhaseDistance) ? sqrf(AITAC_GetPhaseDistanceBetweenPoints(it->Location, Location)) : vDist2DSq(it->Location, Location);
if ((!bUseMinDist || DistSq >= MinDistSq) && (!bUseMaxDist || DistSq <= MaxDistSq))
{
Results.push_back(&(*it));
}
}
return Results;
}
AvHAIResourceNode* AITAC_FindNearestResourceNodeToLocation(const Vector Location, const DeployableSearchFilter* Filter)
{
AvHAIResourceNode* Result = nullptr;
@ -2380,6 +2516,22 @@ AvHAIResourceNode* AITAC_FindNearestResourceNodeToLocation(const Vector Location
}
int AITAC_GetNumActivePlayersOnTeam(const AvHTeamNumber Team)
{
int Result = 0;
for (int i = 1; i <= gpGlobals->maxClients; i++)
{
edict_t* PlayerEdict = INDEXENT(i);
if (!FNullEnt(PlayerEdict) && !PlayerEdict->free && IsPlayerActiveInGame(PlayerEdict)) { Result++; }
}
return Result;
}
int AITAC_GetNumPlayersOfTeamInArea(const AvHTeamNumber Team, const Vector SearchLocation, const float SearchRadius, const bool bConsiderPhaseDist, const edict_t* IgnorePlayer, const AvHUser3 IgnoreClass)
{
int Result = 0;
@ -2996,7 +3148,7 @@ const AvHAIHiveDefinition* AITAC_GetNearestHiveUnderActiveSiege(AvHTeamNumber Si
DeployableSearchFilter SiegeFilter;
SiegeFilter.DeployableTypes = STRUCTURE_MARINE_ADVTURRETFACTORY;
SiegeFilter.DeployableTeam = SiegingTeam;
SiegeFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(20.0f);
SiegeFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(25.0f);
SiegeFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
SiegeFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
@ -3031,7 +3183,7 @@ edict_t* AITAC_GetMarineEligibleToBuildSiege(AvHTeamNumber Team, const AvHAIHive
edict_t* Result = nullptr;
vector<AvHPlayer*> TeamPlayers = AITAC_GetAllPlayersOnTeam(Team);
vector<AvHPlayer*> TeamPlayers = AIMGR_GetAllPlayersOnTeam(Team);
float MinDist = 0.0f;
@ -3062,7 +3214,7 @@ edict_t* AITAC_GetNearestHiddenPlayerInLocation(AvHTeamNumber Team, const Vector
edict_t* Result = nullptr;
float MaxRadiusSq = sqrf(MaxRadius);
vector<AvHPlayer*> TeamPlayers = AITAC_GetAllPlayersOnTeam(Team);
vector<AvHPlayer*> TeamPlayers = AIMGR_GetAllPlayersOnTeam(Team);
float MinDist = 0.0f;
@ -3088,28 +3240,7 @@ edict_t* AITAC_GetNearestHiddenPlayerInLocation(AvHTeamNumber Team, const Vector
return Result;
}
vector<AvHPlayer*> AITAC_GetAllPlayersOnTeam(AvHTeamNumber Team)
{
vector<AvHPlayer*> Result;
for (int i = 1; i <= gpGlobals->maxClients; i++)
{
edict_t* PlayerEdict = INDEXENT(i);
if (!FNullEnt(PlayerEdict) && PlayerEdict->v.team == Team)
{
AvHPlayer* PlayerRef = dynamic_cast<AvHPlayer*>(CBaseEntity::Instance(PlayerEdict));
if (PlayerRef)
{
Result.push_back(PlayerRef);
}
}
}
return Result;
}
const vector<AvHAIResourceNode*> AITAC_GetAllResourceNodes()
{
@ -3139,7 +3270,7 @@ bool AITAC_AnyPlayerOnTeamWithLOS(AvHTeamNumber Team, const Vector& Location, fl
{
float distSq = sqrf(SearchRadius);
vector<AvHPlayer*> Players = AITAC_GetAllPlayersOnTeam(Team);
vector<AvHPlayer*> Players = AIMGR_GetAllPlayersOnTeam(Team);
for (auto it = Players.begin(); it != Players.end(); it++)
{

View file

@ -27,7 +27,9 @@ AvHAIBuildableStructure* AITAC_FindFurthestDeployableFromLocation(const Vector&
AvHAIBuildableStructure* AITAC_GetDeployableRefFromEdict(const edict_t* Structure);
AvHAIBuildableStructure* AITAC_GetNearestDeployableDirectlyReachable(AvHAIPlayer* pBot, const Vector Location, const DeployableSearchFilter* Filter);
int AITAC_GetNumDeployablesNearLocation(const Vector& Location, const DeployableSearchFilter* Filter);
void AITAC_PopulateHiveData();
void AITAC_RefreshHiveData();
void AITAC_PopulateResourceNodes();
void AITAC_RefreshResourceNodes();
void AITAC_UpdateMapAIData();
void AITAC_CheckNavMeshModified();
@ -102,14 +104,20 @@ bool UTIL_StructureIsRecycling(edict_t* Structure);
bool AITAC_StructureCanBeUpgraded(edict_t* Structure);
AvHAIHiveDefinition* AITAC_GetHiveFromEdict(const edict_t* Edict);
AvHAIResourceNode* AITAC_GetResourceNodeFromEdict(const edict_t* Edict);
// What percentage of all viable (can be reached by the requested team) resource nodes does the team currently own? Expressed as 0.0 - 1.0
float AITAC_GetTeamResNodeOwnership(const AvHTeamNumber Team);
int AITAC_GetNumResourceNodesNearLocation(const Vector Location, const DeployableSearchFilter* Filter);
AvHAIResourceNode* AITAC_FindNearestResourceNodeToLocation(const Vector Location, const DeployableSearchFilter* Filter);
AvHAIResourceNode* AITAC_GetNearestResourceNodeToLocation(const Vector Location);
vector<AvHAIResourceNode*> AITAC_GetAllMatchingResourceNodes(const Vector Location, const DeployableSearchFilter* Filter);
bool UTIL_IsBuildableStructureStillReachable(AvHAIPlayer* pBot, const edict_t* Structure);
bool UTIL_IsDroppedItemStillReachable(AvHAIPlayer* pBot, const edict_t* Item);
AvHAIWeapon UTIL_GetWeaponTypeFromEdict(const edict_t* ItemEdict);
int AITAC_GetNumActivePlayersOnTeam(const AvHTeamNumber Team);
int AITAC_GetNumPlayersOfTeamInArea(const AvHTeamNumber Team, const Vector SearchLocation, const float SearchRadius, const bool bConsiderPhaseDist, const edict_t* IgnorePlayer, const AvHUser3 IgnoreClass);
int AITAC_GetNumPlayersOnTeamOfClass(const AvHTeamNumber Team, const AvHUser3 SearchClass, const edict_t* IgnorePlayer);
edict_t* AITAC_GetNearestPlayerOfClassInArea(const AvHTeamNumber Team, const Vector SearchLocation, const float SearchRadius, const bool bConsiderPhaseDist, const edict_t* IgnorePlayer, const AvHUser3 SearchClass);
@ -143,7 +151,6 @@ int AITAC_GetNumDeadPlayersOnTeam(const AvHTeamNumber Team);
const AvHAIHiveDefinition* AITAC_GetNearestHiveUnderActiveSiege(AvHTeamNumber SiegingTeam, const Vector SearchLocation);
edict_t* AITAC_GetMarineEligibleToBuildSiege(AvHTeamNumber Team, const AvHAIHiveDefinition* Hive);
vector<AvHPlayer*> AITAC_GetAllPlayersOnTeam(AvHTeamNumber Team);
edict_t* AITAC_GetNearestHiddenPlayerInLocation(AvHTeamNumber Team, const Vector Location, const float MaxRadius);
const vector<AvHAIResourceNode*> AITAC_GetAllResourceNodes();

View file

@ -332,6 +332,17 @@ bool AITASK_IsTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
}
case TASK_REINFORCE_STRUCTURE:
return AITASK_IsReinforceStructureTaskStillValid(pBot, Task);
case TASK_SECURE_HIVE:
{
if (IsPlayerMarine(pBot->Edict))
{
return AITASK_IsMarineSecureHiveTaskStillValid(pBot, Task);
}
else
{
return false;
}
}
case TASK_DEFEND:
return AITASK_IsDefendTaskStillValid(pBot, Task);
case TASK_WELD:
@ -548,11 +559,15 @@ bool AITASK_IsMarineBuildTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task
return false;
}
// Always go build if commanded to, regardless of how many are already working on it
if (!Task->bIssuedByCommander)
{
int NumBuilders = AITAC_GetNumPlayersOfTeamInArea((AvHTeamNumber)pBot->Edict->v.team, Task->TaskTarget->v.origin, UTIL_MetresToGoldSrcUnits(2.0f), false, pBot->Edict, AVH_USER3_NONE);
// Only one marine should build stuff if it's near the marine base. If not, then two for safety
int NumDesiredBuilders = (vDist2DSq(Task->TaskTarget->v.origin, AITAC_GetCommChairLocation(pBot->Player->GetTeam())) < sqrf(UTIL_MetresToGoldSrcUnits(15.0f))) ? 1 : 2;
if (NumBuilders >= 2)
if (NumBuilders >= NumDesiredBuilders)
{
return false;
}
@ -764,20 +779,17 @@ bool AITASK_IsMarineCapResNodeTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask*
// Always obey commander orders even if there's a bunch of other marines already there
if (!Task->bIssuedByCommander)
{
int NumMarinesNearby = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), Task->TaskLocation, UTIL_MetresToGoldSrcUnits(4.0f), false, pBot->Edict, AVH_USER3_NONE);
int DesiredNumCappers = (ResNodeIndex->OwningTeam == AIMGR_GetEnemyTeam(pBot->Player->GetTeam())) ? 2 : 1;
int NumMarinesNearby = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), Task->TaskLocation, UTIL_MetresToGoldSrcUnits(4.0f), false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER);
if (NumMarinesNearby >= 2 && vDist2DSq(pBot->Edict->v.origin, Task->TaskLocation) > sqrf(UTIL_MetresToGoldSrcUnits(4.0f))) { return false; }
if (NumMarinesNearby >= DesiredNumCappers && vDist2DSq(pBot->Edict->v.origin, Task->TaskLocation) > sqrf(UTIL_MetresToGoldSrcUnits(4.0f))) { return false; }
}
if (ResNodeIndex->bIsOccupied)
{
if (ResNodeIndex->OwningTeam == pBot->Player->GetTeam() && !FNullEnt(ResNodeIndex->ActiveTowerEntity))
if (ResNodeIndex->OwningTeam == pBot->Player->GetTeam())
{
return !UTIL_StructureIsFullyBuilt(ResNodeIndex->ActiveTowerEntity);
}
else
{
return true;
return (FNullEnt(ResNodeIndex->ActiveTowerEntity) || !UTIL_StructureIsFullyBuilt(ResNodeIndex->ActiveTowerEntity));
}
}
@ -876,6 +888,67 @@ bool AITASK_IsReinforceStructureTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTas
return false;
}
bool AITASK_IsMarineSecureHiveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
{
if (!Task || FNullEnt(Task->TaskTarget) || IsPlayerAlien(pBot->Edict)) { return false; }
AvHAIHiveDefinition* HiveToSecure = AITAC_GetHiveFromEdict(Task->TaskTarget);
if (!HiveToSecure || HiveToSecure->Status != HIVE_STATUS_UNBUILT) { return false; }
// A marine bot will consider their "secure hive" task completed if the following structures have been fully built:
// Phase gate (only if tech available)
// Turret factory (regular or advanced)
// 5 turrets
// Resource node has been capped by the bot's team
AvHTeamNumber BotTeam = pBot->Player->GetTeam();
bool bPhaseGatesAvailable = AITAC_PhaseGatesAvailable(BotTeam);
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 = BotTeam;
SearchFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
SearchFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
SearchFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f);
vector<AvHAIBuildableStructure*> HiveStructures = AITAC_FindAllDeployables(HiveToSecure->FloorLocation, &SearchFilter);
for (auto it = HiveStructures.begin(); it != HiveStructures.end(); it++)
{
AvHAIBuildableStructure* Structure = (*it);
if (Structure->StructureType == STRUCTURE_MARINE_TURRETFACTORY)
{
bHasPhaseGate = true;
}
if (Structure->StructureType == STRUCTURE_MARINE_TURRETFACTORY || Structure->StructureType == STRUCTURE_MARINE_ADVTURRETFACTORY)
{
bHasTurretFactory = true;
SearchFilter.DeployableTypes = STRUCTURE_MARINE_TURRET;
SearchFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(8.0f);
NumTurrets = AITAC_GetNumDeployablesNearLocation(Structure->Location, &SearchFilter);
}
}
const AvHAIResourceNode* ResNode = HiveToSecure->HiveResNodeRef;
bool bSecuredResNode = (!ResNode || (ResNode->OwningTeam == BotTeam && !FNullEnt(ResNode->ActiveTowerEntity) && UTIL_StructureIsFullyBuilt(ResNode->ActiveTowerEntity)));
return !((!bPhaseGatesAvailable || bHasPhaseGate) && bHasTurretFactory && NumTurrets >= 5 && bSecuredResNode);
}
bool AITASK_IsEvolveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
{
if (!Task || Task->Evolution == MESSAGE_NULL || !IsPlayerAlien(pBot->Edict)) { return false; }
@ -1438,6 +1511,88 @@ void BotProgressResupplyTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
}
void AIPlayerBuildStructure(AvHAIPlayer* pBot, edict_t* BuildTarget)
{
if (!pBot || !IsPlayerActiveInGame(pBot->Edict) || FNullEnt(BuildTarget) || UTIL_StructureIsFullyBuilt(BuildTarget)) { return; }
if (IsPlayerAlien(pBot->Edict) && !IsPlayerGorge(pBot->Edict)) { return; }
if (IsPlayerMarine(pBot->Edict))
{
// If we're not already building
if (pBot->Edict->v.viewmodel != 0)
{
// If someone else is building, then we will guard
edict_t* OtherBuilder = AITAC_GetClosestPlayerOnTeamWithLOS(pBot->Player->GetTeam(), BuildTarget->v.origin, UTIL_MetresToGoldSrcUnits(2.0f), pBot->Edict);
if (!FNullEnt(OtherBuilder) && OtherBuilder->v.weaponmodel == 0)
{
BotGuardLocation(pBot, BuildTarget->v.origin);
return;
}
}
}
if (IsPlayerInUseRange(pBot->Edict, BuildTarget))
{
// If we were ducking before then keep ducking
if (pBot->Edict->v.oldbuttons & IN_DUCK)
{
pBot->Button |= IN_DUCK;
}
BotUseObject(pBot, BuildTarget, true);
// Haven't started building, maybe not quite looking at the right angle
if (pBot->Edict->v.weaponmodel != 0)
{
if (vDist2DSq(pBot->Edict->v.origin, BuildTarget->v.origin) > sqrf(60.0f))
{
MoveDirectlyTo(pBot, BuildTarget->v.origin);
}
else
{
Vector NewViewPoint = UTIL_GetRandomPointInBoundingBox(BuildTarget->v.absmin, BuildTarget->v.absmax);
BotLookAt(pBot, NewViewPoint);
}
}
return;
}
else
{
// Might need to duck if it's an infantry portal
if (vDist2DSq(pBot->Edict->v.origin, BuildTarget->v.origin) < sqrf(max_player_use_reach))
{
if (BuildTarget->v.origin > pBot->Edict->v.origin)
{
BotJump(pBot);
}
else
{
pBot->Button |= IN_DUCK;
}
}
}
MoveTo(pBot, BuildTarget->v.origin, MOVESTYLE_NORMAL);
if (IsPlayerMarine(pBot->Edict))
{
if (gpGlobals->time - pBot->LastCombatTime > 5.0f)
{
BotReloadWeapons(pBot);
}
}
if (vDist2DSq(pBot->Edict->v.origin, BuildTarget->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(5.0f)))
{
BotLookAt(pBot, UTIL_GetCentreOfEntity(BuildTarget));
}
}
void MarineProgressBuildTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
{
edict_t* pEdict = pBot->Edict;
@ -2314,70 +2469,66 @@ void MarineProgressSecureHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
if (!Hive) { return; }
bool bWaitForBuildingPlacement = false;
AvHTeamNumber BotTeam = pBot->Player->GetTeam();
DeployableSearchFilter StructureFilter;
StructureFilter.DeployableTypes = (STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY);
StructureFilter.DeployableTypes = SEARCH_ALL_MARINE_STRUCTURES;
StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f);
StructureFilter.ReachabilityFlags = AI_REACHABILITY_MARINE;
StructureFilter.DeployableTeam = BotTeam;
StructureFilter.ReachabilityTeam = BotTeam;
StructureFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING | STRUCTURE_STATUS_COMPLETED;
AvHAIBuildableStructure* TF = AITAC_FindClosestDeployableToLocation(Hive->FloorLocation, &StructureFilter);
vector<AvHAIBuildableStructure*> BuildableStructures = AITAC_FindAllDeployables(Hive->FloorLocation, &StructureFilter);
if (!TF || !(TF->StructureStatusFlags & STRUCTURE_STATUS_COMPLETED)) { bWaitForBuildingPlacement = true; }
AvHAIBuildableStructure* StructureToBuild = nullptr;
float MinDist = 0.0f;
bool bPhaseGatesAvailable = AITAC_ResearchIsComplete(pBot->Player->GetTeam(), TECH_PHASE_GATE);
if (bPhaseGatesAvailable && !bWaitForBuildingPlacement)
for (auto it = BuildableStructures.begin(); it != BuildableStructures.end(); it++)
{
StructureFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE;
AvHAIBuildableStructure* PhaseGate = AITAC_FindClosestDeployableToLocation(Hive->FloorLocation, &StructureFilter);
if (!PhaseGate || !(TF->StructureStatusFlags & STRUCTURE_STATUS_COMPLETED)) { bWaitForBuildingPlacement = true; }
}
if (!bWaitForBuildingPlacement)
{
StructureFilter.DeployableTypes = STRUCTURE_MARINE_TURRET;
int NumTurrets = AITAC_GetNumDeployablesNearLocation(TF->Location, &StructureFilter);
if (NumTurrets < 5) { bWaitForBuildingPlacement = true; }
}
if (bWaitForBuildingPlacement)
{
if (TF)
{
BotGuardLocation(pBot, TF->Location);
}
else
{
BotGuardLocation(pBot, Task->TaskLocation);
}
AvHAIBuildableStructure* ThisStructure = (*it);
if (ThisStructure->StructureType == STRUCTURE_MARINE_PHASEGATE)
{
AIPlayerBuildStructure(pBot, ThisStructure->edict);
return;
}
float ThisDist = vDist2DSq(pBot->Edict->v.origin, ThisStructure->Location);
if (!StructureToBuild || ThisDist < MinDist)
{
StructureToBuild = ThisStructure;
MinDist = ThisDist;
}
}
if (StructureToBuild)
{
AIPlayerBuildStructure(pBot, StructureToBuild->edict);
return;
}
const AvHAIResourceNode* ResNode = Hive->HiveResNodeRef;
if (ResNode && ResNode->OwningTeam != pBot->Player->GetTeam())
if (ResNode && ResNode->bIsOccupied)
{
if (ResNode->bIsOccupied)
if (ResNode->OwningTeam != BotTeam)
{
BotAttackTarget(pBot, ResNode->ActiveTowerEntity);
return;
}
else
{
BotGuardLocation(pBot, ResNode->Location);
if (!UTIL_StructureIsFullyBuilt(ResNode->ActiveTowerEntity))
{
AIPlayerBuildStructure(pBot, ResNode->ActiveTowerEntity);
return;
}
}
return;
}
BotGuardLocation(pBot, Task->TaskLocation);
}
@ -2413,24 +2564,7 @@ void MarineProgressCapResNodeTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
{
if (!UTIL_StructureIsFullyBuilt(ResNodeIndex->ActiveTowerEntity))
{
// Now we're committed, don't get distracted
Task->bTaskIsUrgent = true;
if (UTIL_PlayerHasLOSToEntity(pBot->Edict, ResNodeIndex->ActiveTowerEntity, max_player_use_reach, true))
{
BotUseObject(pBot, ResNodeIndex->ActiveTowerEntity, true);
if (vDist2DSq(pBot->Edict->v.origin, ResNodeIndex->ActiveTowerEntity->v.origin) > sqrf(50.0f))
{
MoveDirectlyTo(pBot, ResNodeIndex->ActiveTowerEntity->v.origin);
}
return;
}
MoveTo(pBot, ResNodeIndex->ActiveTowerEntity->v.origin, MOVESTYLE_NORMAL);
if (vDist2DSq(pBot->Edict->v.origin, ResNodeIndex->ActiveTowerEntity->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(5.0f)))
{
BotLookAt(pBot, UTIL_GetCentreOfEntity(ResNodeIndex->ActiveTowerEntity));
}
AIPlayerBuildStructure(pBot, ResNodeIndex->ActiveTowerEntity);
return;
}

View file

@ -44,6 +44,7 @@ bool AITASK_IsDefendTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
bool AITASK_IsEvolveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
bool AITASK_IsReinforceStructureTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
bool AITASK_IsMarineSecureHiveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
bool AITASK_IsAlienGetHealthTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
bool AITASK_IsAlienHealTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
@ -87,6 +88,8 @@ void MarineProgressBuildTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
void MarineProgressCapResNodeTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
void BotProgressWeldTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
void AIPlayerBuildStructure(AvHAIPlayer* pBot, edict_t* BuildTarget);
void MarineProgressSecureHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
void AlienProgressGetHealthTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);