From f31e2bbf1c6e1ad0d6d9b20b14432f5dbd2231dc Mon Sep 17 00:00:00 2001 From: RGreenlees Date: Sat, 20 Jan 2024 22:43:41 +0000 Subject: [PATCH] Alien assault AI --- main/source/mod/AIPlayers/AvHAIPlayer.cpp | 249 +++++++++++++++++++- main/source/mod/AIPlayers/AvHAITactical.cpp | 100 +++++++- main/source/mod/AIPlayers/AvHAITactical.h | 1 + 3 files changed, 333 insertions(+), 17 deletions(-) diff --git a/main/source/mod/AIPlayers/AvHAIPlayer.cpp b/main/source/mod/AIPlayers/AvHAIPlayer.cpp index 35465e65..a337d0e2 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayer.cpp +++ b/main/source/mod/AIPlayers/AvHAIPlayer.cpp @@ -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 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 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 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 HumanPlayers = AIMGR_GetNonAIPlayersOnTeam(BotTeam); + vector 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 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 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); + } } diff --git a/main/source/mod/AIPlayers/AvHAITactical.cpp b/main/source/mod/AIPlayers/AvHAITactical.cpp index c0205370..57cc3d96 100644 --- a/main/source/mod/AIPlayers/AvHAITactical.cpp +++ b/main/source/mod/AIPlayers/AvHAITactical.cpp @@ -2748,6 +2748,34 @@ int AITAC_GetNumPlayersOfTeamAndClassInArea(const AvHTeamNumber Team, const Vect return Result; } +vector AITAC_GetAllPlayersOfTeamInArea(const AvHTeamNumber Team, const Vector SearchLocation, const float SearchRadius, const bool bConsiderPhaseDist, const edict_t* IgnorePlayer, const AvHUser3 IgnoreClass) +{ + vector 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(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 CandidateTeamMates = AIMGR_GetNonAIPlayersOnTeam(BotTeam); + vector 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) diff --git a/main/source/mod/AIPlayers/AvHAITactical.h b/main/source/mod/AIPlayers/AvHAITactical.h index d9ea8eec..fe835b34 100644 --- a/main/source/mod/AIPlayers/AvHAITactical.h +++ b/main/source/mod/AIPlayers/AvHAITactical.h @@ -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 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);