Alien assault AI

This commit is contained in:
RGreenlees 2024-01-20 22:43:41 +00:00 committed by pierow
parent ccf180d9a3
commit f31e2bbf1c
3 changed files with 333 additions and 17 deletions

View file

@ -2646,6 +2646,7 @@ void AIPlayerSetAlienBuilderPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task
void AIPlayerSetAlienCapperPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
{
AvHTeamNumber BotTeam = pBot->Player->GetTeam();
AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam);
AvHAIResourceNode* NodeToCap = nullptr;
@ -2675,7 +2676,7 @@ void AIPlayerSetAlienCapperPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
vector<AvHAIResourceNode*> EligibleNodes = AITAC_GetAllMatchingResourceNodes(pBot->Edict->v.origin, &EmptyNodeFilter);
float MinDist = 0.0f;
float MaxDist = 0.0f;
for (auto it = EligibleNodes.begin(); it != EligibleNodes.end(); it++)
{
@ -2701,12 +2702,12 @@ void AIPlayerSetAlienCapperPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
if (bNodeClaimed) { continue; }
float ThisDist = vDist2DSq(pBot->Edict->v.origin, ThisNode->Location);
float ThisDist = vDist2DSq(AITAC_GetTeamStartingLocation(EnemyTeam), ThisNode->Location);
if (!NodeToCap || ThisDist < MinDist)
if (ThisDist > MaxDist)
{
NodeToCap = ThisNode;
MinDist = ThisDist;
MaxDist = ThisDist;
}
}
@ -2726,7 +2727,7 @@ void AIPlayerSetAlienCapperPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
vector<AvHAIResourceNode*> EligibleNodes = AITAC_GetAllMatchingResourceNodes(pBot->Edict->v.origin, &EnemyNodeFilter);
float MinDist = 0.0f;
float MaxDist = 0.0f;
NodeToCap = nullptr;
@ -2752,12 +2753,12 @@ void AIPlayerSetAlienCapperPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
}
}
float ThisDist = vDist2DSq(ThisNode->Location, AITAC_GetTeamStartingLocation(BotTeam));
float ThisDist = vDist2DSq(ThisNode->Location, AITAC_GetTeamStartingLocation(EnemyTeam));
if (!NodeToCap || ThisDist < MinDist)
if (!NodeToCap || ThisDist > MaxDist)
{
NodeToCap = ThisNode;
MinDist = ThisDist;
MaxDist = ThisDist;
}
}
@ -2774,6 +2775,238 @@ void AIPlayerSetAlienCapperPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
{
AvHTeamNumber BotTeam = pBot->Player->GetTeam();
AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam);
Vector EnemyBaseLocation = AITAC_GetTeamStartingLocation(EnemyTeam);
AvHAIHiveDefinition* HiveToGuard = nullptr;
AvHAIHiveDefinition* HiveToSecure = nullptr;
vector<AvHAIHiveDefinition*> AllHives = AITAC_GetAllHives();
float MaxGuardDist = 0.0f;
float MaxSecureDist = 0.0f;
bool bEnemyIsMarines = (AIMGR_GetTeamType(EnemyTeam) == AVH_CLASS_TYPE_MARINE);
DeployableSearchFilter EnemyStuffFilter;
EnemyStuffFilter.DeployableTeam = EnemyTeam;
EnemyStuffFilter.ReachabilityTeam = BotTeam;
EnemyStuffFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
EnemyStuffFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f);
if (bEnemyIsMarines)
{
EnemyStuffFilter.DeployableTypes = (STRUCTURE_MARINE_PHASEGATE | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY | STRUCTURE_MARINE_COMMCHAIR);
EnemyStuffFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
}
else
{
EnemyStuffFilter.DeployableTypes = STRUCTURE_ALIEN_OFFENCECHAMBER;
}
for (auto it = AllHives.begin(); it != AllHives.end(); it++)
{
AvHAIHiveDefinition* ThisHive = (*it);
if (ThisHive->OwningTeam != TEAM_IND) { continue; }
bool bEnemyIsSecuring = AITAC_DeployableExistsAtLocation(ThisHive->FloorLocation, &EnemyStuffFilter);
if (bEnemyIsSecuring)
{
float ThisDist = vDist2DSq(ThisHive->FloorLocation, EnemyBaseLocation);
if (ThisDist > MaxSecureDist)
{
HiveToSecure = ThisHive;
MaxSecureDist = ThisDist;
}
}
else
{
DeployableSearchFilter FriendlyStuffFilter;
FriendlyStuffFilter.DeployableTeam = BotTeam;
FriendlyStuffFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f);
FriendlyStuffFilter.DeployableTypes = STRUCTURE_ALIEN_OFFENCECHAMBER;
if (AITAC_DeployableExistsAtLocation(ThisHive->FloorLocation, &FriendlyStuffFilter)) { continue; }
bool bNeedsExtraGuards = true;
int NumGuards = 0;
vector<AvHPlayer*> HumanPlayers = AIMGR_GetNonAIPlayersOnTeam(BotTeam);
vector<AvHAIPlayer*> AITeamPlayers = AIMGR_GetAIPlayersOnTeam(BotTeam);
for (auto AIIt = AITeamPlayers.begin(); AIIt != AITeamPlayers.end(); AIIt++)
{
if ((*AIIt) == pBot) { continue; }
if ((*AIIt)->PrimaryBotTask.TaskType == TASK_GUARD && (*AIIt)->PrimaryBotTask.TaskTarget == ThisHive->HiveEntity->edict())
{
if ((*AIIt)->Player->GetUser3() >= AVH_USER3_ALIEN_PLAYER3) { bNeedsExtraGuards = false; }
NumGuards++;
}
}
for (auto GuardIt = HumanPlayers.begin(); GuardIt != HumanPlayers.end(); GuardIt++)
{
AvHPlayer* ThisGuard = (*GuardIt);
if (IsPlayerActiveInGame(ThisGuard->edict()) && vDist2DSq(ThisGuard->edict()->v.origin, ThisHive->FloorLocation) < sqrf(UTIL_MetresToGoldSrcUnits(15.0f)))
{
if (ThisGuard->GetUser3() >= AVH_USER3_ALIEN_PLAYER3) { bNeedsExtraGuards = false; }
NumGuards++;
}
}
bNeedsExtraGuards = bNeedsExtraGuards && NumGuards < 2;
if (bNeedsExtraGuards)
{
float ThisDist = vDist2DSq(ThisHive->FloorLocation, EnemyBaseLocation);
if (ThisDist > MaxSecureDist)
{
HiveToGuard = ThisHive;
MaxGuardDist = ThisDist;
}
}
}
}
// Favour attacking an enemy outpost over guarding an empty hive if fade or onos
if (pBot->Player->GetUser3() > AVH_USER3_ALIEN_PLAYER3)
{
if (HiveToSecure)
{
EnemyStuffFilter.DeployableTypes = SEARCH_ALL_STRUCTURES;
vector<AvHAIBuildableStructure*> AllEnemyThings = AITAC_FindAllDeployables(HiveToSecure->FloorLocation, &EnemyStuffFilter);
AvHAIBuildableStructure* StructureToAttack = nullptr;
for (auto it = AllEnemyThings.begin(); it != AllEnemyThings.end(); it++)
{
AvHAIBuildableStructure* ThisStructure = (*it);
// First prioritise phase gates or alien OCs
if (ThisStructure->StructureType == STRUCTURE_MARINE_PHASEGATE || ThisStructure->StructureType == STRUCTURE_ALIEN_OFFENCECHAMBER)
{
if (!StructureToAttack || StructureToAttack->StructureType != ThisStructure->StructureType || vDist2DSq(pBot->Edict->v.origin, ThisStructure->Location) < vDist2DSq(pBot->Edict->v.origin, StructureToAttack->Location))
{
StructureToAttack = ThisStructure;
continue;
}
}
if (StructureToAttack && (StructureToAttack->StructureType == STRUCTURE_MARINE_PHASEGATE || ThisStructure->StructureType == STRUCTURE_ALIEN_OFFENCECHAMBER)) { continue; }
// Then prioritise turret factories
if (ThisStructure->StructureType == STRUCTURE_MARINE_TURRETFACTORY || ThisStructure->StructureType == STRUCTURE_MARINE_ADVTURRETFACTORY)
{
if (!StructureToAttack || StructureToAttack->StructureType != ThisStructure->StructureType || vDist2DSq(pBot->Edict->v.origin, ThisStructure->Location) < vDist2DSq(pBot->Edict->v.origin, StructureToAttack->Location))
{
StructureToAttack = ThisStructure;
continue;
}
}
if (StructureToAttack && (StructureToAttack->StructureType == STRUCTURE_MARINE_TURRETFACTORY || ThisStructure->StructureType == STRUCTURE_MARINE_ADVTURRETFACTORY)) { continue; }
// Then target any other structures
if (!StructureToAttack || vDist2DSq(pBot->Edict->v.origin, ThisStructure->Location) < vDist2DSq(pBot->Edict->v.origin, StructureToAttack->Location))
{
StructureToAttack = ThisStructure;
}
}
if (StructureToAttack)
{
AITASK_SetAttackTask(pBot, Task, StructureToAttack->edict, false);
return;
}
}
else if (HiveToGuard)
{
Task->TaskType = TASK_GUARD;
Task->TaskLocation = HiveToGuard->FloorLocation;
Task->TaskTarget = HiveToGuard->HiveEntity->edict();
return;
}
}
// We're a skulk
else
{
if (HiveToGuard)
{
Task->TaskType = TASK_GUARD;
Task->TaskLocation = HiveToGuard->FloorLocation;
Task->TaskTarget = HiveToGuard->HiveEntity->edict();
return;
}
else if (HiveToSecure)
{
EnemyStuffFilter.DeployableTypes = SEARCH_ALL_STRUCTURES;
EnemyStuffFilter.ExcludeStatusFlags = STRUCTURE_STATUS_ELECTRIFIED;
vector<AvHAIBuildableStructure*> AllEnemyThings = AITAC_FindAllDeployables(HiveToSecure->FloorLocation, &EnemyStuffFilter);
AvHAIBuildableStructure* StructureToAttack = nullptr;
for (auto it = AllEnemyThings.begin(); it != AllEnemyThings.end(); it++)
{
AvHAIBuildableStructure* ThisStructure = (*it);
// First prioritise phase gates or alien OCs
if (ThisStructure->StructureType == STRUCTURE_MARINE_PHASEGATE || ThisStructure->StructureType == STRUCTURE_ALIEN_OFFENCECHAMBER)
{
if (!StructureToAttack || StructureToAttack->StructureType != ThisStructure->StructureType || vDist2DSq(pBot->Edict->v.origin, ThisStructure->Location) < vDist2DSq(pBot->Edict->v.origin, StructureToAttack->Location))
{
StructureToAttack = ThisStructure;
continue;
}
}
if (StructureToAttack && (StructureToAttack->StructureType == STRUCTURE_MARINE_PHASEGATE || ThisStructure->StructureType == STRUCTURE_ALIEN_OFFENCECHAMBER)) { continue; }
// Then prioritise turret factories
if (ThisStructure->StructureType == STRUCTURE_MARINE_TURRETFACTORY || ThisStructure->StructureType == STRUCTURE_MARINE_ADVTURRETFACTORY)
{
if (!StructureToAttack || StructureToAttack->StructureType != ThisStructure->StructureType || vDist2DSq(pBot->Edict->v.origin, ThisStructure->Location) < vDist2DSq(pBot->Edict->v.origin, StructureToAttack->Location))
{
StructureToAttack = ThisStructure;
continue;
}
}
if (StructureToAttack && (StructureToAttack->StructureType == STRUCTURE_MARINE_TURRETFACTORY || ThisStructure->StructureType == STRUCTURE_MARINE_ADVTURRETFACTORY)) { continue; }
// Then target any other structures
if (!StructureToAttack || vDist2DSq(pBot->Edict->v.origin, ThisStructure->Location) < vDist2DSq(pBot->Edict->v.origin, StructureToAttack->Location))
{
StructureToAttack = ThisStructure;
}
}
if (StructureToAttack)
{
AITASK_SetAttackTask(pBot, Task, StructureToAttack->edict, false);
return;
}
}
}
// TODO: Attack enemy hive/base
edict_t* EnemyChair = AITAC_GetCommChair(EnemyTeam);
if (!FNullEnt(EnemyChair))
{
AITASK_SetAttackTask(pBot, Task, EnemyChair, false);
}
}

View file

@ -2748,6 +2748,34 @@ int AITAC_GetNumPlayersOfTeamAndClassInArea(const AvHTeamNumber Team, const Vect
return Result;
}
vector<AvHPlayer*> AITAC_GetAllPlayersOfTeamInArea(const AvHTeamNumber Team, const Vector SearchLocation, const float SearchRadius, const bool bConsiderPhaseDist, const edict_t* IgnorePlayer, const AvHUser3 IgnoreClass)
{
vector<AvHPlayer*> Result;
float MaxRadiusSq = sqrf(SearchRadius);
for (int i = 1; i <= gpGlobals->maxClients; i++)
{
edict_t* PlayerEdict = INDEXENT(i);
if (FNullEnt(PlayerEdict) || PlayerEdict->free || PlayerEdict == IgnorePlayer) { continue; }
AvHPlayer* PlayerRef = dynamic_cast<AvHPlayer*>(CBaseEntity::Instance(PlayerEdict));
if (PlayerRef != nullptr && GetPlayerActiveClass(PlayerRef) != IgnoreClass && (Team == TEAM_IND || PlayerRef->GetTeam() == Team) && IsPlayerActiveInGame(PlayerEdict))
{
float Dist = (bConsiderPhaseDist) ? sqrf(AITAC_GetPhaseDistanceBetweenPoints(PlayerEdict->v.origin, SearchLocation)) : vDist2DSq(PlayerEdict->v.origin, SearchLocation);
if (Dist <= MaxRadiusSq)
{
Result.push_back(PlayerRef);
}
}
}
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;
@ -3695,13 +3723,10 @@ bool AITAC_ShouldBotBuildHive(AvHAIPlayer* pBot, AvHAIHiveDefinition** EligibleH
bool AITAC_IsAlienCapperNeeded(AvHAIPlayer* pBot)
{
// Ok so this logic is fairly involved:
// A node is eligible if it is reachable, and not in the enemy base (either marine spawn or a live enemy hive)
// Of all eligible nodes, if we own 60% or more of them, then we only need one capper, and we will only become a capper if we're skulk/gorge
// Otherwise, the number of cappers is on a sliding scale based on map ownership and number of RTs held (max 40% of team capping if things really bad)
// For an individual alien, they will only go capper if:
// They have the resources to cap, or there is an enemy RT they could take out
// If they are lerk/fade/onos, then only if there isn't a lower life form (non-builder) to do the job and we are desperate (<35% ownership or less than 3 nodes)
// A node is eligible if it is reachable, and not in the enemy base (either marine spawn or a live enemy hive).
// We set a max acceptable % of team allowed to be capping nodes at one time (default 50%).
// The number of desired cappers is expressed as an inverse function of how many nodes we own, so the more nodes we own = the smaller % of team should be capping
// Max desired cappers is also limited by how many available nodes there are so we can't end up with more cappers than available nodes
AvHTeamNumber BotTeam = pBot->Player->GetTeam();
AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam);
@ -3731,6 +3756,7 @@ bool AITAC_IsAlienCapperNeeded(AvHAIPlayer* pBot)
if (NumNodesLeft == 0) { return false; }
float OurNodeOwnership = (float)NumOwnedNodes / (float)NumEligibleNodes;
float EnemyNodeOwnership = (float)NumEnemyNodes / (float)NumEligibleNodes;
int NumTeamPlayers = AIMGR_GetNumPlayersOnTeam(BotTeam);
float MaxCapperPercent = 0.5f;
@ -3743,8 +3769,64 @@ bool AITAC_IsAlienCapperNeeded(AvHAIPlayer* pBot)
if (NumCurrentCappers >= DesiredCappers) { return false; }
// TODO: Ok so we need more cappers, but more logic is needed here
// to decide if we should go capper or not based on what others are doing
int CapperDeficit = DesiredCappers - NumCurrentCappers;
// Ok, we have established that we need cappers, but let's see if WE should be capping
float ResourcesNeeded = BALANCE_VAR(kResourceTowerCost);
if (!IsPlayerGorge(pBot->Edict))
{
ResourcesNeeded += BALANCE_VAR(kGorgeCost);
}
if (pBot->Player->GetResources() < ResourcesNeeded)
{
if (EnemyNodeOwnership < 0.35f) { return false; }
}
bool bIsHigherLifeform = (!IsPlayerSkulk(pBot->Edict) && !IsPlayerGorge(pBot->Edict));
bool bIsBuilder = (IsPlayerGorge(pBot->Edict) && pBot->BotRole == BOT_ROLE_BUILDER);
if (bIsHigherLifeform || bIsBuilder)
{
if (OurNodeOwnership > 0.35f) { return false; }
int NumAlternativeCandidates = 0;
vector<AvHPlayer*> CandidateTeamMates = AIMGR_GetNonAIPlayersOnTeam(BotTeam);
vector<AvHAIPlayer*> AITeamMates = AIMGR_GetAIPlayersOnTeam(BotTeam);
for (auto it = AITeamMates.begin(); it != AITeamMates.end(); it++)
{
AvHAIPlayer* ThisBot = (*it);
if (ThisBot == pBot) { continue; }
if (ThisBot->BotRole != BOT_ROLE_FIND_RESOURCES)
{
CandidateTeamMates.push_back(ThisBot->Player);
}
}
for (auto it = CandidateTeamMates.begin(); it != CandidateTeamMates.end(); it++)
{
AvHPlayer* ThisPlayer = (*it);
if (ThisPlayer == pBot->Player) { continue; }
if (ThisPlayer->GetUser3() < pBot->Player->GetUser3() || (ThisPlayer->GetUser3() == pBot->Player->GetUser3() && ThisPlayer->GetResources() > pBot->Player->GetResources()))
{
NumAlternativeCandidates++;
}
if (NumAlternativeCandidates >= CapperDeficit) { return false; }
}
}
// Nobody better to do the job
return true;
}
bool AITAC_IsAlienBuilderNeeded(AvHAIPlayer* pBot)

View file

@ -119,6 +119,7 @@ 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);
vector<AvHPlayer*> AITAC_GetAllPlayersOfTeamInArea(const AvHTeamNumber Team, const Vector SearchLocation, const float SearchRadius, const bool bConsiderPhaseDist, const edict_t* IgnorePlayer, const AvHUser3 IgnoreClass);
int AITAC_GetNumPlayersOfTeamAndClassInArea(const AvHTeamNumber Team, const Vector SearchLocation, const float SearchRadius, const bool bConsiderPhaseDist, const edict_t* IgnorePlayer, const AvHUser3 SearchClass);
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);