From 680aa8dbdb030928c7b6a77a31cc74d076d32688 Mon Sep 17 00:00:00 2001 From: RGreenlees Date: Fri, 16 Feb 2024 23:36:59 +0000 Subject: [PATCH] Marine and alien tactical bug fixes --- main/source/mod/AIPlayers/AvHAICommander.cpp | 48 +++++- main/source/mod/AIPlayers/AvHAIHelper.cpp | 5 + main/source/mod/AIPlayers/AvHAIHelper.h | 1 + main/source/mod/AIPlayers/AvHAINavigation.cpp | 14 +- main/source/mod/AIPlayers/AvHAIPlayer.cpp | 152 ++++++++++++++++-- main/source/mod/AIPlayers/AvHAIPlayer.h | 1 + .../mod/AIPlayers/AvHAIPlayerManager.cpp | 22 ++- .../source/mod/AIPlayers/AvHAIPlayerManager.h | 3 + main/source/mod/AIPlayers/AvHAITactical.cpp | 7 +- main/source/mod/AIPlayers/AvHAITactical.h | 2 +- main/source/mod/AIPlayers/AvHAITask.cpp | 108 +++++++++---- main/source/mod/AIPlayers/AvHAITask.h | 2 + main/source/mod/AvHGamerules.cpp | 12 ++ 13 files changed, 317 insertions(+), 60 deletions(-) diff --git a/main/source/mod/AIPlayers/AvHAICommander.cpp b/main/source/mod/AIPlayers/AvHAICommander.cpp index 873e262a..cc1b289e 100644 --- a/main/source/mod/AIPlayers/AvHAICommander.cpp +++ b/main/source/mod/AIPlayers/AvHAICommander.cpp @@ -1086,7 +1086,7 @@ bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot) } StructureFilter.DeployableTypes = STRUCTURE_MARINE_PROTOTYPELAB; - StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); + StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(20.0f); StructureFilter.IncludeStatusFlags = STRUCTURE_STATUS_NONE; StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; @@ -2065,12 +2065,56 @@ bool AICOMM_CheckForNextRecycleAction(AvHAIPlayer* pBot) { AvHAIHiveDefinition* Hive = (*HiveIt); + // If the hive is still active or growing, then clearly we should keep any siege bases if (Hive->Status != HIVE_STATUS_UNBUILT) { continue; } + // If the hive is empty, but we've not secured it yet, then keep any siege bases nearby in case we need to re-siege later + DeployableSearchFilter SecuringStructuresFilter; + SecuringStructuresFilter.DeployableTypes = (STRUCTURE_MARINE_PHASEGATE | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY | STRUCTURE_MARINE_TURRET); + SecuringStructuresFilter.DeployableTeam = pBot->Player->GetTeam(); + SecuringStructuresFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + SecuringStructuresFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + SecuringStructuresFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); + + vector NearbySecuringStructures = AITAC_FindAllDeployables(Hive->Location, &SecuringStructuresFilter); + + bool bHiveHasPG = false; + bool bHiveHasTF = false; + bool bHiveHasTurret = false; + + for (auto SecureIt = NearbySecuringStructures.begin(); SecureIt != NearbySecuringStructures.end(); SecureIt++) + { + AvHAIBuildableStructure* Structure = (*SecureIt); + + if (Structure->Purpose == STRUCTURE_PURPOSE_SIEGE) + { + if (Structure->StructureType == STRUCTURE_MARINE_PHASEGATE) + { + bHiveHasPG = true; + } + + if (Structure->StructureType == STRUCTURE_MARINE_TURRETFACTORY || Structure->StructureType == STRUCTURE_MARINE_ADVTURRETFACTORY) + { + bHiveHasTF = true; + } + + if (Structure->StructureType == STRUCTURE_MARINE_TURRET) + { + bHiveHasTurret = true; + } + } + } + + bool bHiveIsSecureEnough = (bHiveHasPG && bHiveHasTF && bHiveHasTurret); + + if (!bHiveIsSecureEnough) { continue; } + + // Ok, hive is secured by us, now we can check if there are any siege objects to be got rid of DeployableSearchFilter RedundantFilter; + RedundantFilter.DeployableTypes = SEARCH_ALL_STRUCTURES; RedundantFilter.DeployableTeam = pBot->Player->GetTeam(); RedundantFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; - RedundantFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(25.0f); + RedundantFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(30.0f); vector NearbyStructures = AITAC_FindAllDeployables(Hive->Location, &RedundantFilter); diff --git a/main/source/mod/AIPlayers/AvHAIHelper.cpp b/main/source/mod/AIPlayers/AvHAIHelper.cpp index 2714d7af..5e43b63a 100644 --- a/main/source/mod/AIPlayers/AvHAIHelper.cpp +++ b/main/source/mod/AIPlayers/AvHAIHelper.cpp @@ -224,6 +224,11 @@ bool IsEdictStructure(const edict_t* edict) return (GetDeployableObjectTypeFromEdict(edict) != STRUCTURE_NONE); } +bool IsEdictHive(const edict_t* edict) +{ + return (GetDeployableObjectTypeFromEdict(edict) != STRUCTURE_ALIEN_HIVE); +} + bool IsDamagingStructure(const edict_t* StructureEdict) { return IsDamagingStructure(GetStructureTypeFromEdict(StructureEdict)); diff --git a/main/source/mod/AIPlayers/AvHAIHelper.h b/main/source/mod/AIPlayers/AvHAIHelper.h index ec08b82a..0c5b11e1 100644 --- a/main/source/mod/AIPlayers/AvHAIHelper.h +++ b/main/source/mod/AIPlayers/AvHAIHelper.h @@ -25,6 +25,7 @@ Vector UTIL_GetClosestPointOnEntityToLocation(const Vector Location, edict_t* En AvHAIDeployableStructureType IUSER3ToStructureType(const int inIUSER3); bool IsEdictStructure(const edict_t* edict); +bool IsEdictHive(const edict_t* edict); AvHAIDeployableStructureType GetStructureTypeFromEdict(const edict_t* StructureEdict); diff --git a/main/source/mod/AIPlayers/AvHAINavigation.cpp b/main/source/mod/AIPlayers/AvHAINavigation.cpp index bc7d7f72..7c10077b 100644 --- a/main/source/mod/AIPlayers/AvHAINavigation.cpp +++ b/main/source/mod/AIPlayers/AvHAINavigation.cpp @@ -2004,7 +2004,7 @@ bool HasBotReachedPathPoint(const AvHAIPlayer* pBot) Vector ClosestPointToPath = vClosestPointOnLine2D(MoveFrom, MoveTo, pEdict->v.origin); bool bDestIsDirectlyReachable = UTIL_PointIsDirectlyReachable(CurrentPos, MoveTo); - bool bAtOrPastDestination = vEquals2D(ClosestPointToPath, MoveTo, 1.0f) && bDestIsDirectlyReachable; + bool bAtOrPastDestination = vEquals2D(ClosestPointToPath, MoveTo, 8.0f) && bDestIsDirectlyReachable; dtPolyRef BotPoly = pBot->BotNavInfo.CurrentPoly; dtPolyRef DestinationPoly = pBot->BotNavInfo.CurrentPathPoint->poly; @@ -3916,14 +3916,14 @@ bool IsBotOffPath(const AvHAIPlayer* pBot) bool bAtMoveEnd = vEquals(PointOnPath, MoveTo, GetPlayerRadius(pBot->Player)); - if (bAtMoveEnd && fabs(pBot->CurrentFloorPosition.z - MoveTo.z) > PlayerHeight) + if (bAtMoveEnd && fabsf(pBot->CurrentFloorPosition.z - MoveTo.z) > PlayerHeight) { return true; } float MaxDist = (bAtMoveStart || bAtMoveEnd) ? 50.0f : 200.0f; - if (vDistanceFromLine2D(MoveFrom, MoveTo, pBot->CurrentFloorPosition) > sqrf(MaxDist)) + if (vDistanceFromLine2D(MoveFrom, MoveTo, pBot->CurrentFloorPosition) > MaxDist) { return true; } @@ -5744,6 +5744,12 @@ void BotFollowFlightPath(AvHAIPlayer* pBot) SkipAheadInFlightPath(pBot); } + if (!UTIL_QuickTrace(pBot->Edict, pBot->Edict->v.origin, BotNavInfo->CurrentPathPoint->Location + Vector(0.0f, 0.0f, 5.0f))) + { + ClearBotPath(pBot); + return; + } + CurrentMoveDest = BotNavInfo->CurrentPathPoint->Location; Vector MoveFrom = BotNavInfo->CurrentPathPoint->FromLocation; @@ -7702,7 +7708,7 @@ void UTIL_UpdateDoorTriggers(nav_door* Door) { if (it->LastToggleState != TS_GOING_UP && it->LastToggleState != TS_GOING_DOWN) { - it->NextActivationTime = gpGlobals->time + it->ActivationDelay; + it->NextActivationTime = gpGlobals->time + fmaxf(it->ActivationDelay + 1.0f, 1.0f); } it->LastToggleState = (TOGGLE_STATE)it->ToggleEnt->GetToggleState(); diff --git a/main/source/mod/AIPlayers/AvHAIPlayer.cpp b/main/source/mod/AIPlayers/AvHAIPlayer.cpp index 16e329fe..7c9c4dea 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayer.cpp +++ b/main/source/mod/AIPlayers/AvHAIPlayer.cpp @@ -368,7 +368,7 @@ void BotSay(AvHAIPlayer* pBot, bool bTeamSay, float Delay, char* textToSay) bool BotReloadWeapons(AvHAIPlayer* pBot) { // Aliens and commander don't reload - if (!IsPlayerMarine(pBot->Edict) || !IsPlayerActiveInGame(pBot->Edict)) { return false; } + if (!IsPlayerMarine(pBot->Edict) || !IsPlayerActiveInGame(pBot->Edict) || IsPlayerReloading(pBot->Player)) { return false; } AvHAIWeapon PrimaryWeapon = UTIL_GetPlayerPrimaryWeapon(pBot->Player); AvHAIWeapon SecondaryWeapon = GetBotMarineSecondaryWeapon(pBot); @@ -3314,6 +3314,11 @@ void AIPlayerSetMarineBombardierPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* void AIPlayerSetWantsAndNeedsMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { + if (gpGlobals->time - pBot->LastCombatTime > 5.0f) + { + if (BotReloadWeapons(pBot)) { return; } + } + if (Task->TaskType == TASK_RESUPPLY || Task->TaskType == TASK_GET_HEALTH || Task->TaskType == TASK_GET_AMMO) { return; } AvHTeamNumber BotTeam = pBot->Player->GetTeam(); @@ -3818,11 +3823,12 @@ void AIPlayerNSAlienThink(AvHAIPlayer* pBot) AIPlayerSetPrimaryAlienTask(pBot, &pBot->PrimaryBotTask); AIPlayerSetSecondaryAlienTask(pBot, &pBot->SecondaryBotTask); + AIPlayerSetWantsAndNeedsAlienTask(pBot, &pBot->WantsAndNeedsTask); } pBot->CurrentTask = AIPlayerGetNextTask(pBot); - if (pBot->LastCombatTime > 5.0f) + if (gpGlobals->time - pBot->LastCombatTime > 5.0f) { if (!PlayerHasAlienUpgradeOfType(pBot->Edict, HIVE_TECH_DEFENCE) && AITAC_IsAlienUpgradeAvailableForTeam(pBot->Player->GetTeam(), HIVE_TECH_DEFENCE)) { @@ -4148,11 +4154,24 @@ void AIPlayerSetAlienBuilderPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task AvHTeamNumber BotTeam = pBot->Player->GetTeam(); AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam); + int NumMissingChambers = 0; + // Do we have any missing upgrade chambers (should have 3 of each if we can build them) - AvHAIDeployableStructureType MissingStructure = AITAC_GetNextMissingUpgradeChamberForTeam(BotTeam); + AvHAIDeployableStructureType MissingStructure = AITAC_GetNextMissingUpgradeChamberForTeam(BotTeam, NumMissingChambers); + bool bShouldBuildMissingStructure = false; + + if (MissingStructure != STRUCTURE_NONE) + { + int NumBuilders = AITASK_GetNumBotsWithBuildTask(BotTeam, MissingStructure, pBot->Edict); + + if (NumBuilders < NumMissingChambers) + { + bShouldBuildMissingStructure = true; + } + } // If we do have a missing upgrade chamber, built it at the nearest hive or resource node that we own, whichever is nearest - if (MissingStructure != STRUCTURE_NONE) + if (bShouldBuildMissingStructure) { if (Task->TaskType == TASK_BUILD && Task->StructureType == MissingStructure) { return; } @@ -4221,7 +4240,7 @@ void AIPlayerSetAlienBuilderPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task AvHAIHiveDefinition* HiveToSecure = nullptr; - float MaxDist = 0.0f; + float MinDist = 0.0f; for (auto it = AllHives.begin(); it != AllHives.end(); it++) { @@ -4291,12 +4310,12 @@ void AIPlayerSetAlienBuilderPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task || (AITAC_TeamHiveWithTechExists(BotTeam, ALIEN_BUILD_MOVEMENT_CHAMBER) && NumMCs < 1) || (AITAC_TeamHiveWithTechExists(BotTeam, ALIEN_BUILD_SENSORY_CHAMBER) && NumSCs < 1)) { - float ThisDist = vDist2DSq(AITAC_GetTeamStartingLocation(EnemyTeam), ThisHive->FloorLocation); + float ThisDist = vDist2DSq(pBot->Edict->v.origin, ThisHive->FloorLocation); - if (ThisDist > MaxDist) + if (!HiveToSecure || ThisDist < MinDist) { HiveToSecure = ThisHive; - MaxDist = ThisDist; + MinDist = ThisDist; } } @@ -4318,7 +4337,7 @@ void AIPlayerSetAlienBuilderPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task vector AllMatchingTowers = AITAC_FindAllDeployables(pBot->Edict->v.origin, &ResNodeFilter); edict_t* TowerToReinforce = nullptr; - float MinDist = 0.0f; + MinDist = 0.0f; for (auto it = AllMatchingTowers.begin(); it != AllMatchingTowers.end(); it++) { @@ -4327,7 +4346,7 @@ void AIPlayerSetAlienBuilderPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task DeployableSearchFilter ExistingReinforcementFilter; ExistingReinforcementFilter.DeployableTeam = BotTeam; ExistingReinforcementFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); - ExistingReinforcementFilter.DeployableTypes = SEARCH_ALL_ALIEN_STRUCTURES; + ExistingReinforcementFilter.DeployableTypes = SEARCH_ALL_STRUCTURES; vector AllReinforcingStructures = AITAC_FindAllDeployables(ThisResTower->Location, &ExistingReinforcementFilter); @@ -4584,6 +4603,8 @@ void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task return; } + if (Task->TaskType == TASK_EVOLVE) { return; } + if (!IsPlayerOnos(pBot->Edict) && pBot->Player->GetResources() >= BALANCE_VAR(kOnosCost)) { int NumOnos = AITAC_GetNumPlayersOnTeamOfClass(BotTeam, AVH_USER3_ALIEN_PLAYER5, pBot->Edict); @@ -4622,6 +4643,14 @@ void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task if (NearestSiegedHive) { + // Check if we're already trying to break a siege attempt, so we don't get torn between multiple potentials + if (Task->TaskType == TASK_ATTACK) + { + const AvHAIHiveDefinition* HiveNearestAttackTarget = AITAC_GetNearestTeamHive(BotTeam, Task->TaskTarget->v.origin, false); + + if (HiveNearestAttackTarget && vDist2DSq(HiveNearestAttackTarget->Location, Task->TaskTarget->v.origin) <= sqrf(UTIL_MetresToGoldSrcUnits(25.0f))) { return; } + } + DeployableSearchFilter EnemyStuffFilter; EnemyStuffFilter.DeployableTypes = SEARCH_ALL_STRUCTURES; EnemyStuffFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; @@ -4681,6 +4710,14 @@ void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task // If we're up against marines, look out for any siege stuff if (AIMGR_GetTeamType(EnemyTeam) == AVH_CLASS_TYPE_MARINE) { + // Check if we're already trying to break a siege attempt, so we don't get torn between multiple potentials + if (Task->TaskType == TASK_ATTACK) + { + const AvHAIHiveDefinition* HiveNearestAttackTarget = AITAC_GetNearestTeamHive(BotTeam, Task->TaskTarget->v.origin, false); + + if (HiveNearestAttackTarget && vDist2DSq(HiveNearestAttackTarget->Location, Task->TaskTarget->v.origin) <= sqrf(UTIL_MetresToGoldSrcUnits(25.0f))) { return; } + } + vector AllTeamHives = AITAC_GetAllTeamHives(BotTeam, false); for (auto it = AllTeamHives.begin(); it != AllTeamHives.end(); it++) @@ -4689,7 +4726,6 @@ void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task DeployableSearchFilter EnemyStuffFilter; EnemyStuffFilter.DeployableTypes = SEARCH_ALL_STRUCTURES; - EnemyStuffFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; EnemyStuffFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; EnemyStuffFilter.DeployableTeam = EnemyTeam; EnemyStuffFilter.ReachabilityTeam = BotTeam; @@ -4814,8 +4850,10 @@ void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task FriendlyStuffFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); FriendlyStuffFilter.DeployableTypes = STRUCTURE_ALIEN_OFFENCECHAMBER; - if (AITAC_DeployableExistsAtLocation(ThisHive->FloorLocation, &FriendlyStuffFilter)) { continue; } + // Don't guard a hive if some defences are already present + if (AITAC_GetNumDeployablesNearLocation(ThisHive->FloorLocation, &FriendlyStuffFilter) >= 2) { continue; } + // Only guard empty hives if a gorge is in there if (AITAC_GetNumPlayersOfTeamAndClassInArea(BotTeam, ThisHive->FloorLocation, UTIL_MetresToGoldSrcUnits(20.0f), false, pBot->Edict, AVH_USER3_ALIEN_PLAYER2) == 0) { continue; } bool bNeedsExtraGuards = true; @@ -4876,6 +4914,14 @@ void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task } else if (HiveToSecure) { + // Check if we're already trying to clear out a hive + if (Task->TaskType == TASK_ATTACK) + { + const AvHAIHiveDefinition* HiveNearestAttackTarget = AITAC_GetNearestTeamHive(BotTeam, Task->TaskTarget->v.origin, false); + + if (HiveNearestAttackTarget && vDist2DSq(HiveNearestAttackTarget->Location, Task->TaskTarget->v.origin) <= sqrf(UTIL_MetresToGoldSrcUnits(15.0f))) { return; } + } + EnemyStuffFilter.DeployableTypes = SEARCH_ALL_STRUCTURES; // Don't attack electrified structures as skulk @@ -5343,8 +5389,58 @@ void AIPlayerSetSecondaryAlienTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) return; } - AITASK_ClearBotTask(pBot, Task); +} +void AIPlayerSetWantsAndNeedsAlienTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) +{ + float CurrentHealth = GetPlayerOverallHealthPercent(pBot->Edict); + + if (Task->TaskType == TASK_GET_HEALTH) + { + Task->bTaskIsUrgent = Task->bTaskIsUrgent || CurrentHealth < 0.4f; + return; + } + + if (CurrentHealth >= 1.0f) { return; } + + bool bCanSelfHeal = (PlayerHasWeapon(pBot->Player, WEAPON_GORGE_HEALINGSPRAY) || PlayerHasWeapon(pBot->Player, WEAPON_FADE_METABOLIZE)); + + if (CurrentHealth < 0.95f && bCanSelfHeal && gpGlobals->time - pBot->LastCombatTime > 5.0f) + { + pBot->DesiredCombatWeapon = (PlayerHasWeapon(pBot->Player, WEAPON_FADE_METABOLIZE)) ? WEAPON_FADE_METABOLIZE : WEAPON_GORGE_HEALINGSPRAY; + + if (GetPlayerCurrentWeapon(pBot->Player) == pBot->DesiredCombatWeapon) + { + pBot->Button |= IN_ATTACK; + } + } + + // Only look for gorges as a healing source if we're something with low health like a skulk or lerk, or we have over 50% health. Don't use gorges if we're near death as an Onos or it will take forever... + edict_t* NearestHealingSource = AITAC_AlienFindNearestHealingSource(pBot->Player->GetTeam(), pBot->Edict->v.origin, pBot->Edict, (IsPlayerSkulk(pBot->Edict) || IsPlayerLerk(pBot->Edict) || CurrentHealth > 0.5f)); + + if (FNullEnt(NearestHealingSource)) { return; } + + float GetHealthThreshold = 0.6f; + + // If we're right by a healing source, then might as well heal up, set the "find health" threshold to 90% or less health + if (vDist2DSq(pBot->Edict->v.origin, NearestHealingSource->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) + { + GetHealthThreshold = 0.9f; + } + else + { + // If we can heal ourselves, then don't go running for the hive/DCs/Gorge unless we're very low + if (bCanSelfHeal) + { + GetHealthThreshold = 0.4f; + } + } + + + if (CurrentHealth < GetHealthThreshold) + { + AITASK_SetGetHealthTask(pBot, Task, NearestHealingSource, true); + } } bool AlienCombatThink(AvHAIPlayer* pBot) @@ -6066,7 +6162,7 @@ bool FadeCombatThink(AvHAIPlayer* pBot) bool bInHealingRange = (DistFromHealingSourceSq <= sqrf(DesiredDistFromHealingSource)); - if (!bInHealingRange) + if (!bInHealingRange && GetPlayerOverallHealthPercent(pBot->Edict) < 0.5f) { MoveTo(pBot, UTIL_GetEntityGroundLocation(NearestHealingSource), MOVESTYLE_NORMAL, DesiredDistFromHealingSource); @@ -6078,11 +6174,24 @@ bool FadeCombatThink(AvHAIPlayer* pBot) BotLeap(pBot, pBot->BotNavInfo.CurrentPathPoint->Location); } } + else + { + if (PlayerHasWeapon(pBot->Player, WEAPON_FADE_METABOLIZE)) + { + pBot->DesiredCombatWeapon = WEAPON_FADE_METABOLIZE; + + if (GetPlayerCurrentWeapon(pBot->Player) == WEAPON_FADE_METABOLIZE) + { + pBot->Button |= IN_ATTACK; + } + } + } return true; } - if (bOutOfEnemyLOS) + // If we're not in immediate danger, and we're either healing up at the source, or we can metabolize, then wait a bit and catch our breath + if (bOutOfEnemyLOS && (bInHealingRange || PlayerHasWeapon(pBot->Player, WEAPON_FADE_METABOLIZE))) { BotLookAt(pBot, TrackedEnemyRef->LastLOSPosition); if (PlayerHasWeapon(pBot->Player, WEAPON_FADE_METABOLIZE)) @@ -6100,10 +6209,21 @@ bool FadeCombatThink(AvHAIPlayer* pBot) if (!UTIL_PlayerHasLOSToLocation(TrackedEnemyRef->EnemyEdict, UTIL_GetEntityGroundLocation(NearestHealingSource) + Vector(0.0f, 0.0f, 16.0f), UTIL_MetresToGoldSrcUnits(30.0f))) { MoveTo(pBot, UTIL_GetEntityGroundLocation(NearestHealingSource), MOVESTYLE_NORMAL, DesiredDistFromHealingSource); + + if (PlayerHasWeapon(pBot->Player, WEAPON_FADE_METABOLIZE)) + { + pBot->DesiredCombatWeapon = WEAPON_FADE_METABOLIZE; + + if (GetPlayerCurrentWeapon(pBot->Player) == WEAPON_FADE_METABOLIZE) + { + pBot->Button |= IN_ATTACK; + } + } + return true; } - // If the enemy can see the healing source, then we must go on the attack + // If the enemy can see the healing source, then we must go on the attack as we're cornered bShouldBreakRetreat = true; } } diff --git a/main/source/mod/AIPlayers/AvHAIPlayer.h b/main/source/mod/AIPlayers/AvHAIPlayer.h index 1533cf15..b8bdb9ee 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayer.h +++ b/main/source/mod/AIPlayers/AvHAIPlayer.h @@ -88,6 +88,7 @@ void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task void AIPlayerSetAlienHarasserPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); void AIPlayerSetSecondaryAlienTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); +void AIPlayerSetWantsAndNeedsAlienTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); void BotSwitchToWeapon(AvHAIPlayer* pBot, AvHAIWeapon NewWeaponSlot); diff --git a/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp b/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp index 4a0efde6..4efd4e9f 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp +++ b/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp @@ -30,6 +30,8 @@ float AIStartedTime = 0.0f; // Used to give 5-second grace period before adding bool bHasRoundStarted = false; bool bMapDataInitialised = false; +bool bTestNavigation = false; + extern int m_spriteTexture; Vector DebugVector1 = ZERO_VECTOR; @@ -623,7 +625,15 @@ void AIMGR_UpdateAIPlayers() UpdateBotChat(bot); - DroneThink(bot); + if (bTestNavigation) + { + TestNavThink(bot); + } + else + { + DroneThink(bot); + } + EndBotFrame(bot); @@ -1051,4 +1061,14 @@ void AIMGR_SetDebugAIPlayer(edict_t* AIPlayer) return; } } +} + +void AIMGR_SetTestNavMode(bool bNewValue) +{ + bTestNavigation = bNewValue; +} + +bool AIMGR_GetTestNavMode() +{ + return bTestNavigation; } \ No newline at end of file diff --git a/main/source/mod/AIPlayers/AvHAIPlayerManager.h b/main/source/mod/AIPlayers/AvHAIPlayerManager.h index 676da7c0..51e4c88b 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayerManager.h +++ b/main/source/mod/AIPlayers/AvHAIPlayerManager.h @@ -87,4 +87,7 @@ void AIMGR_ClearBotData(); AvHAIPlayer* AIMGR_GetDebugAIPlayer(); void AIMGR_SetDebugAIPlayer(edict_t* AIPlayer); +void AIMGR_SetTestNavMode(bool bNewValue); +bool AIMGR_GetTestNavMode(); + #endif \ No newline at end of file diff --git a/main/source/mod/AIPlayers/AvHAITactical.cpp b/main/source/mod/AIPlayers/AvHAITactical.cpp index 288b5ef9..e180c070 100644 --- a/main/source/mod/AIPlayers/AvHAITactical.cpp +++ b/main/source/mod/AIPlayers/AvHAITactical.cpp @@ -2628,6 +2628,8 @@ bool AITAC_ElectricalResearchIsAvailable(edict_t* Structure) AvHAIHiveDefinition* AITAC_GetHiveFromEdict(const edict_t* Edict) { + if (Edict->v.iuser3 != AVH_USER3_HIVE) { return nullptr; } + for (auto it = Hives.begin(); it != Hives.end(); it++) { if (it->HiveEntity->edict() == Edict) @@ -4428,7 +4430,7 @@ bool AITAC_IsAlienBuilderNeeded(AvHAIPlayer* pBot) } -AvHAIDeployableStructureType AITAC_GetNextMissingUpgradeChamberForTeam(AvHTeamNumber Team) +AvHAIDeployableStructureType AITAC_GetNextMissingUpgradeChamberForTeam(AvHTeamNumber Team, int& NumMissing) { if (AIMGR_GetTeamType(Team) != AVH_CLASS_TYPE_ALIEN) { return STRUCTURE_NONE; } @@ -4453,6 +4455,7 @@ AvHAIDeployableStructureType AITAC_GetNextMissingUpgradeChamberForTeam(AvHTeamNu if (NumChambers < 3) { + NumMissing = 3 - NumChambers; return ChamberTypeOne; } } @@ -4465,6 +4468,7 @@ AvHAIDeployableStructureType AITAC_GetNextMissingUpgradeChamberForTeam(AvHTeamNu if (NumChambers < 3) { + NumMissing = 3 - NumChambers; return ChamberTypeTwo; } } @@ -4477,6 +4481,7 @@ AvHAIDeployableStructureType AITAC_GetNextMissingUpgradeChamberForTeam(AvHTeamNu if (NumChambers < 3) { + NumMissing = 3 - NumChambers; return ChamberTypeThree; } } diff --git a/main/source/mod/AIPlayers/AvHAITactical.h b/main/source/mod/AIPlayers/AvHAITactical.h index dde57dca..d5300e16 100644 --- a/main/source/mod/AIPlayers/AvHAITactical.h +++ b/main/source/mod/AIPlayers/AvHAITactical.h @@ -176,7 +176,7 @@ bool AITAC_IsAlienHarasserNeeded(AvHAIPlayer* pBot); bool AITAC_ShouldBotBuildHive(AvHAIPlayer* pBot, AvHAIHiveDefinition** EligibleHive); -AvHAIDeployableStructureType AITAC_GetNextMissingUpgradeChamberForTeam(AvHTeamNumber Team); +AvHAIDeployableStructureType AITAC_GetNextMissingUpgradeChamberForTeam(AvHTeamNumber Team, int& NumMissing); void AITAC_OnTeamStartsModified(); diff --git a/main/source/mod/AIPlayers/AvHAITask.cpp b/main/source/mod/AIPlayers/AvHAITask.cpp index d40657f0..feee55b2 100644 --- a/main/source/mod/AIPlayers/AvHAITask.cpp +++ b/main/source/mod/AIPlayers/AvHAITask.cpp @@ -1035,14 +1035,6 @@ void BotProgressMoveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { MoveDirectlyTo(pBot, Task->TaskLocation); } - - if (IsPlayerMarine(pBot->Edict)) - { - if (gpGlobals->time - pBot->LastCombatTime > 5.0f) - { - BotReloadWeapons(pBot); - } - } } void BotProgressTouchTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) @@ -1225,22 +1217,18 @@ void BotProgressReinforceStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) SearchRadius = UTIL_MetresToGoldSrcUnits(10.0f); } - AvHAIDeployableStructureType NextStructure = AITAC_GetNextMissingUpgradeChamberForTeam(BotTeam); + AvHAIDeployableStructureType NextStructure = STRUCTURE_NONE; DeployableSearchFilter StructureFilter; StructureFilter.DeployableTeam = BotTeam; StructureFilter.MaxSearchRadius = SearchRadius; + StructureFilter.DeployableTypes = STRUCTURE_ALIEN_OFFENCECHAMBER; - if (NextStructure == STRUCTURE_NONE) + int NumOCs = AITAC_GetNumDeployablesNearLocation(ReinforceLocation, &StructureFilter); + + if (NumOCs < 3) { - StructureFilter.DeployableTypes = STRUCTURE_ALIEN_OFFENCECHAMBER; - - int NumOCs = AITAC_GetNumDeployablesNearLocation(ReinforceLocation, &StructureFilter); - - if (NumOCs < 3) - { - NextStructure = STRUCTURE_ALIEN_OFFENCECHAMBER; - } + NextStructure = STRUCTURE_ALIEN_OFFENCECHAMBER; } if (NextStructure == STRUCTURE_NONE) @@ -1548,14 +1536,6 @@ void MarineProgressBuildTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) MoveTo(pBot, Task->TaskTarget->v.origin, MOVESTYLE_NORMAL); - if (IsPlayerMarine(pBot->Edict)) - { - if (gpGlobals->time - pBot->LastCombatTime > 5.0f) - { - BotReloadWeapons(pBot); - } - } - if (vDist2DSq(pBot->Edict->v.origin, Task->TaskTarget->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) { BotLookAt(pBot, UTIL_GetCentreOfEntity(Task->TaskTarget)); @@ -1565,13 +1545,6 @@ void MarineProgressBuildTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) void BotProgressGuardTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { - if (IsPlayerMarine(pBot->Edict)) - { - if (gpGlobals->time - pBot->LastCombatTime > 5.0f) - { - BotReloadWeapons(pBot); - } - } if (vDist2DSq(pBot->Edict->v.origin, Task->TaskLocation) > sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) { @@ -2816,7 +2789,7 @@ AvHAIPlayer* GetFirstBotWithBuildTask(AvHTeamNumber Team, AvHAIDeployableStructu { AvHAIPlayer* Bot = (*it); - if (!IsPlayerActiveInGame(Bot->Edict)) { continue; } + if (!IsPlayerActiveInGame(Bot->Edict) || Bot->Edict == IgnorePlayer) { continue; } bool bPrimaryIsBuildTask = (Bot->PrimaryBotTask.TaskType == TASK_BUILD || Bot->PrimaryBotTask.TaskType == TASK_REINFORCE_STRUCTURE); bool bSecondaryIsBuildTask = (Bot->SecondaryBotTask.TaskType == TASK_BUILD || Bot->SecondaryBotTask.TaskType == TASK_REINFORCE_STRUCTURE); @@ -2831,6 +2804,30 @@ AvHAIPlayer* GetFirstBotWithBuildTask(AvHTeamNumber Team, AvHAIDeployableStructu return nullptr; } +int AITASK_GetNumBotsWithBuildTask(AvHTeamNumber Team, AvHAIDeployableStructureType StructureType, edict_t* IgnorePlayer) +{ + vector AIPlayers = AIMGR_GetAIPlayersOnTeam(Team); + int Result = 0; + + for (auto it = AIPlayers.begin(); it != AIPlayers.end(); it++) + { + AvHAIPlayer* Bot = (*it); + + if (!IsPlayerActiveInGame(Bot->Edict) || Bot->Edict == IgnorePlayer) { continue; } + + bool bPrimaryIsBuildTask = (Bot->PrimaryBotTask.TaskType == TASK_BUILD || Bot->PrimaryBotTask.TaskType == TASK_REINFORCE_STRUCTURE); + bool bSecondaryIsBuildTask = (Bot->SecondaryBotTask.TaskType == TASK_BUILD || Bot->SecondaryBotTask.TaskType == TASK_REINFORCE_STRUCTURE); + + if ((bPrimaryIsBuildTask && Bot->PrimaryBotTask.StructureType == StructureType) || (bSecondaryIsBuildTask && Bot->SecondaryBotTask.StructureType == StructureType)) + { + Result++; + } + + } + + return Result; +} + AvHAIPlayer* GetFirstBotWithReinforceTask(AvHTeamNumber Team, edict_t* ReinforceStructure, edict_t* IgnorePlayer) { vector AIPlayers = AIMGR_GetAIPlayersOnTeam(Team); @@ -2839,7 +2836,7 @@ AvHAIPlayer* GetFirstBotWithReinforceTask(AvHTeamNumber Team, edict_t* Reinforce { AvHAIPlayer* Bot = (*it); - if (!IsPlayerActiveInGame(Bot->Edict)) { continue; } + if (!IsPlayerActiveInGame(Bot->Edict) || Bot->Edict == IgnorePlayer) { continue; } if ((Bot->PrimaryBotTask.TaskType == TASK_REINFORCE_STRUCTURE && Bot->PrimaryBotTask.TaskTarget == ReinforceStructure) || (Bot->SecondaryBotTask.TaskType == TASK_REINFORCE_STRUCTURE && Bot->SecondaryBotTask.TaskTarget == ReinforceStructure)) { @@ -2948,6 +2945,47 @@ void AITASK_SetPickupTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, edict_t* Tar } +void AITASK_SetGetHealthTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, edict_t* HealingSource, const bool bIsUrgent) +{ + if (Task->TaskType == TASK_GET_HEALTH && Task->TaskTarget == HealingSource) + { + Task->bTaskIsUrgent = bIsUrgent; + return; + } + + AITASK_ClearBotTask(pBot, Task); + + if (FNullEnt(HealingSource)) { return; } + + Vector HealLocation = ZERO_VECTOR; + AvHAIHiveDefinition* HiveRef = AITAC_GetHiveFromEdict(HealingSource); + + if (HiveRef) + { + HealLocation = HiveRef->FloorLocation; + } + else + { + if (IsEdictPlayer(HealingSource)) + { + HealLocation = HealingSource->v.origin; + } + else + { + HealLocation = FindClosestNavigablePointToDestination(pBot->BotNavInfo.NavProfile, pBot->Edict->v.origin, HealingSource->v.origin, UTIL_MetresToGoldSrcUnits(5.0f)); + } + } + + if (!vIsZero(HealLocation)) + { + Task->TaskType = TASK_GET_HEALTH; + Task->TaskTarget = HealingSource; + Task->TaskLocation = HealLocation; + Task->bTaskIsUrgent = bIsUrgent; + } + +} + void AITASK_SetWeldTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, edict_t* Target, const bool bIsUrgent) { if (FNullEnt(Target) || (Target->v.deadflag != DEAD_NO)) diff --git a/main/source/mod/AIPlayers/AvHAITask.h b/main/source/mod/AIPlayers/AvHAITask.h index 04d52d19..afb8297b 100644 --- a/main/source/mod/AIPlayers/AvHAITask.h +++ b/main/source/mod/AIPlayers/AvHAITask.h @@ -68,6 +68,7 @@ void AITASK_SetSecureHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, edict_t* void AITASK_SetMineStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, edict_t* Target, bool bIsUrgent); void AITASK_SetWeldTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, edict_t* Target, const bool bIsUrgent); void AITASK_SetPickupTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, edict_t* Target, const bool bIsUrgent); +void AITASK_SetGetHealthTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, edict_t* HealingSource, const bool bIsUrgent); void BotProgressTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); @@ -104,6 +105,7 @@ void BotGuardLocation(AvHAIPlayer* pBot, const Vector GuardLocation); void AITASK_GenerateGuardWatchPoints(AvHAIPlayer* pBot, const Vector& GuardLocation); bool BotWithBuildTaskExists(AvHTeamNumber Team, AvHAIDeployableStructureType StructureType); +int AITASK_GetNumBotsWithBuildTask(AvHTeamNumber Team, AvHAIDeployableStructureType StructureType, edict_t* IgnorePlayer); AvHAIPlayer* GetFirstBotWithBuildTask(AvHTeamNumber Team, AvHAIDeployableStructureType StructureType, edict_t* IgnorePlayer); AvHAIPlayer* GetFirstBotWithReinforceTask(AvHTeamNumber Team, edict_t* ReinforceStructure, edict_t* IgnorePlayer); diff --git a/main/source/mod/AvHGamerules.cpp b/main/source/mod/AvHGamerules.cpp index 8fd83a70..50fd0b79 100644 --- a/main/source/mod/AvHGamerules.cpp +++ b/main/source/mod/AvHGamerules.cpp @@ -392,6 +392,18 @@ AvHGamerules::AvHGamerules() : mTeamA(TEAM_ONE), mTeamB(TEAM_TWO) AIMGR_RemoveAIPlayerFromTeam(DesiredTeam); }); + REGISTER_SERVER_FUNCTION("sv_testainavigation", []() + { + if (avh_botsenabled.value == 0) + { + return; + } + + bool bNewTestValue = !AIMGR_GetTestNavMode(); + + AIMGR_SetTestNavMode(bNewTestValue); + }); + g_VoiceGameMgr.Init(&gVoiceHelper, gpGlobals->maxClients); #ifdef DEBUG