From a747ed0187da2de91ff3fb5e9a99ff3a8b4cdca3 Mon Sep 17 00:00:00 2001 From: RGreenlees Date: Mon, 5 Feb 2024 23:18:07 +0000 Subject: [PATCH] Improved bot stuck detection --- main/source/mod/AIPlayers/AvHAICommander.cpp | 201 +++- main/source/mod/AIPlayers/AvHAIConstants.h | 13 +- main/source/mod/AIPlayers/AvHAIHelper.cpp | 4 +- main/source/mod/AIPlayers/AvHAINavigation.cpp | 120 +-- main/source/mod/AIPlayers/AvHAINavigation.h | 4 +- main/source/mod/AIPlayers/AvHAIPlayer.cpp | 865 ++++++++++++++++-- .../mod/AIPlayers/AvHAIPlayerManager.cpp | 7 +- .../source/mod/AIPlayers/AvHAIPlayerManager.h | 2 + main/source/mod/AIPlayers/AvHAITactical.cpp | 119 ++- main/source/mod/AIPlayers/AvHAITactical.h | 3 +- main/source/mod/AIPlayers/AvHAITask.cpp | 87 +- .../mod/AIPlayers/AvHAIWeaponHelper.cpp | 1 + 12 files changed, 1177 insertions(+), 249 deletions(-) diff --git a/main/source/mod/AIPlayers/AvHAICommander.cpp b/main/source/mod/AIPlayers/AvHAICommander.cpp index 83d479d2..fe31522e 100644 --- a/main/source/mod/AIPlayers/AvHAICommander.cpp +++ b/main/source/mod/AIPlayers/AvHAICommander.cpp @@ -61,9 +61,11 @@ bool AICOMM_DeployItem(AvHAIPlayer* pBot, const AvHAIDeployableItemType ItemToDe if (!AvHSHUGetIsSiteValidForBuild(StructureID, &BuildLocation)) { return false; } - bool theSuccess = (AvHSUBuildTechForPlayer(StructureID, BuildLocation, pBot->Player) != NULL); + CBaseEntity* NewItem = AvHSUBuildTechForPlayer(StructureID, BuildLocation, pBot->Player); - if (!theSuccess) { return false; } + if (!NewItem) { return false; } + + AITAC_UpdateMarineItem(NewItem, ItemToDeploy); pBot->Player->PayPurchaseCost(theCost); @@ -85,6 +87,20 @@ bool AICOMM_ResearchTech(AvHAIPlayer* pBot, AvHAIBuildableStructure* StructureTo if (!StructureToResearch->EntityRef->GetIsTechnologyAvailable(Research)) { return false; } + AvHTeam* CommanderTeamRef = AIMGR_GetTeamRef(pBot->Player->GetTeam()); + + if (!CommanderTeamRef) { return false; } + + AvHResearchManager& theResearchManager = CommanderTeamRef->GetResearchManager(); + + bool theIsResearchable = false; + int theResearchCost = 0.0f; + float theResearchTime = 0.0f; + + theResearchManager.GetResearchInfo(Research, theIsResearchable, theResearchCost, theResearchTime); + + if (pBot->Player->GetResources() < theResearchCost) { return false; } + pBot->Player->SetSelection(StructureIndex, true); pBot->Button |= IN_ATTACK2; @@ -388,6 +404,7 @@ bool AICOMM_ShouldCommanderPrioritiseNodes(AvHAIPlayer* pBot) int NumOwnedNodes = 0; int NumEligibleNodes = 0; + int NumFreeNodes = 0; // First get ours and the enemy's ownership of all eligible nodes (we can reach them, and they're in the enemy base) vector AllNodes = AITAC_GetAllReachableResourceNodes(BotTeam); @@ -399,6 +416,11 @@ bool AICOMM_ShouldCommanderPrioritiseNodes(AvHAIPlayer* pBot) // We don't care about the node at marine spawn or enemy hives, ignore then in our calculations if (ThisNode->OwningTeam == EnemyTeam && ThisNode->bIsBaseNode) { continue; } + if (ThisNode->OwningTeam == TEAM_IND) + { + NumFreeNodes++; + } + NumEligibleNodes++; if (ThisNode->OwningTeam == BotTeam) { NumOwnedNodes++; } @@ -408,7 +430,7 @@ bool AICOMM_ShouldCommanderPrioritiseNodes(AvHAIPlayer* pBot) if (NumNodesLeft == 0) { return false; } - return NumOwnedNodes < 3 || NumNodesLeft > 3; + return NumOwnedNodes < 3 || NumFreeNodes > 3; } @@ -587,8 +609,11 @@ edict_t* AICOMM_GetPlayerWithNoOrderNearestLocation(AvHAIPlayer* pBot, Vector Se for (auto it = PlayerList.begin(); it != PlayerList.end();) { AvHPlayer* PlayerRef = (*it); + AvHAIPlayer* AIPlayerRef = AIMGR_GetBotRefFromPlayer(PlayerRef); - if (!IsPlayerActiveInGame(PlayerRef->edict())) + // Don't give orders to incapacitated players, or if the bot is currently playing a defensive role. Stops the commander sending everyone out + // and leaving nobody at base + if (!IsPlayerActiveInGame(PlayerRef->edict()) || (AIPlayerRef && AIPlayerRef->BotRole == BOT_ROLE_SWEEPER)) { it = PlayerList.erase(it); } @@ -1093,9 +1118,76 @@ bool AICOMM_CheckForNextSupplyAction(AvHAIPlayer* pBot) { AvHTeamNumber CommanderTeam = pBot->Player->GetTeam(); + // First thing: if our base is damaged and there's nobody able to weld, drop a welder so we don't let the base die + bool bBaseIsDamaged = false; + + DeployableSearchFilter DamagedBaseStructures; + DamagedBaseStructures.DeployableTypes = (STRUCTURE_MARINE_COMMCHAIR | STRUCTURE_MARINE_INFANTRYPORTAL); + DamagedBaseStructures.DeployableTeam = CommanderTeam; + DamagedBaseStructures.ReachabilityTeam = CommanderTeam; + DamagedBaseStructures.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; + DamagedBaseStructures.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + DamagedBaseStructures.IncludeStatusFlags = STRUCTURE_STATUS_DAMAGED; + DamagedBaseStructures.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + DamagedBaseStructures.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); + + bBaseIsDamaged = AITAC_DeployableExistsAtLocation(AITAC_GetCommChairLocation(CommanderTeam), &DamagedBaseStructures); + + if (bBaseIsDamaged) + { + AvHAIDroppedItem* NearestWelder = AITAC_FindClosestItemToLocation(AITAC_GetCommChairLocation(CommanderTeam), DEPLOYABLE_ITEM_WELDER, CommanderTeam, AI_REACHABILITY_MARINE, 0.0f, UTIL_MetresToGoldSrcUnits(15.0f), false); + bool bPlayerHasWelder = false; + + if (!NearestWelder) + { + vector PlayersAtBase = AITAC_GetAllPlayersOfTeamInArea(CommanderTeam, AITAC_GetCommChairLocation(CommanderTeam), UTIL_MetresToGoldSrcUnits(15.0f), false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER); + + for (auto it = PlayersAtBase.begin(); it != PlayersAtBase.end(); it++) + { + AvHPlayer* ThisPlayer = (*it); + + if (PlayerHasWeapon(ThisPlayer, WEAPON_MARINE_WELDER)) + { + bPlayerHasWelder = true; + } + } + } + + if (!NearestWelder && !bPlayerHasWelder) + { + DeployableSearchFilter ArmouryFilter; + ArmouryFilter.DeployableTypes = (STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY); + ArmouryFilter.DeployableTeam = CommanderTeam; + ArmouryFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + ArmouryFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + + AvHAIBuildableStructure* NearestArmoury = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &ArmouryFilter); + + if (NearestArmoury) + { + Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), NearestArmoury->Location, UTIL_MetresToGoldSrcUnits(3.0f)); + bool bSuccess = AICOMM_DeployItem(pBot, DEPLOYABLE_ITEM_WELDER, DeployLocation); + + return bSuccess; + } + } + + } + + // Now work out how many welders we want on the team generally + int NumDesiredWelders = 1; + + if (!AICOMM_ShouldCommanderPrioritiseNodes(pBot)) + { + NumDesiredWelders = (int)ceilf((float)AIMGR_GetNumPlayersOnTeam(CommanderTeam) * 0.3f); + return false; + } + int NumTeamWelders = AITAC_GetNumWeaponsInPlay(CommanderTeam, WEAPON_MARINE_WELDER); + // Add additional welders to the team if we have hives or resource nodes which can only be reached with a welder + vector AllNodes = AITAC_GetAllResourceNodes(); for (auto it = AllNodes.begin(); it != AllNodes.end(); it++) @@ -1127,7 +1219,7 @@ bool AICOMM_CheckForNextSupplyAction(AvHAIPlayer* pBot) } } - NumDesiredWelders = imini(NumDesiredWelders, (AIMGR_GetNumPlayersOnTeam(CommanderTeam) / 2)); + NumDesiredWelders = imini(NumDesiredWelders, (int)(ceilf((float)AIMGR_GetNumPlayersOnTeam(CommanderTeam) * 0.5f))); if (NumTeamWelders < NumDesiredWelders) { @@ -1149,9 +1241,7 @@ bool AICOMM_CheckForNextSupplyAction(AvHAIPlayer* pBot) } // Don't drop stuff if we badly need resource nodes - if (AICOMM_ShouldCommanderPrioritiseNodes(pBot)) { return false; } - - if (pBot->Player->GetResources() < 30) { return false; } + if (AICOMM_ShouldCommanderPrioritiseNodes(pBot) && pBot->Player->GetResources() < 20) { return false; } int NumDesiredShotguns = (int)ceilf(AIMGR_GetNumPlayersOnTeam(CommanderTeam) * 0.33f); @@ -1232,9 +1322,94 @@ bool AICOMM_CheckForNextSupplyAction(AvHAIPlayer* pBot) } } - if (AITAC_ResearchIsComplete(CommanderTeam, TECH_RESEARCH_HEAVYARMOR)) - { + if (!AITAC_ResearchIsComplete(CommanderTeam, TECH_RESEARCH_HEAVYARMOR)) { return false; } + + DeployableSearchFilter StructureFilter; + StructureFilter.DeployableTypes = STRUCTURE_MARINE_ADVARMOURY; + StructureFilter.DeployableTeam = CommanderTeam; + StructureFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + AvHAIBuildableStructure* NearestAdvArmoury = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &StructureFilter); + + StructureFilter.DeployableTypes = STRUCTURE_MARINE_PROTOTYPELAB; + AvHAIBuildableStructure* NearestPrototypeLab = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &StructureFilter); + + if (!NearestAdvArmoury || !NearestPrototypeLab) { return false; } + + AvHAIDroppedItem* ExistingHA = AITAC_FindClosestItemToLocation(NearestPrototypeLab->Location, DEPLOYABLE_ITEM_HEAVYARMOUR, CommanderTeam, AI_REACHABILITY_MARINE, 0.0f, UTIL_MetresToGoldSrcUnits(5.0f), false); + AvHAIDroppedItem* ExistingHMG = AITAC_FindClosestItemToLocation(NearestAdvArmoury->Location, DEPLOYABLE_ITEM_HMG, CommanderTeam, AI_REACHABILITY_MARINE, 0.0f, UTIL_MetresToGoldSrcUnits(5.0f), false); + AvHAIDroppedItem* ExistingWelder = AITAC_FindClosestItemToLocation(NearestAdvArmoury->Location, DEPLOYABLE_ITEM_WELDER, CommanderTeam, AI_REACHABILITY_MARINE, 0.0f, UTIL_MetresToGoldSrcUnits(5.0f), false); + + if (ExistingHA && ExistingHMG && ExistingWelder) { return false; } + + vector NearbyPlayers = AITAC_GetAllPlayersOfClassInArea(CommanderTeam, NearestAdvArmoury->Location, UTIL_MetresToGoldSrcUnits(10.0f), false, pBot->Edict, AVH_USER3_MARINE_PLAYER); + + bool bDropWeapon = false; + bool bDropWelder = false; + + for (auto it = NearbyPlayers.begin(); it != NearbyPlayers.end(); it++) + { + edict_t* PlayerEdict = (*it); + AvHPlayer* PlayerRef = dynamic_cast(CBaseEntity::Instance(PlayerEdict)); + if (!PlayerEdict) { continue; } + + if (PlayerHasHeavyArmour(PlayerEdict) || PlayerHasJetpack(PlayerEdict)) + { + if (PlayerHasWeapon(PlayerRef, WEAPON_MARINE_MG) || UTIL_GetPlayerPrimaryWeapon(PlayerRef) == WEAPON_INVALID) + { + bDropWeapon = true; + } + else + { + if (!PlayerHasWeapon(PlayerRef, WEAPON_MARINE_WELDER)) + { + bDropWelder = true; + } + } + } + } + + if (!ExistingHA && !bDropWelder && !bDropWeapon) + { + Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), NearestPrototypeLab->Location, UTIL_MetresToGoldSrcUnits(3.0f)); + + if (vIsZero(DeployLocation)) + { + DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), NearestPrototypeLab->Location, UTIL_MetresToGoldSrcUnits(3.0f)); + } + + bool bSuccess = AICOMM_DeployItem(pBot, DEPLOYABLE_ITEM_HEAVYARMOUR, DeployLocation); + + return bSuccess; + } + + if (bDropWeapon && !ExistingHMG) + { + Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), NearestAdvArmoury->Location, UTIL_MetresToGoldSrcUnits(3.0f)); + + if (vIsZero(DeployLocation)) + { + DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), NearestAdvArmoury->Location, UTIL_MetresToGoldSrcUnits(3.0f)); + } + + bool bSuccess = AICOMM_DeployItem(pBot, DEPLOYABLE_ITEM_HMG, DeployLocation); + + return bSuccess; + } + + if (bDropWelder && !ExistingWelder) + { + Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), NearestAdvArmoury->Location, UTIL_MetresToGoldSrcUnits(3.0f)); + + if (vIsZero(DeployLocation)) + { + DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), NearestAdvArmoury->Location, UTIL_MetresToGoldSrcUnits(3.0f)); + } + + bool bSuccess = AICOMM_DeployItem(pBot, DEPLOYABLE_ITEM_WELDER, DeployLocation); + + return bSuccess; } return false; @@ -1649,17 +1824,17 @@ bool AICOMM_PerformNextSiegeHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinit { SiegeLocation = ExistingTF->Location; - NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), SiegeLocation, UTIL_MetresToGoldSrcUnits(5.0f)); + NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), ExistingTF->Location, 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)); + NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), ExistingTF->Location, 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)); + NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), ExistingTF->Location, UTIL_MetresToGoldSrcUnits(5.0f)); } } diff --git a/main/source/mod/AIPlayers/AvHAIConstants.h b/main/source/mod/AIPlayers/AvHAIConstants.h index e478b284..317a570c 100644 --- a/main/source/mod/AIPlayers/AvHAIConstants.h +++ b/main/source/mod/AIPlayers/AvHAIConstants.h @@ -103,6 +103,7 @@ typedef enum STRUCTURE_STATUS_PARASITED = 1 << 3, STRUCTURE_STATUS_UNDERATTACK = 1 << 4, STRUCTURE_STATUS_RESEARCHING = 1 << 5, + STRUCTURE_STATUS_DAMAGED = 1 << 6, STRUCTURE_STATUS_ALL = -1 } AvHAIStructureStatus; @@ -536,6 +537,14 @@ typedef struct _AVH_AI_PLAYER_MOVE_TASK bool bPathGenerated = false; } AvHAIPlayerMoveTask; +typedef struct _AVH_AI_STUCK_TRACKER +{ + Vector LastBotPosition = g_vecZero; + Vector MoveDestination = g_vecZero; + float TotalStuckTime = 0.0f; // Total time the bot has spent stuck + +} AvHAIPlayerStuckTracker; + // Contains the bot's current navigation info, such as current path typedef struct _NAV_STATUS { @@ -559,8 +568,6 @@ typedef struct _NAV_STATUS Vector StuckCheckMoveLocation = g_vecZero; // Where is the bot trying to go that we're checking if they're stuck? Vector UnstuckMoveLocation = g_vecZero; // If the bot is unable to find a path, blindly move here to try and fix the problem - Vector UnstuckMoveStartLocation = g_vecZero; // So the bot can track where it was when it started the unstuck movement - float UnstuckMoveLocationStartTime = 0.0f; // When did the bot start trying to move to UnstuckMoveLocation? Give up after certain amount of time float LandedTime = 0.0f; // When the bot last landed after a fall/jump. float LeapAttemptedTime = 0.0f; // When the bot last attempted to leap/blink. Avoid spam that sends it flying around too fast @@ -583,6 +590,8 @@ typedef struct _NAV_STATUS nav_profile NavProfile; bool bNavProfileChanged = false; + AvHAIPlayerStuckTracker StuckInfo; + unsigned int SpecialMovementFlags = 0; // Any special movement flags required for this path (e.g. needs a welder, needs a jetpack etc.) AvHAIPlayerMoveTask MovementTask; diff --git a/main/source/mod/AIPlayers/AvHAIHelper.cpp b/main/source/mod/AIPlayers/AvHAIHelper.cpp index ce694488..13241317 100644 --- a/main/source/mod/AIPlayers/AvHAIHelper.cpp +++ b/main/source/mod/AIPlayers/AvHAIHelper.cpp @@ -158,9 +158,9 @@ Vector UTIL_GetFloorUnderEntity(const edict_t* Edict) TraceResult hit; - Vector EntityCentre = UTIL_GetCentreOfEntity(Edict); + Vector EntityCentre = UTIL_GetCentreOfEntity(Edict) + Vector(0.0f, 0.0f, 1.0f); - UTIL_TraceHull(EntityCentre, (EntityCentre - Vector(0.0f, 0.0f, 1000.0f)), ignore_monsters, GetPlayerHullIndex(Edict), Edict->v.pContainingEntity, &hit); + UTIL_TraceHull(EntityCentre, (EntityCentre - Vector(0.0f, 0.0f, 1000.0f)), ignore_monsters, head_hull, Edict->v.pContainingEntity, &hit); if (hit.flFraction < 1.0f) { diff --git a/main/source/mod/AIPlayers/AvHAINavigation.cpp b/main/source/mod/AIPlayers/AvHAINavigation.cpp index 9dc1a3a8..99f24c2c 100644 --- a/main/source/mod/AIPlayers/AvHAINavigation.cpp +++ b/main/source/mod/AIPlayers/AvHAINavigation.cpp @@ -442,6 +442,8 @@ unsigned int UTIL_AddTemporaryObstacle(unsigned int NavMeshIndex, const Vector L void UTIL_AddStructureTemporaryObstacles(AvHAIBuildableStructure* Structure) { + if (Structure->StructureType == STRUCTURE_MARINE_DEPLOYEDMINE) { return; } + bool bCollideWithPlayers = UTIL_ShouldStructureCollide(Structure->StructureType); float Radius = UTIL_GetStructureRadiusForObstruction(Structure->StructureType); @@ -3821,6 +3823,7 @@ void LiftMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint) void PhaseGateMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint) { DeployableSearchFilter PGFilter; + PGFilter.DeployableTeam = pBot->Player->GetTeam(); PGFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE; PGFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(2.0f); PGFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; @@ -3945,7 +3948,7 @@ bool IsBotOffPath(const AvHAIPlayer* pBot) { Vector ExactJumpTarget = UTIL_GetGroundLocation(MoveTo); - if (pBot->BotNavInfo.IsOnGround && (MoveTo.z - pBot->CurrentFloorPosition.z) > max_player_jump_height) + if (pBot->BotNavInfo.IsOnGround && (ExactJumpTarget.z - pBot->CurrentFloorPosition.z) > max_player_jump_height) { return true; } @@ -4256,6 +4259,7 @@ void MoveDirectlyTo(AvHAIPlayer* pBot, const Vector Destination) HandlePlayerAvoidance(pBot, Destination); BotMovementInputs(pBot); + } @@ -4952,12 +4956,6 @@ bool AbortCurrentMove(AvHAIPlayer* pBot, const Vector NewDestination) { if (pBot->BotNavInfo.CurrentPath.size() == 0 || pBot->BotNavInfo.NavProfile.bFlyingProfile) { return true; } - if (IsBotPermaStuck(pBot)) - { - BotSuicide(pBot); - return false; - } - Vector MoveFrom = pBot->BotNavInfo.CurrentPathPoint->FromLocation; Vector MoveTo = pBot->BotNavInfo.CurrentPathPoint->Location; unsigned int flag = pBot->BotNavInfo.CurrentPathPoint->flag; @@ -5092,20 +5090,37 @@ bool AbortCurrentMove(AvHAIPlayer* pBot, const Vector NewDestination) return false; } -bool IsBotPermaStuck(AvHAIPlayer* pBot) +void UpdateBotStuck(AvHAIPlayer* pBot) { - if (MAX_BOT_STUCK_TIME <= 0.1f) { return false; } - - if (vIsZero(pBot->LastPosition) || vDist3DSq(pBot->Edict->v.origin, pBot->LastPosition) > sqrf(32.0f)) + if (vIsZero(pBot->desiredMovementDir)) { - pBot->TimeSinceLastMovement = 0.0f; - pBot->LastPosition = pBot->Edict->v.origin; - return false; + return; } - pBot->TimeSinceLastMovement += AIMGR_GetBotDeltaTime(); + bool bIsFollowingPath = (pBot->BotNavInfo.CurrentPath.size() > 0 && pBot->BotNavInfo.CurrentPathPoint != pBot->BotNavInfo.CurrentPath.end()); - return (pBot->TimeSinceLastMovement >= 30.0f); + bool bDist3D = pBot->BotNavInfo.NavProfile.bFlyingProfile || (bIsFollowingPath && (pBot->BotNavInfo.CurrentPathPoint->flag == SAMPLE_POLYFLAGS_LADDER || pBot->BotNavInfo.CurrentPathPoint->flag == SAMPLE_POLYFLAGS_WALLCLIMB)); + + float DistFromLastPoint = (bDist3D) ? vDist3DSq(pBot->Edict->v.origin, pBot->BotNavInfo.StuckInfo.LastBotPosition) : vDist2DSq(pBot->Edict->v.origin, pBot->BotNavInfo.StuckInfo.LastBotPosition); + + if (DistFromLastPoint >= sqrf(8.0f)) + { + pBot->BotNavInfo.StuckInfo.TotalStuckTime = 0.0f; + pBot->BotNavInfo.StuckInfo.LastBotPosition = pBot->Edict->v.origin; + } + else + { + pBot->BotNavInfo.StuckInfo.TotalStuckTime += AIMGR_GetBotDeltaTime(); + } + + if (pBot->BotNavInfo.StuckInfo.TotalStuckTime > 0.25f) + { + BotJump(pBot); + if (!IsPlayerSkulk(pBot->Edict)) + { + pBot->Button |= IN_DUCK; + } + } } void SetBaseNavProfile(AvHAIPlayer* pBot) @@ -5355,7 +5370,7 @@ void OnosUpdateBotMoveProfile(AvHAIPlayer* pBot, BotMoveStyle MoveStyle) bool MoveTo(AvHAIPlayer* pBot, const Vector Destination, const BotMoveStyle MoveStyle, const float MaxAcceptableDist) { - if (vIsZero(Destination) || (vDist2D(pBot->Edict->v.origin, Destination) <= 8.0f && (fabs(pBot->CollisionHullBottomLocation.z - Destination.z) < 50.0f))) + if (vIsZero(Destination) || (vDist2D(pBot->Edict->v.origin, Destination) <= 6.0f && (fabs(pBot->CollisionHullBottomLocation.z - Destination.z) < 50.0f))) { ClearBotMovement(pBot); return true; @@ -5449,12 +5464,6 @@ bool MoveTo(AvHAIPlayer* pBot, const Vector Destination, const BotMoveStyle Move } } - if (IsBotPermaStuck(pBot)) - { - BotSuicide(pBot); - return false; - } - ClearBotPath(pBot); return false; } @@ -5494,12 +5503,6 @@ bool MoveTo(AvHAIPlayer* pBot, const Vector Destination, const BotMoveStyle Move } } - if (IsBotPermaStuck(pBot)) - { - BotSuicide(pBot); - return false; - } - if (pBot->Edict->v.flags & FL_INWATER) { BotFollowSwimPath(pBot); @@ -5633,7 +5636,11 @@ void SkipAheadInFlightPath(AvHAIPlayer* pBot) void BotFollowFlightPath(AvHAIPlayer* pBot) { - if (pBot->BotNavInfo.CurrentPath.size() == 0) { return; } + if (pBot->BotNavInfo.CurrentPath.size() == 0) + { + ClearBotStuck(pBot); + return; + } nav_status* BotNavInfo = &pBot->BotNavInfo; edict_t* pEdict = pBot->Edict; @@ -5680,15 +5687,6 @@ void BotFollowFlightPath(AvHAIPlayer* pBot) Vector MoveDir = UTIL_GetVectorNormal(CurrentMoveDest - MoveFrom); - if (IsBotStuck(pBot, CurrentMoveDest)) - { - if (BotNavInfo->TotalStuckTime > 3.0f) - { - ClearBotPath(pBot); - return; - } - } - float CurrentSpeed = vSize3D(pEdict->v.velocity); if (vDist2DSq(pEdict->v.origin, MoveFrom) > sqrf(100.0f) && vDist2DSq(pEdict->v.origin, CurrentMoveDest) > sqrf(100.0f)) @@ -5854,6 +5852,7 @@ void BotFollowPath(AvHAIPlayer* pBot) nav_status* BotNavInfo = &pBot->BotNavInfo; edict_t* pEdict = pBot->Edict; + // If we've reached our current path point if (HasBotReachedPathPoint(pBot)) { @@ -5877,33 +5876,8 @@ void BotFollowPath(AvHAIPlayer* pBot) Vector MoveTo = BotNavInfo->CurrentPathPoint->Location; - unsigned int CurrentFlag = BotNavInfo->CurrentPathPoint->flag; - - bool bIsUsingPhaseGate = (CurrentFlag == SAMPLE_POLYFLAGS_TEAM1PHASEGATE || CurrentFlag == SAMPLE_POLYFLAGS_TEAM2PHASEGATE); - NewMove(pBot); - if (!bIsUsingPhaseGate && IsBotStuck(pBot, MoveTo)) - { - if (BotNavInfo->TotalStuckTime > 3.0f) - { - ClearBotPath(pBot); - return; - } - - // If onos, ducking is usually a good way to get unstuck... - if (IsPlayerOnos(pBot->Edict)) - { - pBot->Button |= IN_DUCK; - } - - if (!IsPlayerClimbingWall(pBot->Edict) && !IsPlayerOnLadder(pBot->Edict)) - { - PerformUnstuckMove(pBot, MoveTo); - return; - } - } - } void PerformUnstuckMove(AvHAIPlayer* pBot, const Vector MoveDestination) @@ -5971,9 +5945,17 @@ void PerformUnstuckMove(AvHAIPlayer* pBot, const Vector MoveDestination) bool IsBotStuck(AvHAIPlayer* pBot, const Vector MoveDestination) { // If invalid move destination then bail out - if (vIsZero(MoveDestination) || vIsZero(pBot->desiredMovementDir)) { return false; } + if (vIsZero(MoveDestination) || vDist2DSq(pBot->Edict->v.origin, MoveDestination) < sqrf(32.0f)) + { + pBot->BotNavInfo.LastStuckCheckTime = gpGlobals->time; + return false; + } - if (pBot->BotNavInfo.CurrentPathPoint->flag == SAMPLE_POLYFLAGS_LIFT) { return false; } + if (pBot->BotNavInfo.CurrentPathPoint->flag == SAMPLE_POLYFLAGS_LIFT || pBot->BotNavInfo.CurrentPathPoint->flag == SAMPLE_POLYFLAGS_TEAM1PHASEGATE || pBot->BotNavInfo.CurrentPathPoint->flag == SAMPLE_POLYFLAGS_TEAM2PHASEGATE) + { + pBot->BotNavInfo.LastStuckCheckTime = gpGlobals->time; + return false; + } // If moving to a new destination set a new distance baseline. We do not reset the stuck timer if (MoveDestination != pBot->BotNavInfo.StuckCheckMoveLocation) @@ -5985,7 +5967,7 @@ bool IsBotStuck(AvHAIPlayer* pBot, const Vector MoveDestination) } // If first time performing a stuck check, set a baseline time and return false - if (pBot->BotNavInfo.LastStuckCheckTime == 0.0f) + if (pBot->BotNavInfo.LastStuckCheckTime == 0.0f || gpGlobals->time - pBot->BotNavInfo.LastStuckCheckTime > 3.0f) { pBot->BotNavInfo.LastStuckCheckTime = gpGlobals->time; return false; @@ -6381,8 +6363,6 @@ void ClearBotStuck(AvHAIPlayer* pBot) pBot->BotNavInfo.LastStuckCheckTime = gpGlobals->time; pBot->BotNavInfo.TotalStuckTime = 0.0f; pBot->BotNavInfo.UnstuckMoveLocation = g_vecZero; - pBot->BotNavInfo.UnstuckMoveLocationStartTime = 0.0f; - pBot->BotNavInfo.UnstuckMoveStartLocation = g_vecZero; pBot->BotNavInfo.StuckCheckMoveLocation = g_vecZero; } @@ -6677,8 +6657,6 @@ void ClearBotPath(AvHAIPlayer* pBot) void ClearBotStuckMovement(AvHAIPlayer* pBot) { pBot->BotNavInfo.UnstuckMoveLocation = g_vecZero; - pBot->BotNavInfo.UnstuckMoveLocationStartTime = 0.0f; - pBot->BotNavInfo.UnstuckMoveStartLocation = g_vecZero; } void BotMovementInputs(AvHAIPlayer* pBot) diff --git a/main/source/mod/AIPlayers/AvHAINavigation.h b/main/source/mod/AIPlayers/AvHAINavigation.h index fde63030..9b0d94aa 100644 --- a/main/source/mod/AIPlayers/AvHAINavigation.h +++ b/main/source/mod/AIPlayers/AvHAINavigation.h @@ -309,6 +309,8 @@ bool BotRecalcPath(AvHAIPlayer* pBot, const Vector Destination); */ bool MoveTo(AvHAIPlayer* pBot, const Vector Destination, const BotMoveStyle MoveStyle, const float MaxAcceptableDist = max_ai_use_reach); +void UpdateBotStuck(AvHAIPlayer* pBot); + // Used by the MoveTo command, handles the bot's movement and inputs to follow a path it has calculated for itself void BotFollowPath(AvHAIPlayer* pBot); void BotFollowFlightPath(AvHAIPlayer* pBot); @@ -316,8 +318,6 @@ void BotFollowSwimPath(AvHAIPlayer* pBot); void SkipAheadInFlightPath(AvHAIPlayer* pBot); -// If the bot has been unable to move more than 32 units in the last MaxStuckTime seconds (must be trying to move somewhere) then returns true -bool IsBotPermaStuck(AvHAIPlayer* pBot); // Walks directly towards the destination. No path finding, just raw movement input. Will detect obstacles and try to jump/duck under them. void MoveDirectlyTo(AvHAIPlayer* pBot, const Vector Destination); diff --git a/main/source/mod/AIPlayers/AvHAIPlayer.cpp b/main/source/mod/AIPlayers/AvHAIPlayer.cpp index b7b25a22..7ec3706d 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayer.cpp +++ b/main/source/mod/AIPlayers/AvHAIPlayer.cpp @@ -1393,7 +1393,7 @@ void BotUpdateView(AvHAIPlayer* pBot) if (bInFOV && (bHasLOS || bIsTracked)) { - Vector FloorLocation = UTIL_GetFloorUnderEntity(Enemy); + Vector FloorLocation = UTIL_GetEntityGroundLocation(Enemy); Vector BotVelocity = Enemy->v.velocity; if (gpGlobals->time >= TrackingInfo->NextVelocityUpdateTime) @@ -1597,9 +1597,12 @@ void StartNewBotFrame(AvHAIPlayer* pBot) pBot->BotNavInfo.LastNavMeshCheckPosition = pBot->CurrentFloorPosition; } + UpdateBotStuck(pBot); + pBot->LookTargetLocation = ZERO_VECTOR; pBot->MoveLookLocation = ZERO_VECTOR; pBot->LookTarget = nullptr; + pBot->desiredMovementDir = ZERO_VECTOR; pBot->DesiredCombatWeapon = WEAPON_INVALID; pBot->DesiredMoveWeapon = WEAPON_INVALID; @@ -1634,8 +1637,6 @@ void StartNewBotFrame(AvHAIPlayer* pBot) SetBaseNavProfile(pBot); } - //UpdateBotMoveProfile(pBot, pBot->BotNavInfo.MoveStyle); - if (IsPlayerMarine(pBot->Edict)) { UpdateCommanderOrders(pBot); @@ -1669,6 +1670,46 @@ void StartNewBotFrame(AvHAIPlayer* pBot) void CustomThink(AvHAIPlayer* pBot) { + if (IsPlayerMarine(pBot->Player)) + { + if (!PlayerHasWeapon(pBot->Player, WEAPON_MARINE_MINES)) + { + AvHAIDroppedItem* NearestMines = AITAC_FindClosestItemToLocation(pBot->Edict->v.origin, DEPLOYABLE_ITEM_MINES, pBot->Player->GetTeam(), AI_REACHABILITY_MARINE, 0.0f, 5000.0f, false); + + if (NearestMines) + { + AITASK_SetPickupTask(pBot, &pBot->PrimaryBotTask, NearestMines->edict, true); + } + } + else + { + DeployableSearchFilter MineStructureFilter; + MineStructureFilter.DeployableTeam = pBot->Player->GetTeam(); + MineStructureFilter.DeployableTypes = STRUCTURE_MARINE_INFANTRYPORTAL; + MineStructureFilter.ReachabilityTeam = pBot->Player->GetTeam(); + MineStructureFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; + + AvHAIBuildableStructure* NearestIP = AITAC_FindClosestDeployableToLocation(pBot->Edict->v.origin, &MineStructureFilter); + + if (NearestIP) + { + AITASK_SetMineStructureTask(pBot, &pBot->PrimaryBotTask, NearestIP->edict, true); + } + } + + BotProgressTask(pBot, &pBot->PrimaryBotTask); + + AvHAIWeapon DesiredWeapon = (pBot->DesiredMoveWeapon != WEAPON_INVALID) ? pBot->DesiredMoveWeapon : pBot->DesiredCombatWeapon; + + if (DesiredWeapon != WEAPON_NONE && GetPlayerCurrentWeapon(pBot->Player) != DesiredWeapon) + { + BotSwitchToWeapon(pBot, DesiredWeapon); + } + + return; + } + + if (IsPlayerMarine(pBot->Player)) { pBot->CurrentEnemy = BotGetNextEnemyTarget(pBot); @@ -1682,7 +1723,7 @@ void CustomThink(AvHAIPlayer* pBot) MarineCombatThink(pBot); } - AvHAIWeapon DesiredWeapon = (pBot->DesiredMoveWeapon != WEAPON_NONE) ? pBot->DesiredMoveWeapon : pBot->DesiredCombatWeapon; + AvHAIWeapon DesiredWeapon = (pBot->DesiredMoveWeapon != WEAPON_INVALID) ? pBot->DesiredMoveWeapon : pBot->DesiredCombatWeapon; if (DesiredWeapon != WEAPON_INVALID && GetPlayerCurrentWeapon(pBot->Player) != DesiredWeapon) { @@ -1692,14 +1733,14 @@ void CustomThink(AvHAIPlayer* pBot) return; } - if (!IsPlayerLerk(pBot->Edict)) + if (!IsPlayerOnos(pBot->Edict)) { - if (pBot->Player->GetResources() < BALANCE_VAR(kLerkCost)) + if (pBot->Player->GetResources() < BALANCE_VAR(kOnosCost)) { - pBot->Player->GiveResources(30.0f); + pBot->Player->GiveResources(70.0f); } - BotEvolveLifeform(pBot, pBot->Edict->v.origin, ALIEN_LIFEFORM_THREE); + BotEvolveLifeform(pBot, pBot->Edict->v.origin, ALIEN_LIFEFORM_FIVE); return; } @@ -1713,17 +1754,15 @@ void CustomThink(AvHAIPlayer* pBot) else { AlienCombatThink(pBot); - } + } - AvHAIWeapon DesiredWeapon = (pBot->DesiredMoveWeapon != WEAPON_NONE) ? pBot->DesiredMoveWeapon : pBot->DesiredCombatWeapon; + AvHAIWeapon DesiredWeapon = (pBot->DesiredMoveWeapon != WEAPON_INVALID) ? pBot->DesiredMoveWeapon : pBot->DesiredCombatWeapon; - if (DesiredWeapon != WEAPON_NONE && GetPlayerCurrentWeapon(pBot->Player) != DesiredWeapon) + if (DesiredWeapon != WEAPON_INVALID && GetPlayerCurrentWeapon(pBot->Player) != DesiredWeapon) { BotSwitchToWeapon(pBot, DesiredWeapon); } - - } void DroneThink(AvHAIPlayer* pBot) @@ -2078,7 +2117,9 @@ AvHAICombatStrategy GetSkulkCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_sta float FacingDot = UTIL_GetDotProduct2D(EnemyFacing, OurOrientation); - if (NumEnemyAllies <= NumFriends && (DistToEnemy < sqrf(UTIL_MetresToGoldSrcUnits(10.0f)) || FacingDot < 0.4f)) + bool bIsEnemyDistracted = FacingDot < 0.4f || CurrentEnemy->EnemyEdict->v.weaponmodel == 0 || IsPlayerReloading(pBot->Player); + + if ((NumEnemyAllies <= NumFriends || NumFriends >= 2) || (NumFriends >= NumEnemyAllies - 1 && bIsEnemyDistracted)) { return COMBAT_STRATEGY_ATTACK; } @@ -2101,6 +2142,8 @@ AvHAICombatStrategy GetSkulkCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_sta AvHAICombatStrategy GetGorgeCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_status* CurrentEnemy) { + AvHTeamNumber BotTeam = pBot->Player->GetTeam(); + float CurrentHealthPercent = GetPlayerOverallHealthPercent(pBot->Edict); if (pBot->CurrentCombatStrategy == COMBAT_STRATEGY_RETREAT) @@ -2111,7 +2154,35 @@ AvHAICombatStrategy GetGorgeCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_sta } } - return COMBAT_STRATEGY_ATTACK; + vector Allies = AITAC_GetAllPlayersOnTeamWithLOS(BotTeam, pBot->Edict->v.origin, UTIL_MetresToGoldSrcUnits(10.0f), pBot->Edict); + bool bHasBackup = false; + + Vector EnemyLocation = (CurrentEnemy->bHasLOS) ? CurrentEnemy->EnemyEdict->v.origin : CurrentEnemy->LastVisibleLocation; + + for (auto it = Allies.begin(); it != Allies.end(); it++) + { + AvHPlayer* ThisAlly = (*it); + edict_t* ThisAllyEdict = ThisAlly->edict(); + + if (ThisAllyEdict->v.iuser3 == AVH_USER3_ALIEN_PLAYER2) { continue; } + + if (vDist2DSq(ThisAllyEdict->v.origin, EnemyLocation) < vDist2DSq(pBot->Edict->v.origin, EnemyLocation)) + { + if (UTIL_PlayerHasLOSToLocation(ThisAllyEdict, EnemyLocation, UTIL_MetresToGoldSrcUnits(20.0f))) + { + bHasBackup = true; + } + } + } + + if (bHasBackup) + { + return (CurrentHealthPercent > 0.5f) ? COMBAT_STRATEGY_ATTACK : COMBAT_STRATEGY_SKIRMISH; + } + else + { + return (CurrentHealthPercent > 0.5f) ? COMBAT_STRATEGY_SKIRMISH : COMBAT_STRATEGY_RETREAT; + } } AvHAICombatStrategy GetLerkCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_status* CurrentEnemy) @@ -2250,7 +2321,7 @@ AvHAICombatStrategy GetFadeCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_stat // First, check if we should get the hell out of dodge float RetreatHealth = 0.33f; - if (DistToEnemy < sqrf(UTIL_MetresToGoldSrcUnits(5.0f)) && bEnemyHasDeadlyWeapon) + if ((NumAllies > 1 || bEnemyHasDeadlyWeapon) && vDist2DSq(pBot->Edict->v.origin, pBot->LastSafeLocation) > sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) { RetreatHealth = 0.5f; } @@ -2439,7 +2510,14 @@ AvHAIPlayerTask* AIPlayerGetNextTask(AvHAIPlayer* pBot) } else { - return &pBot->CommanderTask; + if (pBot->WantsAndNeedsTask.bTaskIsUrgent) + { + return &pBot->WantsAndNeedsTask; + } + else + { + return &pBot->CommanderTask; + } } } @@ -3172,71 +3250,183 @@ void AIPlayerSetWantsAndNeedsMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task bool bNeedsHealth = pBot->Edict->v.health < (pBot->Edict->v.max_health * 0.9f); bool bNeedsAmmo = UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) < (UTIL_GetPlayerPrimaryMaxAmmoReserve(pBot->Player) * 0.9f); - if (!bNeedsHealth && !bNeedsAmmo) + if (bNeedsHealth || bNeedsAmmo) { - AITASK_ClearBotTask(pBot, Task); - return; - } + bool bTaskIsUrgent = (pBot->Edict->v.health < (pBot->Edict->v.max_health * 0.7f)) || (UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) < UTIL_GetPlayerPrimaryWeaponMaxClipSize(pBot->Player)); - bool bTaskIsUrgent = (pBot->Edict->v.health < (pBot->Edict->v.max_health * 0.7f)) || (UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) < UTIL_GetPlayerPrimaryWeaponMaxClipSize(pBot->Player)); + float SearchRadius = (bTaskIsUrgent) ? UTIL_MetresToGoldSrcUnits(5.0f) : UTIL_MetresToGoldSrcUnits(10.0f); - float SearchRadius = (bTaskIsUrgent) ? UTIL_MetresToGoldSrcUnits(5.0f) : UTIL_MetresToGoldSrcUnits(10.0f); + AvHAIDroppedItem* NearestHealthPack = AITAC_FindClosestItemToLocation(pBot->Edict->v.origin, DEPLOYABLE_ITEM_HEALTHPACK, BotTeam, pBot->BotNavInfo.NavProfile.ReachabilityFlag, 0.0f, SearchRadius, false); + AvHAIDroppedItem* NearestAmmoPack = AITAC_FindClosestItemToLocation(pBot->Edict->v.origin, DEPLOYABLE_ITEM_AMMO, BotTeam, pBot->BotNavInfo.NavProfile.ReachabilityFlag, 0.0f, SearchRadius, false); - AvHAIDroppedItem* NearestHealthPack = AITAC_FindClosestItemToLocation(pBot->Edict->v.origin, DEPLOYABLE_ITEM_HEALTHPACK, BotTeam, pBot->BotNavInfo.NavProfile.ReachabilityFlag, 0.0f, SearchRadius, false); - AvHAIDroppedItem* NearestAmmoPack = AITAC_FindClosestItemToLocation(pBot->Edict->v.origin, DEPLOYABLE_ITEM_AMMO, BotTeam, pBot->BotNavInfo.NavProfile.ReachabilityFlag, 0.0f, SearchRadius, false); - - if (bNeedsHealth && NearestHealthPack) - { - AITASK_SetPickupTask(pBot, Task, NearestHealthPack->edict, bTaskIsUrgent); - return; - } - - if (bNeedsAmmo && NearestAmmoPack) - { - AITASK_SetPickupTask(pBot, Task, NearestAmmoPack->edict, bTaskIsUrgent); - return; - } - - DeployableSearchFilter NearestArmouryFilter; - NearestArmouryFilter.DeployableTypes = (STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY); - NearestArmouryFilter.DeployableTeam = pBot->Player->GetTeam(); - NearestArmouryFilter.ReachabilityTeam = pBot->Player->GetTeam(); - NearestArmouryFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; - NearestArmouryFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; - NearestArmouryFilter.MaxSearchRadius = (bTaskIsUrgent) ? UTIL_MetresToGoldSrcUnits(20.0f) : UTIL_MetresToGoldSrcUnits(5.0f); - - AvHAIBuildableStructure* NearestArmoury = AITAC_FindClosestDeployableToLocation(pBot->Edict->v.origin, &NearestArmouryFilter); - - // If we don't really need health or ammo and there isn't a handy armoury nearby, just get on with things - if (!NearestArmoury && !bTaskIsUrgent) - { - AITASK_ClearBotTask(pBot, Task); - return; - } - - // We really need some health or ammo, either hit the armoury, or ask for a resupply - if (NearestArmoury) - { - Task->TaskType = TASK_RESUPPLY; - Task->bTaskIsUrgent = true; - Task->TaskLocation = NearestArmoury->Location; - Task->TaskTarget = NearestArmoury->edict; - return; - } - else - { - AITASK_ClearBotTask(pBot, Task); - if (bNeedsHealth) + if (bNeedsHealth && NearestHealthPack) { - AIPlayerRequestHealth(pBot); + AITASK_SetPickupTask(pBot, Task, NearestHealthPack->edict, bTaskIsUrgent); return; } - if (bNeedsAmmo) + if (bNeedsAmmo && NearestAmmoPack) { - AIPlayerRequestAmmo(pBot); + AITASK_SetPickupTask(pBot, Task, NearestAmmoPack->edict, bTaskIsUrgent); return; } + + DeployableSearchFilter NearestArmouryFilter; + NearestArmouryFilter.DeployableTypes = (STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY); + NearestArmouryFilter.DeployableTeam = pBot->Player->GetTeam(); + NearestArmouryFilter.ReachabilityTeam = pBot->Player->GetTeam(); + NearestArmouryFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; + NearestArmouryFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + NearestArmouryFilter.MaxSearchRadius = (bTaskIsUrgent) ? UTIL_MetresToGoldSrcUnits(20.0f) : UTIL_MetresToGoldSrcUnits(5.0f); + + AvHAIBuildableStructure* NearestArmoury = AITAC_FindClosestDeployableToLocation(pBot->Edict->v.origin, &NearestArmouryFilter); + + // We really need some health or ammo, either hit the armoury, or ask for a resupply + if (NearestArmoury) + { + Task->TaskType = TASK_RESUPPLY; + Task->bTaskIsUrgent = true; + Task->TaskLocation = NearestArmoury->Location; + Task->TaskTarget = NearestArmoury->edict; + return; + } + else + { + if (bTaskIsUrgent) + { + if (bNeedsHealth) + { + AIPlayerRequestHealth(pBot); + } + + if (bNeedsAmmo) + { + AIPlayerRequestAmmo(pBot); + } + } + } + } + + if (!PlayerHasEquipment(pBot->Edict)) + { + AvHAIDroppedItem* NearbyHA = AITAC_FindClosestItemToLocation(pBot->Edict->v.origin, DEPLOYABLE_ITEM_HEAVYARMOUR, BotTeam, pBot->BotNavInfo.NavProfile.ReachabilityFlag, 0.0f, UTIL_MetresToGoldSrcUnits(10.0f), true); + + if (NearbyHA) + { + vector NearbyPlayers = AITAC_GetAllPlayersOfTeamInArea(BotTeam, NearbyHA->Location, UTIL_MetresToGoldSrcUnits(5.0f), false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER); + bool bHumanNearby = false; + + for (auto it = NearbyPlayers.begin(); it != NearbyPlayers.end(); it++) + { + AvHPlayer* ThisPlayer = (*it); + edict_t* PlayerEdict = ThisPlayer->edict(); + + if (IsPlayerActiveInGame(PlayerEdict) && !PlayerHasEquipment(PlayerEdict) && !IsPlayerBot(PlayerEdict)) + { + bHumanNearby = true; + break; + } + } + + if (!bHumanNearby) + { + AITASK_SetPickupTask(pBot, Task, NearbyHA->edict, vDist2DSq(pBot->Edict->v.origin, NearbyHA->Location) < sqrf(UTIL_MetresToGoldSrcUnits(5.0f))); + return; + } + } + } + + if (PlayerHasEquipment(pBot->Edict)) + { + if (!PlayerHasSpecialWeapon(pBot->Player)) + { + AvHAIDroppedItem* NearbyHMG = AITAC_FindClosestItemToLocation(pBot->Edict->v.origin, DEPLOYABLE_ITEM_HMG, BotTeam, pBot->BotNavInfo.NavProfile.ReachabilityFlag, 0.0f, UTIL_MetresToGoldSrcUnits(10.0f), true); + AvHAIDroppedItem* NearbyGL = AITAC_FindClosestItemToLocation(pBot->Edict->v.origin, DEPLOYABLE_ITEM_GRENADELAUNCHER, BotTeam, pBot->BotNavInfo.NavProfile.ReachabilityFlag, 0.0f, UTIL_MetresToGoldSrcUnits(10.0f), true); + + AvHAIDroppedItem* NearbyWeapon = (NearbyHMG != nullptr) ? NearbyHMG : NearbyGL; + + if (NearbyWeapon) + { + vector NearbyPlayers = AITAC_GetAllPlayersOfTeamInArea(BotTeam, NearbyWeapon->Location, UTIL_MetresToGoldSrcUnits(5.0f), false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER); + bool bHumanNearby = false; + + for (auto it = NearbyPlayers.begin(); it != NearbyPlayers.end(); it++) + { + AvHPlayer* ThisPlayer = (*it); + edict_t* PlayerEdict = ThisPlayer->edict(); + + if (IsPlayerActiveInGame(PlayerEdict) && !PlayerHasSpecialWeapon(ThisPlayer) && !IsPlayerBot(PlayerEdict)) + { + bHumanNearby = true; + break; + } + } + + if (!bHumanNearby) + { + AITASK_SetPickupTask(pBot, Task, NearbyWeapon->edict, vDist2DSq(pBot->Edict->v.origin, NearbyWeapon->Location) < sqrf(UTIL_MetresToGoldSrcUnits(5.0f))); + return; + } + } + } + } + + if (!PlayerHasWeapon(pBot->Player, WEAPON_MARINE_WELDER)) + { + AvHAIDroppedItem* NearbyWeapon = AITAC_FindClosestItemToLocation(pBot->Edict->v.origin, DEPLOYABLE_ITEM_WELDER, BotTeam, pBot->BotNavInfo.NavProfile.ReachabilityFlag, 0.0f, UTIL_MetresToGoldSrcUnits(10.0f), true); + + if (NearbyWeapon) + { + vector NearbyPlayers = AITAC_GetAllPlayersOfTeamInArea(BotTeam, NearbyWeapon->Location, UTIL_MetresToGoldSrcUnits(5.0f), false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER); + bool bHumanNearby = false; + + for (auto it = NearbyPlayers.begin(); it != NearbyPlayers.end(); it++) + { + AvHPlayer* ThisPlayer = (*it); + edict_t* PlayerEdict = ThisPlayer->edict(); + + if (IsPlayerActiveInGame(PlayerEdict) && !PlayerHasWeapon(ThisPlayer, WEAPON_MARINE_WELDER) && !IsPlayerBot(PlayerEdict)) + { + bHumanNearby = true; + break; + } + } + + if (!bHumanNearby) + { + AITASK_SetPickupTask(pBot, Task, NearbyWeapon->edict, vDist2DSq(pBot->Edict->v.origin, NearbyWeapon->Location) < sqrf(UTIL_MetresToGoldSrcUnits(5.0f))); + return; + } + } + } + + if (!PlayerHasSpecialWeapon(pBot->Player)) + { + AvHAIDroppedItem* NearbyWeapon = AITAC_FindClosestItemToLocation(pBot->Edict->v.origin, DEPLOYABLE_ITEM_SHOTGUN, BotTeam, pBot->BotNavInfo.NavProfile.ReachabilityFlag, 0.0f, UTIL_MetresToGoldSrcUnits(10.0f), true); + + if (NearbyWeapon) + { + vector NearbyPlayers = AITAC_GetAllPlayersOfTeamInArea(BotTeam, NearbyWeapon->Location, UTIL_MetresToGoldSrcUnits(5.0f), false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER); + bool bHumanNearby = false; + + for (auto it = NearbyPlayers.begin(); it != NearbyPlayers.end(); it++) + { + AvHPlayer* ThisPlayer = (*it); + edict_t* PlayerEdict = ThisPlayer->edict(); + + if (IsPlayerActiveInGame(PlayerEdict) && !PlayerHasSpecialWeapon(ThisPlayer) && !IsPlayerBot(PlayerEdict)) + { + bHumanNearby = true; + break; + } + } + + if (!bHumanNearby) + { + AITASK_SetPickupTask(pBot, Task, NearbyWeapon->edict, vDist2DSq(pBot->Edict->v.origin, NearbyWeapon->Location) < sqrf(UTIL_MetresToGoldSrcUnits(5.0f))); + return; + } + } } } @@ -3266,11 +3456,19 @@ void AIPlayerRequestOrder(AvHAIPlayer* pBot) void AIPlayerSetSecondaryMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { + // Don't pick a new build target if we're already near one + if (Task->TaskType == TASK_BUILD && vDist2DSq(pBot->Edict->v.origin, Task->TaskTarget->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(3.0f))) + { + return; + } + + AvHTeamNumber BotTeam = pBot->Player->GetTeam(); + // Find any nearby unbuilt structures DeployableSearchFilter UnbuiltFilter; UnbuiltFilter.DeployableTypes = SEARCH_ALL_MARINE_STRUCTURES; - UnbuiltFilter.DeployableTeam = pBot->Player->GetTeam(); - UnbuiltFilter.ReachabilityTeam = pBot->Player->GetTeam(); + UnbuiltFilter.DeployableTeam = BotTeam; + UnbuiltFilter.ReachabilityTeam = BotTeam; UnbuiltFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; UnbuiltFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING | STRUCTURE_STATUS_COMPLETED; UnbuiltFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(20.0f); @@ -3284,13 +3482,12 @@ void AIPlayerSetSecondaryMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { float ThisDist = vDist2D((*it)->Location, pBot->Edict->v.origin); - int NumBuilders = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), (*it)->Location, ThisDist - 5.0f, false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER); + int NumBuilders = AITAC_GetNumPlayersOfTeamInArea(BotTeam, (*it)->Location, ThisDist - 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; + int NumDesiredBuilders = (vDist2DSq((*it)->Location, AITAC_GetCommChairLocation(BotTeam)) < sqrf(UTIL_MetresToGoldSrcUnits(15.0f))) ? 1 : 2; if (NumBuilders < NumDesiredBuilders) { - if (!NearestStructure || ThisDist < MinDist) { NearestStructure = (*it); @@ -3305,6 +3502,106 @@ void AIPlayerSetSecondaryMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) return; } + if (PlayerHasWeapon(pBot->Player, WEAPON_MARINE_WELDER)) + { + bool bWeldIsUrgent = false; + edict_t* ThingToWeld = nullptr; + + vector NearbyPlayers = AITAC_GetAllPlayersOfTeamInArea(BotTeam, pBot->Edict->v.origin, UTIL_MetresToGoldSrcUnits(5.0f), false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER); + AvHPlayer* NearestWeldablePlayer = nullptr; + AvHPlayer* NearestBadlyDamagedPlayer = nullptr; + float MinDist = 0.0f; + float MinBadDist = 0.0f; + + for (auto it = NearbyPlayers.begin(); it != NearbyPlayers.end(); it++) + { + AvHPlayer* ThisPlayer = (*it); + edict_t* PlayerEdict = ThisPlayer->edict(); + + float ArmourPercent = PlayerEdict->v.armorvalue / (float)GetPlayerMaxArmour(PlayerEdict); + + if (ArmourPercent < 1.0f) + { + float ThisDist = vDist2DSq(pBot->Edict->v.origin, PlayerEdict->v.origin); + + if (ArmourPercent < 0.75f) + { + if (!NearestBadlyDamagedPlayer || ThisDist < MinBadDist) + { + NearestBadlyDamagedPlayer = ThisPlayer; + MinBadDist = ThisDist; + } + } + else + { + if (!NearestWeldablePlayer || ThisDist < MinDist) + { + NearestWeldablePlayer = ThisPlayer; + MinDist = ThisDist; + } + } + } + } + + // Basically, we won't prioritise welding players over structures unless they're low on armour, otherwise we prefer structures. This avoids + // situations where the bot constantly keeps topping up nearby players when there are more important weld targets to worry about + if (NearestBadlyDamagedPlayer) + { + AITASK_SetWeldTask(pBot, Task, NearestBadlyDamagedPlayer->edict(), false); + return; + } + + DeployableSearchFilter WeldableStructures; + WeldableStructures.DeployableTypes = SEARCH_ALL_STRUCTURES; + WeldableStructures.DeployableTeam = BotTeam; + WeldableStructures.ReachabilityTeam = BotTeam; + WeldableStructures.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; + WeldableStructures.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + WeldableStructures.IncludeStatusFlags = STRUCTURE_STATUS_DAMAGED; + WeldableStructures.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + WeldableStructures.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(20.0f); + WeldableStructures.bConsiderPhaseDistance = true; + + AvHAIBuildableStructure* NearestDamagedStructure = AITAC_FindClosestDeployableToLocation(pBot->Edict->v.origin, &WeldableStructures); + + if (NearestDamagedStructure) + { + bool bIsUrgent = (NearestDamagedStructure->healthPercent < 0.5f); + + AITASK_SetWeldTask(pBot, Task, NearestDamagedStructure->edict, bIsUrgent); + return; + } + + + if (NearestWeldablePlayer) + { + AITASK_SetWeldTask(pBot, Task, NearestWeldablePlayer->edict(), false); + return; + } + } + + if (PlayerHasWeapon(pBot->Player, WEAPON_MARINE_MINES)) + { + if (Task->TaskType == TASK_PLACE_MINE) { return; } + + DeployableSearchFilter MineableStructures; + MineableStructures.DeployableTypes = (STRUCTURE_MARINE_INFANTRYPORTAL, STRUCTURE_MARINE_PHASEGATE, STRUCTURE_MARINE_TURRETFACTORY, STRUCTURE_MARINE_ADVTURRETFACTORY); + MineableStructures.DeployableTeam = BotTeam; + MineableStructures.ReachabilityTeam = BotTeam; + MineableStructures.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; + MineableStructures.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + MineableStructures.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + + vector AllMineableStructures = AITAC_FindAllDeployables(AITAC_GetTeamStartingLocation(BotTeam), &MineableStructures); + AvHAIBuildableStructure* StructureToMine = nullptr; + + for (auto it = AllMineableStructures.begin(); it != AllMineableStructures.end(); it++) + { + + } + + } + } bool AIPlayerMustFinishCurrentTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) @@ -4094,6 +4391,139 @@ void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task } } + const AvHAIHiveDefinition* NearestSiegedHive = AITAC_GetNearestHiveUnderActiveSiege(EnemyTeam, pBot->Edict->v.origin); + + if (NearestSiegedHive) + { + DeployableSearchFilter EnemyStuffFilter; + EnemyStuffFilter.DeployableTypes = SEARCH_ALL_STRUCTURES; + EnemyStuffFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + EnemyStuffFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + EnemyStuffFilter.DeployableTeam = EnemyTeam; + EnemyStuffFilter.ReachabilityTeam = BotTeam; + EnemyStuffFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; + EnemyStuffFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(25.0f); + + vector AllSiegingStructures = AITAC_FindAllDeployables(NearestSiegedHive->Location, &EnemyStuffFilter); + + AvHAIBuildableStructure* StructureToTarget = nullptr; + + float MinDist = 0.0f; + + for (auto it = AllSiegingStructures.begin(); it != AllSiegingStructures.end(); it++) + { + AvHAIBuildableStructure* ThisStructure = (*it); + + // Always go for the phase gate first to prevent reinforcements + if (ThisStructure->StructureType == STRUCTURE_MARINE_PHASEGATE) + { + StructureToTarget = ThisStructure; + continue; + } + + // Then go for any turret factories, especially advanced ones to cut off siege turrets + if (ThisStructure->StructureType == STRUCTURE_MARINE_ADVTURRETFACTORY) + { + StructureToTarget = ThisStructure; + continue; + } + + if (ThisStructure->StructureType == STRUCTURE_MARINE_TURRETFACTORY) + { + StructureToTarget = ThisStructure; + continue; + } + + // Pick up anything else + float ThisDist = vDist2DSq(ThisStructure->Location, pBot->Edict->v.origin); + + if (!StructureToTarget || ThisDist < MinDist) + { + StructureToTarget = ThisStructure; + MinDist = ThisDist; + } + } + + if (StructureToTarget) + { + AITASK_SetAttackTask(pBot, Task, StructureToTarget->edict, true); + return; + } + } + + // If we're up against marines, look out for any siege stuff + if (AIMGR_GetTeamType(EnemyTeam) == AVH_CLASS_TYPE_MARINE) + { + vector AllTeamHives = AITAC_GetAllTeamHives(BotTeam, false); + + for (auto it = AllTeamHives.begin(); it != AllTeamHives.end(); it++) + { + AvHAIHiveDefinition* ThisHive = (*it); + + DeployableSearchFilter EnemyStuffFilter; + EnemyStuffFilter.DeployableTypes = SEARCH_ALL_STRUCTURES; + EnemyStuffFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + EnemyStuffFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + EnemyStuffFilter.DeployableTeam = EnemyTeam; + EnemyStuffFilter.ReachabilityTeam = BotTeam; + EnemyStuffFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; + EnemyStuffFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(25.0f); + + vector AllSiegingStructures = AITAC_FindAllDeployables(ThisHive->Location, &EnemyStuffFilter); + + AvHAIBuildableStructure* StructureToTarget = nullptr; + + float MinDist = 0.0f; + + for (auto it = AllSiegingStructures.begin(); it != AllSiegingStructures.end(); it++) + { + AvHAIBuildableStructure* ThisStructure = (*it); + + // Always go for the phase gate first to prevent reinforcements + if (ThisStructure->StructureType == STRUCTURE_MARINE_PHASEGATE) + { + StructureToTarget = ThisStructure; + continue; + } + + // Then go for any turret factories, especially advanced ones to cut off siege turrets + if (ThisStructure->StructureType == STRUCTURE_MARINE_ADVTURRETFACTORY) + { + StructureToTarget = ThisStructure; + continue; + } + + if (ThisStructure->StructureType == STRUCTURE_MARINE_TURRETFACTORY) + { + StructureToTarget = ThisStructure; + continue; + } + + // Pick up anything else + float ThisDist = vDist2DSq(ThisStructure->Location, pBot->Edict->v.origin); + + if (!StructureToTarget || ThisDist < MinDist) + { + StructureToTarget = ThisStructure; + MinDist = ThisDist; + } + } + + if (StructureToTarget) + { + int NumAttackers = AITAC_GetNumPlayersOfTeamInArea(BotTeam, StructureToTarget->Location, UTIL_MetresToGoldSrcUnits(10.0f), false, pBot->Edict, AVH_USER3_ALIEN_PLAYER2); + + if (NumAttackers < 2) + { + AITASK_SetAttackTask(pBot, Task, StructureToTarget->edict, true); + return; + } + } + } + + } + + Vector EnemyBaseLocation = AITAC_GetTeamStartingLocation(EnemyTeam); AvHAIHiveDefinition* HiveToGuard = nullptr; @@ -4273,6 +4703,9 @@ void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task } } + DeployableSearchFilter EnemyInfPortalFilter; + + // TODO: Attack enemy hive/base edict_t* EnemyChair = AITAC_GetCommChair(EnemyTeam); @@ -4308,7 +4741,7 @@ void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task if (!FNullEnt(TargetPlayer)) { - MoveTo(pBot, UTIL_GetFloorUnderEntity(TargetPlayer), MOVESTYLE_NORMAL); + MoveTo(pBot, UTIL_GetEntityGroundLocation(TargetPlayer), MOVESTYLE_NORMAL); } } @@ -4437,7 +4870,7 @@ void AIPlayerSetAlienHarasserPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Tas if (!FNullEnt(TargetPlayer)) { - MoveTo(pBot, UTIL_GetFloorUnderEntity(TargetPlayer), MOVESTYLE_NORMAL); + MoveTo(pBot, UTIL_GetEntityGroundLocation(TargetPlayer), MOVESTYLE_NORMAL); } @@ -4815,7 +5248,7 @@ bool SkulkCombatThink(AvHAIPlayer* pBot) } BotAttackResult LOSCheck = PerformAttackLOSCheck(pBot, DesiredWeapon, CurrentEnemy); - Vector MoveTarget = UTIL_GetFloorUnderEntity(CurrentEnemy); + Vector MoveTarget = UTIL_GetEntityGroundLocation(CurrentEnemy); if (LOSCheck == ATTACK_SUCCESS) { @@ -4947,6 +5380,264 @@ bool SkulkCombatThink(AvHAIPlayer* pBot) bool GorgeCombatThink(AvHAIPlayer* pBot) { + AvHTeamNumber BotTeam = pBot->Player->GetTeam(); + + edict_t* CurrentEnemy = pBot->TrackedEnemies[pBot->CurrentEnemy].EnemyEdict; + + if (FNullEnt(CurrentEnemy) || !IsPlayerActiveInGame(CurrentEnemy)) { return false; } + + enemy_status* TrackedEnemyRef = &pBot->TrackedEnemies[pBot->CurrentEnemy]; + + float CurrentHealthPercent = GetPlayerOverallHealthPercent(pBot->Edict); + + if (pBot->CurrentCombatStrategy == COMBAT_STRATEGY_RETREAT) + { + BotAttackResult AttackResult = PerformAttackLOSCheck(pBot, WEAPON_GORGE_SPIT, CurrentEnemy); + + edict_t* NearestHealingSource = AITAC_AlienFindNearestHealingSource(pBot->Player->GetTeam(), pBot->Edict->v.origin, pBot->Edict, true); + + // Run away if low on health and have a healing spot + if (!FNullEnt(NearestHealingSource)) + { + float DesiredDistFromHealingSource = (IsEdictPlayer(NearestHealingSource)) ? UTIL_MetresToGoldSrcUnits(2.0f) : UTIL_MetresToGoldSrcUnits(5.0f); + + bool bOutOfEnemyLOS = !TrackedEnemyRef->bHasLOS; + + float DistFromHealingSourceSq = vDist2DSq(pBot->Edict->v.origin, NearestHealingSource->v.origin); + + bool bInHealingRange = (DistFromHealingSourceSq <= sqrf(DesiredDistFromHealingSource)); + + if (!bInHealingRange) + { + MoveTo(pBot, UTIL_GetEntityGroundLocation(NearestHealingSource), MOVESTYLE_NORMAL, DesiredDistFromHealingSource); + + if (CurrentHealthPercent > 0.5f && AttackResult == ATTACK_SUCCESS) + { + BotShootTarget(pBot, WEAPON_GORGE_SPIT, CurrentEnemy); + } + else + { + pBot->DesiredCombatWeapon = WEAPON_GORGE_HEALINGSPRAY; + + if (GetPlayerCurrentWeapon(pBot->Player) == WEAPON_GORGE_HEALINGSPRAY) + { + pBot->Button |= IN_ATTACK; + } + } + + Vector EnemyOrientation = UTIL_GetVectorNormal2D(pBot->desiredMovementDir); + Vector RightDir = UTIL_GetCrossProduct(EnemyOrientation, UP_VECTOR); + + pBot->desiredMovementDir = (pBot->BotNavInfo.bZig) ? UTIL_GetVectorNormal2D(pBot->desiredMovementDir + RightDir) : UTIL_GetVectorNormal2D(pBot->desiredMovementDir - RightDir); + + // Let's get ziggy with it + if (gpGlobals->time > pBot->BotNavInfo.NextZigTime) + { + pBot->BotNavInfo.bZig = !pBot->BotNavInfo.bZig; + pBot->BotNavInfo.NextZigTime = gpGlobals->time + frandrange(0.5f, 1.0f); + } + + BotMovementInputs(pBot); + + return true; + } + + if (bOutOfEnemyLOS) + { + if (bInHealingRange) + { + BotLookAt(pBot, TrackedEnemyRef->LastLOSPosition); + } + else + { + MoveTo(pBot, UTIL_GetEntityGroundLocation(NearestHealingSource), MOVESTYLE_NORMAL, DesiredDistFromHealingSource); + } + + pBot->DesiredCombatWeapon = WEAPON_GORGE_HEALINGSPRAY; + + if (GetPlayerCurrentWeapon(pBot->Player) == WEAPON_GORGE_HEALINGSPRAY) + { + pBot->Button |= IN_ATTACK; + } + + return true; + } + + 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 (CurrentHealthPercent > 0.5f && AttackResult == ATTACK_SUCCESS) + { + BotShootTarget(pBot, WEAPON_GORGE_SPIT, CurrentEnemy); + } + else + { + pBot->DesiredCombatWeapon = WEAPON_GORGE_HEALINGSPRAY; + + if (GetPlayerCurrentWeapon(pBot->Player) == WEAPON_GORGE_HEALINGSPRAY) + { + pBot->Button |= IN_ATTACK; + } + + } + + Vector EnemyOrientation = UTIL_GetVectorNormal2D(pBot->desiredMovementDir); + Vector RightDir = UTIL_GetCrossProduct(EnemyOrientation, UP_VECTOR); + + pBot->desiredMovementDir = (pBot->BotNavInfo.bZig) ? UTIL_GetVectorNormal2D(pBot->desiredMovementDir + RightDir) : UTIL_GetVectorNormal2D(pBot->desiredMovementDir - RightDir); + + // Let's get ziggy with it + if (gpGlobals->time > pBot->BotNavInfo.NextZigTime) + { + pBot->BotNavInfo.bZig = !pBot->BotNavInfo.bZig; + pBot->BotNavInfo.NextZigTime = gpGlobals->time + frandrange(0.5f, 1.0f); + } + + BotMovementInputs(pBot); + + return true; + } + else + { + if (AttackResult == ATTACK_SUCCESS) + { + BotShootTarget(pBot, WEAPON_GORGE_SPIT, CurrentEnemy); + } + + Vector EnemyOrientation = UTIL_GetVectorNormal2D(pBot->desiredMovementDir); + Vector RightDir = UTIL_GetCrossProduct(EnemyOrientation, UP_VECTOR); + + pBot->desiredMovementDir = (pBot->BotNavInfo.bZig) ? UTIL_GetVectorNormal2D(pBot->desiredMovementDir + RightDir) : UTIL_GetVectorNormal2D(pBot->desiredMovementDir - RightDir); + + // Let's get ziggy with it + if (gpGlobals->time > pBot->BotNavInfo.NextZigTime) + { + pBot->BotNavInfo.bZig = !pBot->BotNavInfo.bZig; + pBot->BotNavInfo.NextZigTime = gpGlobals->time + frandrange(0.5f, 1.0f); + } + + BotMovementInputs(pBot); + } + + } + + return false; + } + + // Just zig-zag and bombard the enemy with spit if they have LOS, or go hunt them if not + if (pBot->CurrentCombatStrategy == COMBAT_STRATEGY_ATTACK) + { + BotAttackResult AttackResult = PerformAttackLOSCheck(pBot, WEAPON_GORGE_SPIT, CurrentEnemy); + + if (CurrentHealthPercent < 0.5f) + { + pBot->DesiredCombatWeapon = WEAPON_GORGE_HEALINGSPRAY; + + if (GetPlayerCurrentWeapon(pBot->Player) == WEAPON_GORGE_HEALINGSPRAY) + { + pBot->Button |= IN_ATTACK; + } + + return true; + } + + if (AttackResult == ATTACK_SUCCESS) + { + BotShootTarget(pBot, WEAPON_GORGE_SPIT, CurrentEnemy); + + Vector EnemyOrientation = UTIL_GetVectorNormal2D(CurrentEnemy->v.origin - pBot->Edict->v.origin); + Vector RightDir = UTIL_GetCrossProduct(EnemyOrientation, UP_VECTOR); + + pBot->desiredMovementDir = (pBot->BotNavInfo.bZig) ? UTIL_GetVectorNormal2D(pBot->desiredMovementDir + RightDir) : UTIL_GetVectorNormal2D(pBot->desiredMovementDir - RightDir); + + // Let's get ziggy with it + if (gpGlobals->time > pBot->BotNavInfo.NextZigTime) + { + pBot->BotNavInfo.bZig = !pBot->BotNavInfo.bZig; + pBot->BotNavInfo.NextZigTime = gpGlobals->time + frandrange(0.5f, 1.0f); + } + + BotMovementInputs(pBot); + + return true; + } + else + { + MoveTo(pBot, TrackedEnemyRef->LastSeenLocation, MOVESTYLE_NORMAL); + } + + return false; + } + + // Just zig-zag and bombard the enemy with spit if they have LOS, or go hunt them if not + if (pBot->CurrentCombatStrategy == COMBAT_STRATEGY_SKIRMISH) + { + BotAttackResult AttackResult = PerformAttackLOSCheck(pBot, WEAPON_GORGE_SPIT, CurrentEnemy); + + if (vDist2DSq(CurrentEnemy->v.origin, pBot->LastSafeLocation) < sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) + { + pBot->LastSafeLocation = ZERO_VECTOR; + } + + if (vIsZero(pBot->LastSafeLocation)) + { + const AvHAIHiveDefinition* NearestHive = AITAC_GetActiveHiveNearestLocation(pBot->Player->GetTeam(), pBot->Edict->v.origin); + + if (NearestHive) + { + pBot->LastSafeLocation = NearestHive->FloorLocation; + } + } + + if (TrackedEnemyRef->bHasLOS) + { + if (CurrentHealthPercent < 0.7f || vDist2DSq(CurrentEnemy->v.origin, pBot->Edict->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(5.0f)) || vDist2DSq(pBot->Edict->v.origin, pBot->LastSafeLocation) > sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) + { + MoveTo(pBot, pBot->LastSafeLocation, MOVESTYLE_NORMAL); + + if (CurrentHealthPercent < 0.5f) + { + pBot->DesiredCombatWeapon = WEAPON_GORGE_HEALINGSPRAY; + + if (GetPlayerCurrentWeapon(pBot->Player) == WEAPON_GORGE_HEALINGSPRAY) + { + pBot->Button |= IN_ATTACK; + } + + return true; + } + } + + BotLookAt(pBot, TrackedEnemyRef->LastSeenLocation); + + if (AttackResult == ATTACK_SUCCESS) + { + BotShootTarget(pBot, WEAPON_GORGE_SPIT, CurrentEnemy); + } + return true; + } + else + { + if (CurrentHealthPercent >= 0.99f) + { + MoveTo(pBot, TrackedEnemyRef->LastSeenLocation, MOVESTYLE_NORMAL); + return true; + } + + BotLookAt(pBot, (!vIsZero(TrackedEnemyRef->LastLOSPosition)) ? TrackedEnemyRef->LastLOSPosition : TrackedEnemyRef->LastSeenLocation); + + pBot->DesiredCombatWeapon = WEAPON_GORGE_HEALINGSPRAY; + + if (GetPlayerCurrentWeapon(pBot->Player) == WEAPON_GORGE_HEALINGSPRAY) + { + pBot->Button |= IN_ATTACK; + } + } + + return true; + } + return false; } @@ -5134,6 +5825,16 @@ bool FadeCombatThink(AvHAIPlayer* pBot) if (!bInHealingRange) { MoveTo(pBot, UTIL_GetEntityGroundLocation(NearestHealingSource), MOVESTYLE_NORMAL, DesiredDistFromHealingSource); + + // If we're still in danger while retreating, do extra leaping to get the hell out + if (TrackedEnemyRef->bHasLOS) + { + if (pBot->BotNavInfo.CurrentPathPoint != pBot->BotNavInfo.CurrentPath.end() && pBot->BotNavInfo.CurrentPathPoint->flag != SAMPLE_POLYFLAGS_WALLCLIMB && pBot->BotNavInfo.CurrentPathPoint->flag != SAMPLE_POLYFLAGS_LIFT) + { + BotLeap(pBot, pBot->BotNavInfo.CurrentPathPoint->Location); + } + } + return true; } @@ -5175,7 +5876,7 @@ bool FadeCombatThink(AvHAIPlayer* pBot) AvHAIWeapon DesiredWeapon = WEAPON_FADE_SWIPE; BotAttackResult LOSCheck = PerformAttackLOSCheck(pBot, DesiredWeapon, CurrentEnemy); - Vector MoveTarget = UTIL_GetFloorUnderEntity(CurrentEnemy); + Vector MoveTarget = UTIL_GetEntityGroundLocation(CurrentEnemy); float EnemySpeed = vSize2D(CurrentEnemy->v.velocity); @@ -5380,9 +6081,9 @@ bool OnosCombatThink(AvHAIPlayer* pBot) if (pBot->CurrentCombatStrategy == COMBAT_STRATEGY_ATTACK || bShouldBreakRetreat) { - Vector MoveTarget = UTIL_GetFloorUnderEntity(CurrentEnemy); + Vector MoveTarget = UTIL_GetEntityGroundLocation(CurrentEnemy); - MoveTarget = MoveTarget + (CurrentEnemy->v.velocity * 0.1f); + MoveTarget = MoveTarget + (UTIL_GetVectorNormal2D(CurrentEnemy->v.velocity) * 0.1f); MoveTo(pBot, MoveTarget, MOVESTYLE_NORMAL); diff --git a/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp b/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp index f7154be7..2e69a5f6 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp +++ b/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp @@ -597,7 +597,7 @@ void AIMGR_UpdateAIPlayers() UpdateBotChat(bot); - AIPlayerThink(bot); + DroneThink(bot); BotUpdateDesiredViewRotation(bot); } @@ -659,6 +659,11 @@ AvHTeamNumber AIMGR_GetTeamBNumber() return GetGameRules()->GetTeamANumber(); } +AvHTeam* AIMGR_GetTeamRef(const AvHTeamNumber Team) +{ + return GetGameRules()->GetTeam(Team); +} + vector AIMGR_GetAllPlayersOnTeam(AvHTeamNumber Team) { vector Result; diff --git a/main/source/mod/AIPlayers/AvHAIPlayerManager.h b/main/source/mod/AIPlayers/AvHAIPlayerManager.h index 6c7df2bf..676da7c0 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayerManager.h +++ b/main/source/mod/AIPlayers/AvHAIPlayerManager.h @@ -72,6 +72,8 @@ AvHClassType AIMGR_GetTeamType(const AvHTeamNumber Team); AvHTeamNumber AIMGR_GetTeamANumber(); AvHTeamNumber AIMGR_GetTeamBNumber(); +AvHTeam* AIMGR_GetTeamRef(const AvHTeamNumber Team); + // Returns all NS AI players. Does not include third-party bots vector AIMGR_GetAllAIPlayers(); // Returns all NS AI players on the requested team. Does not include third-party bots diff --git a/main/source/mod/AIPlayers/AvHAITactical.cpp b/main/source/mod/AIPlayers/AvHAITactical.cpp index ee94329c..faa8276e 100644 --- a/main/source/mod/AIPlayers/AvHAITactical.cpp +++ b/main/source/mod/AIPlayers/AvHAITactical.cpp @@ -1760,6 +1760,13 @@ void AITAC_RefreshReachabilityForStructure(AvHAIBuildableStructure* Structure) AITAC_RefreshHiveData(); } + if (Structure->StructureType == STRUCTURE_MARINE_DEPLOYEDMINE) + { + Structure->TeamAReachabilityFlags = AI_REACHABILITY_ALL; + Structure->TeamBReachabilityFlags = AI_REACHABILITY_ALL; + return; + } + Structure->bReachabilityMarkedDirty = false; Structure->TeamAReachabilityFlags = AI_REACHABILITY_NONE; @@ -1891,23 +1898,47 @@ AvHAIBuildableStructure* AITAC_UpdateBuildableStructure(CBaseEntity* Structure) { if (!Structure || (Structure->pev->effects & EF_NODRAW) || (Structure->pev->deadflag != DEAD_NO)) { return nullptr; } - AvHBaseBuildable* BaseBuildable = dynamic_cast(Structure); + edict_t* BuildingEdict = Structure->edict(); - if (!BaseBuildable) { return nullptr; } - - edict_t* BuildingEdict = BaseBuildable->edict(); - - AvHAIDeployableStructureType StructureType = UTIL_IUSER3ToStructureType(BaseBuildable->pev->iuser3); + AvHAIDeployableStructureType StructureType = UTIL_IUSER3ToStructureType(BuildingEdict->v.iuser3); if (StructureType == STRUCTURE_NONE) { return nullptr; } - int EntIndex = BaseBuildable->entindex(); + int EntIndex = ENTINDEX(BuildingEdict); + if (EntIndex < 0) { return nullptr; } AvHTeamNumber TeamANumber = GetGameRules()->GetTeamANumber(); AvHTeamNumber TeamBNumber = GetGameRules()->GetTeamBNumber(); - std::unordered_map& BuildingMap = (BaseBuildable->GetTeamNumber() == TeamANumber) ? TeamAStructureMap : TeamBStructureMap; + std::unordered_map& BuildingMap = ((AvHTeamNumber)BuildingEdict->v.team == TeamANumber) ? TeamAStructureMap : TeamBStructureMap; + + if (StructureType == STRUCTURE_MARINE_DEPLOYEDMINE) + { + BuildingMap[EntIndex].StructureType = StructureType; + if (BuildingMap[EntIndex].LastSeen == 0) + { + BuildingMap[EntIndex].Location = BuildingEdict->v.origin; + BuildingMap[EntIndex].edict = BuildingEdict; + BuildingMap[EntIndex].healthPercent = 1.0f; + BuildingMap[EntIndex].EntityRef = nullptr; + BuildingMap[EntIndex].StructureStatusFlags = STRUCTURE_STATUS_COMPLETED; + BuildingMap[EntIndex].TeamAReachabilityFlags = AI_REACHABILITY_ALL; + BuildingMap[EntIndex].TeamBReachabilityFlags = AI_REACHABILITY_ALL; + AITAC_OnStructureCreated(&BuildingMap[EntIndex]); + } + + BuildingMap[EntIndex].LastSeen = StructureRefreshFrame; + + return &BuildingMap[EntIndex]; + } + + AvHBaseBuildable* BaseBuildable = dynamic_cast(Structure); + + if (!BaseBuildable) + { + return nullptr; + } BuildingMap[EntIndex].StructureType = StructureType; @@ -1980,6 +2011,11 @@ AvHAIBuildableStructure* AITAC_UpdateBuildableStructure(CBaseEntity* Structure) BuildingMap[EntIndex].healthPercent = NewHealthPercent; + if (BuildingMap[EntIndex].healthPercent < 0.99f && BaseBuildable->GetIsBuilt()) + { + NewFlags |= STRUCTURE_STATUS_DAMAGED; + } + if (gpGlobals->time - BuildingMap[EntIndex].lastDamagedTime < 10.0f) { NewFlags |= STRUCTURE_STATUS_UNDERATTACK; @@ -2008,7 +2044,7 @@ void AITAC_OnStructureCreated(AvHAIBuildableStructure* NewStructure) UTIL_AddStructureTemporaryObstacles(NewStructure); - AvHTeamNumber StructureTeam = NewStructure->EntityRef->GetTeamNumber(); + AvHTeamNumber StructureTeam = (AvHTeamNumber)NewStructure->edict->v.team; AITAC_RefreshReachabilityForStructure(NewStructure); @@ -2018,9 +2054,9 @@ void AITAC_OnStructureCreated(AvHAIBuildableStructure* NewStructure) if (!Team) { return; } - if (Team->GetTeamType() == AVH_CLASS_TYPE_ALIEN) + if (Team->GetTeamType() == AVH_CLASS_TYPE_ALIEN || NewStructure->StructureType == STRUCTURE_MARINE_DEPLOYEDMINE) { - AITAC_LinkAlienStructureToPlayer(NewStructure); + AITAC_LinkStructureToPlayer(NewStructure); } } @@ -2156,7 +2192,7 @@ void AITAC_OnStructureDestroyed(AvHAIBuildableStructure* DestroyedStructure) } } -void AITAC_LinkAlienStructureToPlayer(AvHAIBuildableStructure* NewStructure) +void AITAC_LinkStructureToPlayer(AvHAIBuildableStructure* NewStructure) { vector AllTeamPlayers = AIMGR_GetAIPlayersOnTeam((AvHTeamNumber)NewStructure->edict->v.team); @@ -2370,6 +2406,8 @@ float UTIL_GetStructureRadiusForObstruction(AvHAIDeployableStructureType Structu return 60.0f; case STRUCTURE_MARINE_TURRET: return 30.0f; + case STRUCTURE_MARINE_DEPLOYEDMINE: + return 12.0f; default: return 40.0f; @@ -3120,24 +3158,18 @@ Vector UTIL_GetNextMinePosition(edict_t* StructureToMine) bool bBack = false; bool bLeft = false; - int NumMines = 0; + DeployableSearchFilter MineFilter; + MineFilter.DeployableTeam = StructureTeam; + MineFilter.DeployableTypes = STRUCTURE_MARINE_DEPLOYEDMINE; + MineFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(3.0f); - AvHTeamNumber TeamANumber = GetGameRules()->GetTeamANumber(); - AvHTeamNumber TeamBNumber = GetGameRules()->GetTeamBNumber(); + vector SurroundingMines = AITAC_FindAllDeployables(StructureToMine->v.origin, &MineFilter); - std::unordered_map& BuildingMap = (StructureTeam == TeamANumber) ? TeamAStructureMap : TeamBStructureMap; - - for (auto& it : BuildingMap) + for (auto it = SurroundingMines.begin(); it != SurroundingMines.end(); it++) { - unsigned int TeamReachabilityFlag = (StructureTeam == TeamANumber) ? it.second.TeamAReachabilityFlags : it.second.TeamBReachabilityFlags; + AvHAIBuildableStructure* ThisMine = (*it); - if (it.second.StructureType != STRUCTURE_MARINE_DEPLOYEDMINE || !(TeamReachabilityFlag & AI_REACHABILITY_MARINE)) { continue; } - - if (vDist2DSq(StructureToMine->v.origin, it.second.Location) > sqrf(UTIL_MetresToGoldSrcUnits(2.0f))) { continue; } - - NumMines++; - - Vector Dir = UTIL_GetVectorNormal2D(it.second.Location - StructureToMine->v.origin); + Vector Dir = UTIL_GetVectorNormal2D(ThisMine->Location - StructureToMine->v.origin); if (UTIL_GetDotProduct2D(FwdVector, Dir) > 0.7f) { @@ -3158,7 +3190,6 @@ Vector UTIL_GetNextMinePosition(edict_t* StructureToMine) { bLeft = true; } - } float Size = fmaxf(StructureToMine->v.size.x, StructureToMine->v.size.y); @@ -3212,7 +3243,7 @@ Vector UTIL_GetNextMinePosition(edict_t* StructureToMine) } } - Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInDonut(BaseNavProfiles[MARINE_BASE_NAV_PROFILE], StructureToMine->v.origin, Size, Size + 16.0f); + Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), StructureToMine->v.origin, Size); return BuildLocation; } @@ -3415,6 +3446,35 @@ bool AITAC_AnyPlayerOnTeamHasLOSToLocation(AvHTeamNumber Team, const Vector& Loc return false; } +vector AITAC_GetAllPlayersOnTeamWithLOS(AvHTeamNumber Team, const Vector& Location, float SearchRadius, edict_t* IgnorePlayer) +{ + vector Results; + + float distSq = sqrf(SearchRadius); + + for (int i = 1; i <= gpGlobals->maxClients; i++) + { + edict_t* PlayerEdict = INDEXENT(i); + + if (!FNullEnt(PlayerEdict) && PlayerEdict != IgnorePlayer && PlayerEdict->v.team == Team && IsPlayerActiveInGame(PlayerEdict)) + { + float ThisDist = vDist2DSq(PlayerEdict->v.origin, Location); + + if (ThisDist <= distSq && UTIL_QuickTrace(PlayerEdict, GetPlayerEyePosition(PlayerEdict), Location)) + { + AvHPlayer* PlayerRef = dynamic_cast(CBaseEntity::Instance(PlayerEdict)); + + if (PlayerRef) + { + Results.push_back(PlayerRef); + } + } + } + } + + return Results; +} + bool AITAC_GetNumPlayersOnTeamWithLOS(AvHTeamNumber Team, const Vector& Location, float SearchRadius, edict_t* IgnorePlayer) { int Result = 0; @@ -3591,6 +3651,9 @@ const AvHAIHiveDefinition* AITAC_GetNearestHiveUnderActiveSiege(AvHTeamNumber Si AvHAIHiveDefinition* Result = nullptr; float MinDist = 0.0f; + // Only marines can siege, so return nothing if the enemy are not marines + if (AIMGR_GetTeamType(SiegingTeam) != AVH_CLASS_TYPE_MARINE) { return nullptr; } + for (auto it = Hives.begin(); it != Hives.end(); it++) { if (it->Status == HIVE_STATUS_UNBUILT) { continue; } diff --git a/main/source/mod/AIPlayers/AvHAITactical.h b/main/source/mod/AIPlayers/AvHAITactical.h index 55cb0086..51b60fbe 100644 --- a/main/source/mod/AIPlayers/AvHAITactical.h +++ b/main/source/mod/AIPlayers/AvHAITactical.h @@ -45,7 +45,7 @@ void AITAC_OnStructureCompleted(AvHAIBuildableStructure* NewStructure); void AITAC_OnStructureBeginRecycling(AvHAIBuildableStructure* RecyclingStructure); void AITAC_OnStructureDestroyed(AvHAIBuildableStructure* DestroyedStructure); void AITAC_LinkDeployedItemToAction(AvHAIPlayer* CommanderBot, const AvHAIDroppedItem* NewItem); -void AITAC_LinkAlienStructureToPlayer(AvHAIBuildableStructure* NewStructure); +void AITAC_LinkStructureToPlayer(AvHAIBuildableStructure* NewStructure); float AITAC_GetPhaseDistanceBetweenPoints(const Vector StartPoint, const Vector EndPoint); @@ -80,6 +80,7 @@ AvHMessageID UTIL_ItemTypeToImpulseCommand(const AvHAIDeployableItemType ItemTyp edict_t* AITAC_GetClosestPlayerOnTeamWithLOS(AvHTeamNumber Team, const Vector& Location, float SearchRadius, edict_t* IgnorePlayer); bool AITAC_AnyPlayerOnTeamHasLOSToLocation(AvHTeamNumber Team, const Vector& Location, float SearchRadius, edict_t* IgnorePlayer); bool AITAC_GetNumPlayersOnTeamWithLOS(AvHTeamNumber Team, const Vector& Location, float SearchRadius, edict_t* IgnorePlayer); +vector AITAC_GetAllPlayersOnTeamWithLOS(AvHTeamNumber Team, const Vector& Location, float SearchRadius, edict_t* IgnorePlayer); bool AITAC_ShouldBotBeCautious(AvHAIPlayer* pBot); // Clears out the marine and alien buildable structure maps, resource node and hive lists, and the marine item list diff --git a/main/source/mod/AIPlayers/AvHAITask.cpp b/main/source/mod/AIPlayers/AvHAITask.cpp index e7f45ac2..9f5b5916 100644 --- a/main/source/mod/AIPlayers/AvHAITask.cpp +++ b/main/source/mod/AIPlayers/AvHAITask.cpp @@ -1129,39 +1129,25 @@ void BotProgressPickupTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) void BotProgressMineStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { - float DistToPlaceLocation = vDist2DSq(pBot->Edict->v.origin, Task->TaskLocation); - - if (DistToPlaceLocation < sqrf(UTIL_MetresToGoldSrcUnits(3.0f))) + if (pBot->ActiveBuildInfo.BuildStatus == BUILD_ATTEMPT_PENDING) { - pBot->DesiredCombatWeapon = WEAPON_MARINE_MINES; - } - - if (!FNullEnt(Task->TaskSecondaryTarget)) - { - Task->TaskLocation = g_vecZero; - Task->TaskSecondaryTarget = nullptr; - Task->BuildAttempts = 0; return; } - if (Task->bIsWaitingForBuildLink) + if (pBot->ActiveBuildInfo.BuildStatus != BUILD_ATTEMPT_NONE) { - - if (gpGlobals->time - Task->LastBuildAttemptTime > 1.0f) + if (pBot->ActiveBuildInfo.BuildStatus == BUILD_ATTEMPT_FAILED) { - Task->bIsWaitingForBuildLink = false; - if (Task->BuildAttempts > 3) - { - float Size = fmaxf(Task->TaskTarget->v.size.x, Task->TaskTarget->v.size.y); - Task->TaskLocation = UTIL_GetRandomPointOnNavmeshInRadius(BaseNavProfiles[STRUCTURE_BASE_NAV_PROFILE], Task->TaskTarget->v.origin, Size + 8.0f); - } - else - { - Vector Dir = UTIL_GetVectorNormal2D(Task->TaskLocation - Task->TaskTarget->v.origin); - Task->TaskLocation = Task->TaskLocation + (Dir * 8.0f); - } + float Size = fmaxf(Task->TaskTarget->v.size.x, Task->TaskTarget->v.size.y); + Size += 8.0f; + Task->TaskLocation = UTIL_GetRandomPointOnNavmeshInDonut(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), Task->TaskTarget->v.origin, Size, Size + 8.0f); } - return; + else + { + Task->TaskLocation = ZERO_VECTOR; + } + + pBot->ActiveBuildInfo.BuildStatus = BUILD_ATTEMPT_NONE; } if (vIsZero(Task->TaskLocation)) @@ -1173,22 +1159,23 @@ void BotProgressMineStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) AITASK_ClearBotTask(pBot, Task); return; } - } - - if (DistToPlaceLocation < sqrf(16.0f)) - { - Vector MoveDir = UTIL_GetVectorNormal2D(Task->TaskLocation - pBot->Edict->v.origin); - MoveDirectlyTo(pBot, Task->TaskLocation - (MoveDir * 28.0f)); - return; } - if (DistToPlaceLocation > sqrf(32.0f)) + float DistToPlaceLocation = vDist2DSq(pBot->Edict->v.origin, Task->TaskLocation); + + if (DistToPlaceLocation < sqrf(UTIL_MetresToGoldSrcUnits(3.0f))) + { + pBot->DesiredCombatWeapon = WEAPON_MARINE_MINES; + } + + + if (DistToPlaceLocation > sqrf(8.0f)) { MoveTo(pBot, Task->TaskLocation, MOVESTYLE_NORMAL); return; } - BotLookAt(pBot, Task->TaskLocation); + BotDirectLookAt(pBot, Task->TaskLocation); if (GetPlayerCurrentWeapon(pBot->Player) == WEAPON_MARINE_MINES) { @@ -1197,9 +1184,11 @@ void BotProgressMineStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) if (LookDot > 0.95f) { pBot->Button |= IN_ATTACK; - Task->LastBuildAttemptTime = gpGlobals->time; - Task->BuildAttempts++; - Task->bIsWaitingForBuildLink = true; + pBot->ActiveBuildInfo.AttemptedLocation = Task->TaskLocation; + pBot->ActiveBuildInfo.BuildStatus = BUILD_ATTEMPT_PENDING; + pBot->ActiveBuildInfo.BuildAttemptTime = gpGlobals->time; + pBot->ActiveBuildInfo.AttemptedStructureType = STRUCTURE_MARINE_DEPLOYEDMINE; + pBot->ActiveBuildInfo.NumAttempts++; } } } @@ -1921,11 +1910,6 @@ void AlienProgressBuildTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) return; } - if (!vIsZero(Task->TaskLocation)) - { - UTIL_DrawLine(INDEXENT(1), pBot->Edict->v.origin, Task->TaskLocation); - } - // We tried and failed to place the structure if (pBot->ActiveBuildInfo.BuildStatus == BUILD_ATTEMPT_FAILED) { @@ -2393,12 +2377,21 @@ void BotProgressWeldTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) if (IsPlayerInUseRange(pBot->Edict, Task->TaskTarget)) { - Vector BBMin = Task->TaskTarget->v.absmin; - Vector BBMax = Task->TaskTarget->v.absmax; + Vector AimLocation = UTIL_GetCentreOfEntity(Task->TaskTarget); - vScaleBB(BBMin, BBMax, 0.75f); + // If we're targeting a func_weldable, then the centre of the entity might be in a wall or out of reach + // so instead aim at the closest point on the func_weldable to us. + if (!IsEdictPlayer(Task->TaskTarget) && !IsEdictStructure(Task->TaskTarget)) + { + Vector BBMin = Task->TaskTarget->v.absmin; + Vector BBMax = Task->TaskTarget->v.absmax; - BotLookAt(pBot, vClosestPointOnBB(pBot->CurrentEyePosition, BBMin, BBMax)); + vScaleBB(BBMin, BBMax, 0.75f); + + AimLocation = vClosestPointOnBB(pBot->CurrentEyePosition, BBMin, BBMax); + } + + BotLookAt(pBot, AimLocation); pBot->DesiredCombatWeapon = WEAPON_MARINE_WELDER; if (GetPlayerCurrentWeapon(pBot->Player) != WEAPON_MARINE_WELDER) diff --git a/main/source/mod/AIPlayers/AvHAIWeaponHelper.cpp b/main/source/mod/AIPlayers/AvHAIWeaponHelper.cpp index bd63b909..c5ffda94 100644 --- a/main/source/mod/AIPlayers/AvHAIWeaponHelper.cpp +++ b/main/source/mod/AIPlayers/AvHAIWeaponHelper.cpp @@ -386,6 +386,7 @@ float GetMaxIdealWeaponRange(const AvHAIWeapon Weapon) case WEAPON_SKULK_PARASITE: case WEAPON_SKULK_LEAP: case WEAPON_ONOS_CHARGE: + case WEAPON_GORGE_SPIT: return UTIL_MetresToGoldSrcUnits(20.0f); case WEAPON_MARINE_HMG: case WEAPON_MARINE_GRENADE: