From 7dcd3ca1d8b77f33838e75aa2d47070639ae352d Mon Sep 17 00:00:00 2001 From: RGreenlees Date: Tue, 30 Jan 2024 23:00:41 +0000 Subject: [PATCH] Commander improvements --- main/source/mod/AIPlayers/AvHAICommander.cpp | 165 ++++- main/source/mod/AIPlayers/AvHAICommander.h | 2 + main/source/mod/AIPlayers/AvHAIConstants.h | 67 +- main/source/mod/AIPlayers/AvHAINavigation.cpp | 575 +++++++++--------- main/source/mod/AIPlayers/AvHAINavigation.h | 46 +- main/source/mod/AIPlayers/AvHAIPlayer.cpp | 529 ++++++++++------ main/source/mod/AIPlayers/AvHAIPlayer.h | 5 +- .../mod/AIPlayers/AvHAIPlayerManager.cpp | 77 ++- .../source/mod/AIPlayers/AvHAIPlayerManager.h | 4 +- main/source/mod/AIPlayers/AvHAITactical.cpp | 203 ++++++- main/source/mod/AIPlayers/AvHAITactical.h | 5 + .../mod/AIPlayers/AvHAIWeaponHelper.cpp | 10 +- main/source/mod/AvHPlayer.cpp | 13 + 13 files changed, 1159 insertions(+), 542 deletions(-) diff --git a/main/source/mod/AIPlayers/AvHAICommander.cpp b/main/source/mod/AIPlayers/AvHAICommander.cpp index d97d2f5f..52ad9930 100644 --- a/main/source/mod/AIPlayers/AvHAICommander.cpp +++ b/main/source/mod/AIPlayers/AvHAICommander.cpp @@ -381,6 +381,37 @@ bool AICOMM_DoesPlayerOrderNeedReminder(AvHAIPlayer* pBot, ai_commander_order* O return NewDist >= OldDist; } +bool AICOMM_ShouldCommanderPrioritiseNodes(AvHAIPlayer* pBot) +{ + AvHTeamNumber BotTeam = pBot->Player->GetTeam(); + AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam); + + int NumOwnedNodes = 0; + int NumEligibleNodes = 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); + + for (auto it = AllNodes.begin(); it != AllNodes.end(); it++) + { + AvHAIResourceNode* ThisNode = (*it); + + // 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; } + + NumEligibleNodes++; + + if (ThisNode->OwningTeam == BotTeam) { NumOwnedNodes++; } + } + + int NumNodesLeft = NumEligibleNodes - NumOwnedNodes; + + if (NumNodesLeft == 0) { return false; } + + return NumOwnedNodes < 3 || NumNodesLeft > 3; + +} + void AICOMM_UpdatePlayerOrders(AvHAIPlayer* pBot) { // Clear out any orders which aren't relevant any more @@ -425,6 +456,61 @@ void AICOMM_UpdatePlayerOrders(AvHAIPlayer* pBot) } } + if (AICOMM_ShouldCommanderPrioritiseNodes(pBot)) + { + AvHTeamNumber BotTeam = pBot->Player->GetTeam(); + AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam); + + Vector TeamStartingLocation = AITAC_GetTeamStartingLocation(BotTeam); + + DeployableSearchFilter ResNodeFilter; + ResNodeFilter.ReachabilityTeam = pBot->Player->GetTeam(); + ResNodeFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; + + vector EligibleResNodes = AITAC_GetAllMatchingResourceNodes(ZERO_VECTOR, &ResNodeFilter); + + AvHAIResourceNode* NearestNode = nullptr; + float MinDist = 0.0f; + + for (auto it = EligibleResNodes.begin(); it != EligibleResNodes.end(); it++) + { + AvHAIResourceNode* ThisNode = (*it); + + if (!ThisNode || ThisNode->OwningTeam == BotTeam) { continue; } + + int NumDesiredPlayers = (ThisNode->OwningTeam == EnemyTeam) ? 2 : 1; + int NumAssignedPlayers = AICOMM_GetNumPlayersAssignedToOrder(pBot, ThisNode->ResourceEntity->edict(), ORDERPURPOSE_SECURE_RESNODE); + + if (NumAssignedPlayers >= NumDesiredPlayers) { continue; } + + float ThisDist = vDist2DSq(TeamStartingLocation, ThisNode->Location); + + if (ThisNode->OwningTeam == EnemyTeam) + { + ThisDist *= 2.0f; + } + + if (!NearestNode || ThisDist < MinDist) + { + NearestNode = ThisNode; + MinDist = ThisDist; + } + + } + + if (NearestNode) + { + edict_t* NewAssignee = AICOMM_GetPlayerWithNoOrderNearestLocation(pBot, NearestNode->Location); + + if (!FNullEnt(NewAssignee)) + { + AICOMM_AssignNewPlayerOrder(pBot, NewAssignee, NearestNode->ResourceEntity->edict(), ORDERPURPOSE_SECURE_RESNODE); + } + } + + return; + } + vector Hives = AITAC_GetAllHives(); AvHAIHiveDefinition* EmptyHive = nullptr; @@ -1456,6 +1542,12 @@ bool AICOMM_PerformNextSecureHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefini Vector OutpostLocation = (ExistingStructure) ? ExistingStructure->Location : HiveToSecure->FloorLocation; + if (HiveToSecure->HiveResNodeRef && HiveToSecure->HiveResNodeRef->OwningTeam == TEAM_IND) + { + AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_RESTOWER, HiveToSecure->HiveResNodeRef->Location); + return true; + } + if (ExistingStructure) { if (ExistingStructure->StructureType == STRUCTURE_MARINE_PHASEGATE) @@ -1653,6 +1745,8 @@ bool AICOMM_CheckForNextRecycleAction(AvHAIPlayer* pBot) bool AICOMM_CheckForNextSupportAction(AvHAIPlayer* pBot) { + AvHTeamNumber CommanderTeam = pBot->Player->GetTeam(); + AICOMM_CheckNewRequests(pBot); ai_commander_request* NextRequest = nullptr; @@ -1682,8 +1776,66 @@ bool AICOMM_CheckForNextSupportAction(AvHAIPlayer* pBot) } - // We didn't find any requests outstanding - if (!NextRequest) { return false; } + // We didn't find any requests outstanding, see if we want to pro-actively drop stuff for our team + if (!NextRequest) + { + int NumDesiredWelders = 1; + int NumTeamWelders = AITAC_GetNumWeaponsInPlay(CommanderTeam, WEAPON_MARINE_WELDER); + + vector AllNodes = AITAC_GetAllResourceNodes(); + + for (auto it = AllNodes.begin(); it != AllNodes.end(); it++) + { + AvHAIResourceNode* ThisNode = (*it); + + unsigned int TeamReachabilityFlags = (CommanderTeam == AIMGR_GetTeamANumber()) ? ThisNode->TeamAReachabilityFlags : ThisNode->TeamBReachabilityFlags; + + if ((TeamReachabilityFlags & AI_REACHABILITY_WELDER) && !(TeamReachabilityFlags & AI_REACHABILITY_MARINE)) + { + NumDesiredWelders++; + break; + } + + } + + vector AllHives = AITAC_GetAllHives(); + + for (auto it = AllHives.begin(); it != AllHives.end(); it++) + { + AvHAIHiveDefinition* ThisHive = (*it); + + unsigned int TeamReachabilityFlags = (CommanderTeam == AIMGR_GetTeamANumber()) ? ThisHive->TeamAReachabilityFlags : ThisHive->TeamBReachabilityFlags; + + if ((TeamReachabilityFlags & AI_REACHABILITY_WELDER) && !(TeamReachabilityFlags & AI_REACHABILITY_MARINE)) + { + NumDesiredWelders++; + break; + } + } + + NumDesiredWelders = imini(NumDesiredWelders, (AIMGR_GetNumPlayersOnTeam(CommanderTeam) / 2)); + + if (NumTeamWelders < NumDesiredWelders) + { + 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; + } + } + + return false; + } edict_t* Requestor = NextRequest->Requestor; @@ -1740,12 +1892,14 @@ bool AICOMM_CheckForNextSupportAction(AvHAIPlayer* pBot) AvHAIWeapon WeaponType = UTIL_GetPlayerPrimaryWeapon(thePlayer); + // Requesting player doesn't have a primary weapon, check if they have a pistol if (WeaponType == WEAPON_INVALID) { bFillPrimaryWeapon = false; WeaponType = UTIL_GetPlayerSecondaryWeapon(thePlayer); } + // They've only got a knife, don't bother dropping ammo if (WeaponType == WEAPON_INVALID) { NextRequest->bResponded = true; @@ -1755,6 +1909,7 @@ bool AICOMM_CheckForNextSupportAction(AvHAIPlayer* pBot) int AmmoDeficit = (bFillPrimaryWeapon) ? (UTIL_GetPlayerPrimaryMaxAmmoReserve(thePlayer) - UTIL_GetPlayerPrimaryAmmoReserve(thePlayer)) : (UTIL_GetPlayerSecondaryMaxAmmoReserve(thePlayer) - UTIL_GetPlayerSecondaryAmmoReserve(thePlayer)); int WeaponClipSize = (bFillPrimaryWeapon) ? UTIL_GetPlayerPrimaryWeaponMaxClipSize(thePlayer) : UTIL_GetPlayerSecondaryWeaponMaxClipSize(thePlayer); + // Player already has full ammo, they're yanking our chain if (AmmoDeficit == 0) { NextRequest->bResponded = true; @@ -1762,11 +1917,13 @@ bool AICOMM_CheckForNextSupportAction(AvHAIPlayer* pBot) } int DesiredNumAmmoPacks = (int)(ceilf((float)AmmoDeficit / (float)WeaponClipSize)); + // Don't drop more than 5 at any one time DesiredNumAmmoPacks = clampi(DesiredNumAmmoPacks, 0, 5); int NumAmmoPacksPresent = AITAC_GetNumItemsInLocation(Requestor->v.origin, DEPLOYABLE_ITEM_AMMO, (AvHTeamNumber)Requestor->v.team, AI_REACHABILITY_MARINE, 0.0f, UTIL_MetresToGoldSrcUnits(5.0f), false); DesiredNumAmmoPacks -= NumAmmoPacksPresent; + // Do we need to drop any ammo, or has the player got enough surrounding them already? if (DesiredNumAmmoPacks > 0) { Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(2.0f)); @@ -1774,6 +1931,7 @@ bool AICOMM_CheckForNextSupportAction(AvHAIPlayer* pBot) if (bSuccess) { + // We've dropped enough that the player has enough to fill their boots. Mission accomplished if (DesiredNumAmmoPacks <= 1) { NextRequest->bResponded = true; @@ -1784,6 +1942,7 @@ bool AICOMM_CheckForNextSupportAction(AvHAIPlayer* pBot) } else { + // Player already has enough ammo packs deployed by them to satisfy. Don't drop any more NextRequest->bResponded = true; } @@ -1792,10 +1951,10 @@ bool AICOMM_CheckForNextSupportAction(AvHAIPlayer* pBot) if (NextRequest->RequestType == COMMANDER_NEXTIDLE) { + // TODO: Have the commander prioritise this player when looking for people to give orders to NextRequest->bResponded = true; } - return false; } diff --git a/main/source/mod/AIPlayers/AvHAICommander.h b/main/source/mod/AIPlayers/AvHAICommander.h index 472b4602..84837a13 100644 --- a/main/source/mod/AIPlayers/AvHAICommander.h +++ b/main/source/mod/AIPlayers/AvHAICommander.h @@ -63,5 +63,7 @@ bool AICOMM_ShouldCommanderLeaveChair(AvHAIPlayer* pBot); const AvHAIResourceNode* AICOMM_GetNearestResourceNodeCapOpportunity(const AvHTeamNumber Team, const Vector SearchLocation); const AvHAIHiveDefinition* AICOMM_GetHiveSiegeOpportunityNearestLocation(AvHAIPlayer* CommanderBot, const Vector SearchLocation); +bool AICOMM_ShouldCommanderPrioritiseNodes(AvHAIPlayer* pBot); + #endif // AVH_AI_COMMANDER_H \ No newline at end of file diff --git a/main/source/mod/AIPlayers/AvHAIConstants.h b/main/source/mod/AIPlayers/AvHAIConstants.h index b878b55d..df4cf9b4 100644 --- a/main/source/mod/AIPlayers/AvHAIConstants.h +++ b/main/source/mod/AIPlayers/AvHAIConstants.h @@ -236,8 +236,8 @@ typedef struct _RESOURCE_NODE edict_t* ActiveTowerEntity = nullptr; // Reference to the resource tower edict (if capped) bool bIsBaseNode = false; // Is this a node in the marine base or active alien hive? edict_t* ParentHive = nullptr; - unsigned int TeamAReachabilityFlags = AI_REACHABILITY_NONE; // Is this reachable by the bots? Checks for marine reachability only - unsigned int TeamBReachabilityFlags = AI_REACHABILITY_NONE; // Is this reachable by the bots? Checks for marine reachability only + unsigned int TeamAReachabilityFlags = AI_REACHABILITY_NONE; // Who on team A can reach this node? + unsigned int TeamBReachabilityFlags = AI_REACHABILITY_NONE; // Who on team B can reach this node? bool bReachabilityMarkedDirty = false; // Reachability needs to be recalculated float NextReachabilityRefreshTime = 0.0f; } AvHAIResourceNode; @@ -255,6 +255,8 @@ typedef struct _HIVE_DEFINITION_T unsigned int ObstacleRefs[MAX_NAV_MESHES]; // When in progress or built, will place an obstacle so bots don't try to walk through it float NextFloorLocationCheck = 0.0f; // When should the closest navigable point to the hive be calculated? Used to delay the check after a hive is built AvHTeamNumber OwningTeam = TEAM_IND; // Which team owns this hive currently (TEAM_IND if empty) + unsigned int TeamAReachabilityFlags = AI_REACHABILITY_NONE; // Who on team A can reach this node? + unsigned int TeamBReachabilityFlags = AI_REACHABILITY_NONE; // Who on team B can reach this node? } AvHAIHiveDefinition; @@ -400,6 +402,35 @@ typedef enum BUILD_ATTEMPT_FAILED } BotBuildAttemptStatus; +typedef enum +{ + MOVE_TASK_NONE = 0, + MOVE_TASK_USE, + MOVE_TASK_BREAK, + MOVE_TASK_TOUCH, + MOVE_TASK_PICKUP, + MOVE_TASK_WELD +} BotMovementTaskType; + +// Door type. Not currently used, future feature so bots know how to open a door +enum DoorActivationType +{ + DOOR_NONE, // No type, cannot be activated (permanently open/shut) + DOOR_USE, // Door activated by using it directly + DOOR_TRIGGER,// Door activated by touching a trigger_once or trigger_multiple + DOOR_BUTTON, // Door activated by pressing a button + DOOR_WELD, // Door activated by welding something + DOOR_SHOOT, // Door activated by being shot + DOOR_BREAK // Door activated by breaking something +}; + +// Door type. Not currently used, future feature so bots know how to open a door +enum NavDoorType +{ + DOORTYPE_DOOR, // No type, cannot be activated (permanently open/shut) + DOORTYPE_PLAT, // Door activated by using it directly + DOORTYPE_TRAIN // Door activated by touching a trigger_once or trigger_multiple +}; // Bot path node. A path will be several of these strung together to lead the bot to its destination typedef struct _BOT_PATH_NODE @@ -417,7 +448,8 @@ typedef struct _ENEMY_STATUS { AvHPlayer* EnemyPlayer = nullptr; edict_t* EnemyEdict = nullptr; // Reference to the enemy player edict - Vector LastSeenLocation = g_vecZero; // The last visibly-confirmed location of the player + Vector LastVisibleLocation = g_vecZero; // The last point the bot saw the target + Vector LastSeenLocation = g_vecZero; // The last visibly-confirmed location of the player or tracked location (if parasited / motion tracked) Vector LastFloorPosition = g_vecZero; // Nearest point on the floor where the enemy was (for moving towards it) Vector LastSeenVelocity = g_vecZero; // Last visibly-confirmed movement direction of the player Vector PendingSeenLocation = g_vecZero; // The last visibly-confirmed location of the player @@ -469,6 +501,7 @@ typedef struct _AVH_AI_PLAYER_TASK float TaskLength = 0.0f; // If a task has gone on longer than this time, it will be considered completed } AvHAIPlayerTask; + typedef struct _AVH_AI_BUILD_ATTEMPT { AvHAIDeployableStructureType AttemptedStructureType = STRUCTURE_NONE; @@ -479,6 +512,30 @@ typedef struct _AVH_AI_BUILD_ATTEMPT AvHAIBuildableStructure* LinkedStructure = nullptr; } AvHAIBuildAttempt; +typedef struct _DOOR_TRIGGER +{ + CBaseEntity* Entity = nullptr; + CBaseToggle* ToggleEnt = nullptr; + edict_t* Edict = nullptr; + DoorActivationType TriggerType = DOOR_NONE; + bool bIsActivated = false; + CBaseEntity* TriggerChangeTargetRef = nullptr; + float ActivationDelay = 0.0f; + float LastActivatedTime = 0.0f; + TOGGLE_STATE LastToggleState = TS_AT_BOTTOM; + float LastNextThink = 0.0f; + float NextActivationTime = 0.0f; +} DoorTrigger; + +typedef struct _AVH_AI_PLAYER_MOVE_TASK +{ + BotMovementTaskType TaskType = MOVE_TASK_NONE; + Vector TaskLocation = g_vecZero; + edict_t* TaskTarget = nullptr; + DoorTrigger* TriggerToActivate = nullptr; + bool bPathGenerated = false; +} AvHAIPlayerMoveTask; + // Contains the bot's current navigation info, such as current path typedef struct _NAV_STATUS { @@ -523,14 +580,12 @@ typedef struct _NAV_STATUS bool bZig; // Is the bot zigging, or zagging? float NextZigTime; // Controls how frequently they zig or zag - AvHAIPlayerTask MovementTask; - nav_profile NavProfile; bool bNavProfileChanged = false; unsigned int SpecialMovementFlags = 0; // Any special movement flags required for this path (e.g. needs a welder, needs a jetpack etc.) - + AvHAIPlayerMoveTask MovementTask; } nav_status; // Type of goal the commander wants to achieve diff --git a/main/source/mod/AIPlayers/AvHAINavigation.cpp b/main/source/mod/AIPlayers/AvHAINavigation.cpp index 3cfd2eaa..a010d0a1 100644 --- a/main/source/mod/AIPlayers/AvHAINavigation.cpp +++ b/main/source/mod/AIPlayers/AvHAINavigation.cpp @@ -511,7 +511,6 @@ void UTIL_RemoveStructureTemporaryObstacles(AvHAIBuildableStructure* Structure) void UTIL_AddTemporaryObstacles(const Vector Location, float Radius, float Height, int area, unsigned int* ObstacleRefArray) { - unsigned int ObstacleNum = 0; float Pos[3] = { Location.x, Location.z - (Height * 0.5f), -Location.y }; @@ -526,7 +525,7 @@ void UTIL_AddTemporaryObstacles(const Vector Location, float Radius, float Heigh ObstacleRefArray[i] = (unsigned int)ObsRef; - if (ObstacleNum > 0) + if ((unsigned int)ObsRef > 0) { bNavMeshModified = true; } @@ -1822,6 +1821,11 @@ dtStatus FindPathClosestToPoint(AvHAIPlayer* pBot, const BotMoveStyle MoveStyle, pBot->BotNavInfo.SpecialMovementFlags |= CurrFlags; + if (pBot->BotNavInfo.SpecialMovementFlags & SAMPLE_POLYFLAGS_WELD) + { + bool bPing = true; + } + // End alignment to floor // For ladders and wall climbing, calculate the climb height needed to complete the move. @@ -2063,7 +2067,7 @@ void CheckAndHandleDoorObstruction(AvHAIPlayer* pBot) return; } - AITASK_SetWeldTask(pBot, &pBot->BotNavInfo.MovementTask, BlockingDoorEdict, true); + NAV_SetWeldMovementTask(pBot, BlockingDoorEdict, nullptr); return; } @@ -2126,7 +2130,7 @@ void CheckAndHandleDoorObstruction(AvHAIPlayer* pBot) // Door must be shot to open if (Door->ActivationType == DOOR_SHOOT) { - BotAttackNonPlayerTarget(pBot, Door->DoorEdict); + BotShootTarget(pBot, GetPlayerCurrentWeapon(pBot->Player), Door->DoorEdict); return; } @@ -2139,19 +2143,19 @@ void CheckAndHandleDoorObstruction(AvHAIPlayer* pBot) { Vector UseLocation = UTIL_GetButtonFloorLocation(pBot->Edict->v.origin, Trigger->Edict); - AITASK_SetUseTask(pBot, &pBot->BotNavInfo.MovementTask, Trigger->Edict, UseLocation, true); + NAV_SetUseMovementTask(pBot, Trigger->Edict, Trigger); } else if (Trigger->TriggerType == DOOR_TRIGGER) { - AITASK_SetTouchTask(pBot, &pBot->BotNavInfo.MovementTask, Trigger->Edict, true); + NAV_SetTouchMovementTask(pBot, Trigger->Edict, Trigger); } else if (Trigger->TriggerType == DOOR_WELD) { - AITASK_SetWeldTask(pBot, &pBot->BotNavInfo.MovementTask, Trigger->Edict, true); + NAV_SetWeldMovementTask(pBot, Trigger->Edict, Trigger); } else if (Trigger->TriggerType == DOOR_BREAK) { - AITASK_SetAttackTask(pBot, &pBot->BotNavInfo.MovementTask, Trigger->Edict, true); + NAV_SetBreakMovementTask(pBot, Trigger->Edict, Trigger); } return; @@ -3623,7 +3627,7 @@ void LiftMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint) { if (vDist2DSq(pBot->Edict->v.origin, StartPoint) > sqrf(50.0f)) { - AITASK_SetMoveTask(pBot, &pBot->BotNavInfo.MovementTask, StartPoint, true); + NAV_SetMoveMovementTask(pBot, StartPoint, nullptr); } return; } @@ -3634,7 +3638,7 @@ void LiftMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint) { if (vDist2DSq(pBot->Edict->v.origin, StartPoint) > sqrf(50.0f)) { - AITASK_SetMoveTask(pBot, &pBot->BotNavInfo.MovementTask, StartPoint, true); + NAV_SetMoveMovementTask(pBot, StartPoint, nullptr); return; } @@ -3700,7 +3704,7 @@ void LiftMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint) { if (vDist2DSq(pBot->Edict->v.origin, StartPoint) > sqrf(32.0f)) { - AITASK_SetMoveTask(pBot, &pBot->BotNavInfo.MovementTask, StartPoint, true); + NAV_SetMoveMovementTask(pBot, StartPoint, nullptr); } } @@ -3764,19 +3768,19 @@ void LiftMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint) { Vector UseLocation = UTIL_GetButtonFloorLocation(pBot->Edict->v.origin, NearestLiftTrigger->Edict); - AITASK_SetUseTask(pBot, &pBot->BotNavInfo.MovementTask, NearestLiftTrigger->Edict, UseLocation, true); + NAV_SetUseMovementTask(pBot, NearestLiftTrigger->Edict, NearestLiftTrigger); } else if (NearestLiftTrigger->TriggerType == DOOR_TRIGGER) { - AITASK_SetTouchTask(pBot, &pBot->BotNavInfo.MovementTask, NearestLiftTrigger->Edict, true); + NAV_SetTouchMovementTask(pBot, NearestLiftTrigger->Edict, NearestLiftTrigger); } else if (NearestLiftTrigger->TriggerType == DOOR_WELD) { - AITASK_SetWeldTask(pBot, &pBot->BotNavInfo.MovementTask, NearestLiftTrigger->Edict, true); + NAV_SetWeldMovementTask(pBot, NearestLiftTrigger->Edict, NearestLiftTrigger); } else if (NearestLiftTrigger->TriggerType == DOOR_BREAK) { - AITASK_SetAttackTask(pBot, &pBot->BotNavInfo.MovementTask, NearestLiftTrigger->Edict, true); + NAV_SetBreakMovementTask(pBot, NearestLiftTrigger->Edict, NearestLiftTrigger); } return; @@ -3785,7 +3789,7 @@ void LiftMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint) { if (!bIsOnLift && vDist2DSq(pBot->Edict->v.origin, StartPoint) > sqrf(50.0f) && !bIsLiftAtOrNearStart) { - AITASK_SetMoveTask(pBot, &pBot->BotNavInfo.MovementTask, StartPoint, true); + NAV_SetMoveMovementTask(pBot, StartPoint, nullptr); } else { @@ -4958,6 +4962,23 @@ bool AbortCurrentMove(AvHAIPlayer* pBot, const Vector NewDestination) bool bReverseCourse = (vDist3DSq(DestinationPointOnLine, MoveFrom) < vDist3DSq(DestinationPointOnLine, MoveTo)); + if (flag == SAMPLE_POLYFLAGS_LIFT) + { + if (UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, MoveFrom) || UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, MoveTo)) + { + return true; + } + + if (bReverseCourse) + { + LiftMove(pBot, MoveTo, MoveFrom); + } + else + { + LiftMove(pBot, MoveFrom, MoveTo); + } + } + if (flag == SAMPLE_POLYFLAGS_WALK) { if (UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, MoveFrom) || UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, MoveTo)) @@ -5337,9 +5358,13 @@ bool MoveTo(AvHAIPlayer* pBot, const Vector Destination, const BotMoveStyle Move bool bIsFlyingProfile = pBot->BotNavInfo.NavProfile.bFlyingProfile; bool bNavProfileChanged = pBot->BotNavInfo.bNavProfileChanged; bool bForceRecalculation = (pBot->BotNavInfo.NextForceRecalc > 0.0f && gpGlobals->time >= pBot->BotNavInfo.NextForceRecalc); + bool bIsPerformingMoveTask = (BotNavInfo->MovementTask.TaskType != MOVE_TASK_NONE && vEquals(Destination, BotNavInfo->MovementTask.TaskLocation, GetPlayerRadius(pBot->Player))); + bool bEndGoalChanged = (!vEquals(Destination, BotNavInfo->TargetDestination, GetPlayerRadius(pBot->Player)) && !bIsPerformingMoveTask); + bool bMoveTaskGenerated = (BotNavInfo->MovementTask.TaskType == MOVE_TASK_NONE || (vEquals(BotNavInfo->PathDestination, BotNavInfo->MovementTask.TaskLocation, GetPlayerRadius(pBot->Player)))); + // Only recalculate the path if there isn't a path, or something has changed and enough time has elapsed since the last path calculation - bool bShouldCalculatePath = (bNavProfileChanged || bForceRecalculation || BotNavInfo->CurrentPath.size() == 0 || !vEquals(Destination, BotNavInfo->TargetDestination, GetPlayerRadius(pBot->Player))); + bool bShouldCalculatePath = (bNavProfileChanged || bForceRecalculation || BotNavInfo->CurrentPath.size() == 0 || bEndGoalChanged || !bMoveTaskGenerated); if (bShouldCalculatePath) { @@ -5348,6 +5373,12 @@ bool MoveTo(AvHAIPlayer* pBot, const Vector Destination, const BotMoveStyle Move if (!bIsFlyingProfile && !pBot->BotNavInfo.IsOnGround) { return true; } dtStatus PathFindingStatus = DT_FAILURE; + + if (bEndGoalChanged) + { + ClearBotPath(pBot); + NAV_ClearMovementTask(pBot); + } if (bIsFlyingProfile) { @@ -5365,11 +5396,17 @@ bool MoveTo(AvHAIPlayer* pBot, const Vector Destination, const BotMoveStyle Move pBot->BotNavInfo.bNavProfileChanged = false; if (dtStatusSucceed(PathFindingStatus)) - { - BotNavInfo->PathDestination = BotNavInfo->CurrentPath.back().Location; + { ClearBotStuckMovement(pBot); pBot->BotNavInfo.TotalStuckTime = 0.0f; - BotNavInfo->TargetDestination = Destination; + BotNavInfo->PathDestination = Destination; + + if (!bIsPerformingMoveTask) + { + BotNavInfo->ActualMoveDestination = BotNavInfo->CurrentPath.back().Location; + BotNavInfo->TargetDestination = Destination; + } + BotNavInfo->CurrentPathPoint = BotNavInfo->CurrentPath.begin(); } else @@ -5410,252 +5447,40 @@ bool MoveTo(AvHAIPlayer* pBot, const Vector Destination, const BotMoveStyle Move } } - if (BotNavInfo->CurrentPath.size() > 0) + if (!bIsPerformingMoveTask && BotNavInfo->MovementTask.TaskType != MOVE_TASK_NONE) { - if (IsBotPermaStuck(pBot)) + if (NAV_IsMovementTaskStillValid(pBot)) { - BotSuicide(pBot); - return false; - } - - if (pBot->Edict->v.flags & FL_INWATER) - { - BotFollowSwimPath(pBot); - } - else - { - if (bIsFlyingProfile) - { - BotFollowFlightPath(pBot); - } - else - { - BotFollowPath(pBot); - } - } - - // Check to ensure BotFollowFlightPath or BotFollowPath haven't cleared the path (will happen if reached end of path) - if (BotNavInfo->CurrentPathPoint != BotNavInfo->CurrentPath.end()) - { - HandlePlayerAvoidance(pBot, BotNavInfo->CurrentPathPoint->Location); - BotMovementInputs(pBot); - } - - return true; - } - - return false; - -} - -bool MoveTo_OLD(AvHAIPlayer* pBot, const Vector Destination, const BotMoveStyle MoveStyle, const float MaxAcceptableDist) -{ - nav_status* BotNavInfo = &pBot->BotNavInfo; - - // If we are currently in the process of getting back on the navmesh, don't interrupt - if (BotNavInfo->UnstuckMoveLocation != g_vecZero) - { - if (IsBotPermaStuck(pBot)) - { - BotSuicide(pBot); - return false; - } - - Vector MoveTarget = BotNavInfo->UnstuckMoveStartLocation + (UTIL_GetVectorNormal2D(BotNavInfo->UnstuckMoveLocation - BotNavInfo->UnstuckMoveStartLocation) * 100.0f); - - MoveDirectlyTo(pBot, MoveTarget); - - Vector ClosestPoint = vClosestPointOnLine2D(BotNavInfo->UnstuckMoveStartLocation, BotNavInfo->UnstuckMoveLocation, pBot->Edict->v.origin); - - bool bAtOrPastMoveLocation = vEquals2D(ClosestPoint, BotNavInfo->UnstuckMoveLocation, 0.1f); - - if (bAtOrPastMoveLocation) - { - ClearBotStuckMovement(pBot); + NAV_ProgressMovementTask(pBot); return true; } else { - // This should only be a short movement, if we don't get there in a few seconds then give up - if ((gpGlobals->time - BotNavInfo->UnstuckMoveLocationStartTime) > 5.0f) - { - ClearBotStuckMovement(pBot); - return true; - } - - BotJump(pBot); - - if (IsPlayerSkulk(pBot->Edict)) - { - pBot->Button &= ~IN_DUCK; - } - else - { - pBot->Button |= IN_DUCK; - } - - - - return true; - } - } - - pBot->BotNavInfo.MoveStyle = MoveStyle; - UTIL_UpdateBotMovementStatus(pBot); - - bool bIsFlyingProfile = pBot->BotNavInfo.NavProfile.bFlyingProfile; - - bool bForceRecalculation = (pBot->BotNavInfo.NextForceRecalc > 0.0f && gpGlobals->time >= pBot->BotNavInfo.NextForceRecalc); - - if (BotNavInfo->CurrentPath.size() > 0) - { - if (pBot->BotNavInfo.CurrentPathPoint == pBot->BotNavInfo.CurrentPath.end()) - { + NAV_ClearMovementTask(pBot); ClearBotPath(pBot); return true; } - - bool bNavProfileChanged = pBot->BotNavInfo.bNavProfileChanged; - - bool bHasMovementTask = (BotNavInfo->MovementTask.TaskType != TASK_NONE); - - Vector MoveTaskDestination = g_vecZero; - Vector MoveTaskOrigin = g_vecZero; - Vector MoveSecondaryOrigin = g_vecZero; - - if (bHasMovementTask) - { - MoveTaskDestination = BotNavInfo->MovementTask.TaskLocation; - MoveTaskOrigin = (!FNullEnt(BotNavInfo->MovementTask.TaskTarget)) ? BotNavInfo->MovementTask.TaskTarget->v.origin : g_vecZero; - MoveSecondaryOrigin = (!FNullEnt(BotNavInfo->MovementTask.TaskSecondaryTarget)) ? BotNavInfo->MovementTask.TaskSecondaryTarget->v.origin : g_vecZero; - } - - bool bUltimateDestinationChanged = !vEquals(Destination, BotNavInfo->TargetDestination, GetPlayerRadius(pBot->Player)) && !vEquals(Destination, MoveTaskDestination) && !vEquals(Destination, MoveTaskOrigin) && !vEquals(Destination, MoveSecondaryOrigin); - - bool bHasReachedDestination = BotIsAtLocation(pBot, BotNavInfo->TargetDestination); - - if (bUltimateDestinationChanged || bNavProfileChanged || bHasReachedDestination || bForceRecalculation) - { - // First abort our current move so we don't try to recalculate half-way up a wall or ladder - if (bIsFlyingProfile || AbortCurrentMove(pBot, Destination)) - { - // Don't clear the path if we're in the middle of a movement task - if (!vEquals(Destination, MoveTaskDestination) && !vEquals(Destination, MoveTaskOrigin) && !vEquals(Destination, MoveSecondaryOrigin)) - { - ClearBotPath(pBot); - } - return true; - } - } - else - { - if (bHasMovementTask && !vEquals(Destination, MoveTaskDestination) && !vEquals(Destination, MoveTaskOrigin) && !vEquals(Destination, MoveSecondaryOrigin)) - { - if (AITASK_IsTaskStillValid(pBot, &BotNavInfo->MovementTask)) - { - BotProgressTask(pBot, &BotNavInfo->MovementTask); - return true; - } - else - { - AITASK_ClearBotTask(pBot, &BotNavInfo->MovementTask); - } - } - } - } - - bool bCanRecalculatePath = (gpGlobals->time - pBot->BotNavInfo.LastPathCalcTime > MIN_PATH_RECALC_TIME); - - // Only recalculate the path if there isn't a path, or something has changed and enough time has elapsed since the last path calculation - bool bShouldCalculatePath = bCanRecalculatePath && (bForceRecalculation || BotNavInfo->CurrentPath.size() == 0 || !vEquals(Destination, BotNavInfo->PathDestination)); - - if (bShouldCalculatePath) - { - if (pBot->Player->IsOnLadder()) - { - BotJump(pBot); - return true; - } - - pBot->BotNavInfo.LastPathCalcTime = gpGlobals->time; - BotNavInfo->bNavProfileChanged = false; - BotNavInfo->NextForceRecalc = 0.0f; - - if (vIsZero(BotNavInfo->TargetDestination)) - { - BotNavInfo->TargetDestination = Destination; - } - - BotNavInfo->PathDestination = Destination; - - dtStatus PathFindingStatus = DT_FAILURE; - - if (bIsFlyingProfile) - { - PathFindingStatus = FindFlightPathToPoint(pBot->BotNavInfo.NavProfile, pBot->Edict->v.origin, Destination, BotNavInfo->CurrentPath, MaxAcceptableDist); - } - else - { - Vector ValidNavmeshPoint = UTIL_ProjectPointToNavmesh(Destination, Vector(max_ai_use_reach, max_ai_use_reach, max_ai_use_reach), pBot->BotNavInfo.NavProfile); - - // Destination is not on the nav mesh, so we can't get close enough - if (vIsZero(ValidNavmeshPoint)) - { - sprintf(pBot->PathStatus, "Could not project destination to navmesh"); - return false; - } - - PathFindingStatus = FindPathClosestToPoint(pBot, pBot->BotNavInfo.MoveStyle, pBot->CollisionHullBottomLocation, ValidNavmeshPoint, BotNavInfo->CurrentPath, MaxAcceptableDist); - } - - if (dtStatusSucceed(PathFindingStatus)) - { - BotNavInfo->ActualMoveDestination = BotNavInfo->CurrentPath.back().Location; - ClearBotStuckMovement(pBot); - pBot->BotNavInfo.TotalStuckTime = 0.0f; - - BotNavInfo->CurrentPathPoint = BotNavInfo->CurrentPath.begin(); - sprintf(pBot->PathStatus, "Path finding successful"); - - } - else - { - Vector PointBackOnPath = FindClosestPointBackOnPath(pBot); - - if (PointBackOnPath != g_vecZero) - { - ClearBotStuckMovement(pBot); - ClearBotPath(pBot); - - BotNavInfo->UnstuckMoveLocation = PointBackOnPath; - BotNavInfo->UnstuckMoveStartLocation = pBot->Edict->v.origin; - BotNavInfo->UnstuckMoveLocationStartTime = gpGlobals->time; - - sprintf(pBot->PathStatus, "Backwards Path Find Successful"); - } - else - { - if (BotNavInfo->LastNavMeshPosition != g_vecZero && vDist2DSq(BotNavInfo->LastNavMeshPosition, pBot->Edict->v.origin) > GetPlayerRadius(pBot->Player)) - { - ClearBotStuckMovement(pBot); - ClearBotPath(pBot); - - BotNavInfo->UnstuckMoveLocation = BotNavInfo->LastNavMeshPosition; - BotNavInfo->UnstuckMoveStartLocation = pBot->Edict->v.origin; - BotNavInfo->UnstuckMoveLocationStartTime = gpGlobals->time; - } - else - { - BotSuicide(pBot); - } - - return false; - } - } } if (BotNavInfo->CurrentPath.size() > 0) { + // If this path requires use of a welder and we don't have one, then find one + if ((pBot->BotNavInfo.SpecialMovementFlags & SAMPLE_POLYFLAGS_WELD) && !PlayerHasWeapon(pBot->Player, WEAPON_MARINE_WELDER)) + { + if (pBot->BotNavInfo.MovementTask.TaskType != MOVE_TASK_PICKUP) + { + nav_profile BaseProfile = GetBaseNavProfile(MARINE_BASE_NAV_PROFILE); + + AvHAIDroppedItem* NearestWelder = AITAC_FindClosestItemToLocation(pBot->Edict->v.origin, DEPLOYABLE_ITEM_WELDER, pBot->Player->GetTeam(), BaseProfile.ReachabilityFlag, 0.0f, 0.0f, true); + + if (NearestWelder) + { + NAV_SetPickupMovementTask(pBot, NearestWelder->edict, nullptr); + return true; + } + } + } + if (IsBotPermaStuck(pBot)) { BotSuicide(pBot); @@ -5689,6 +5514,7 @@ bool MoveTo_OLD(AvHAIPlayer* pBot, const Vector Destination, const BotMoveStyle } return false; + } Vector FindClosestPointBackOnPath(AvHAIPlayer* pBot) @@ -6024,23 +5850,6 @@ void BotFollowPath(AvHAIPlayer* pBot) return; } - // If this path requires use of a welder and we don't have one, then find one - if ((pBot->BotNavInfo.SpecialMovementFlags & SAMPLE_POLYFLAGS_WELD) && !PlayerHasWeapon(pBot->Player, WEAPON_MARINE_WELDER)) - { - if (pBot->BotNavInfo.MovementTask.TaskType != TASK_GET_WEAPON) - { - AITASK_ClearBotTask(pBot, &pBot->BotNavInfo.MovementTask); - - AvHAIDroppedItem* NearestWelder = AITAC_FindClosestItemToLocation(pBot->Edict->v.origin, DEPLOYABLE_ITEM_WELDER, pBot->Player->GetTeam(), pBot->BotNavInfo.NavProfile.ReachabilityFlag, 0.0f, 0.0f, true); - - if (NearestWelder) - { - AITASK_SetPickupTask(pBot, &pBot->BotNavInfo.MovementTask, NearestWelder->edict, true); - return; - } - } - } - Vector MoveTo = BotNavInfo->CurrentPathPoint->Location; unsigned int CurrentFlag = BotNavInfo->CurrentPathPoint->flag; @@ -6834,8 +6643,6 @@ void ClearBotPath(AvHAIPlayer* pBot) pBot->BotNavInfo.SpecialMovementFlags = 0; - AITASK_ClearBotTask(pBot, &pBot->BotNavInfo.MovementTask); - pBot->BotNavInfo.bNavProfileChanged = false; pBot->BotNavInfo.TargetDestination = g_vecZero; @@ -8259,4 +8066,228 @@ dtStatus DEBUG_TestFindPath(const nav_profile& NavProfile, const Vector FromLoca } return DT_SUCCESS; +} + +void NAV_SetMoveMovementTask(AvHAIPlayer* pBot, Vector MoveLocation, DoorTrigger* TriggerToActivate) +{ + AvHAIPlayerMoveTask* MoveTask = &pBot->BotNavInfo.MovementTask; + + if (MoveTask->TaskType == MOVE_TASK_TOUCH && vEquals(MoveTask->TaskLocation, MoveLocation)) { return; } + + MoveTask->TaskType = MOVE_TASK_TOUCH; + MoveTask->TaskLocation = MoveLocation; + + vector Path; + dtStatus PathStatus = FindPathClosestToPoint(pBot->BotNavInfo.NavProfile, pBot->CurrentFloorPosition, MoveLocation, Path, 200.0f); + + if (dtStatusSucceed(PathStatus)) + { + MoveTask->TaskLocation = Path.back().Location; + } +} + +void NAV_SetTouchMovementTask(AvHAIPlayer* pBot, edict_t* EntityToTouch, DoorTrigger* TriggerToActivate) +{ + AvHAIPlayerMoveTask* MoveTask = &pBot->BotNavInfo.MovementTask; + + if (MoveTask->TaskType == MOVE_TASK_TOUCH && MoveTask->TaskTarget == EntityToTouch) { return; } + + MoveTask->TaskType = MOVE_TASK_TOUCH; + MoveTask->TaskTarget = EntityToTouch; + MoveTask->TriggerToActivate = TriggerToActivate; + + vector Path; + dtStatus PathStatus = FindPathClosestToPoint(pBot->BotNavInfo.NavProfile, pBot->CurrentFloorPosition, UTIL_GetCentreOfEntity(EntityToTouch), Path, 200.0f); + + if (dtStatusSucceed(PathStatus)) + { + MoveTask->TaskLocation = Path.back().Location; + } +} + +void NAV_SetUseMovementTask(AvHAIPlayer* pBot, edict_t* EntityToUse, DoorTrigger* TriggerToActivate) +{ + AvHAIPlayerMoveTask* MoveTask = &pBot->BotNavInfo.MovementTask; + + if (MoveTask->TaskType == MOVE_TASK_USE && MoveTask->TaskTarget == EntityToUse) { return; } + + NAV_ClearMovementTask(pBot); + + MoveTask->TaskType = MOVE_TASK_USE; + MoveTask->TaskTarget = EntityToUse; + MoveTask->TriggerToActivate = TriggerToActivate; + MoveTask->TaskLocation = UTIL_GetButtonFloorLocation(pBot->Edict->v.origin, EntityToUse); +} + +void NAV_SetBreakMovementTask(AvHAIPlayer* pBot, edict_t* EntityToBreak, DoorTrigger* TriggerToActivate) +{ + AvHAIPlayerMoveTask* MoveTask = &pBot->BotNavInfo.MovementTask; + + if (MoveTask->TaskType == MOVE_TASK_BREAK && MoveTask->TaskTarget == EntityToBreak) { return; } + + NAV_ClearMovementTask(pBot); + + MoveTask->TaskType = MOVE_TASK_BREAK; + MoveTask->TaskTarget = EntityToBreak; + MoveTask->TriggerToActivate = TriggerToActivate; + + MoveTask->TaskLocation = UTIL_GetButtonFloorLocation(pBot->Edict->v.origin, EntityToBreak); +} + +void NAV_SetWeldMovementTask(AvHAIPlayer* pBot, edict_t* EntityToWeld, DoorTrigger* TriggerToActivate) +{ + AvHAIPlayerMoveTask* MoveTask = &pBot->BotNavInfo.MovementTask; + + if (MoveTask->TaskType == MOVE_TASK_WELD && MoveTask->TaskTarget == EntityToWeld) { return; } + + NAV_ClearMovementTask(pBot); + + MoveTask->TaskType = MOVE_TASK_WELD; + MoveTask->TaskTarget = EntityToWeld; + MoveTask->TriggerToActivate = TriggerToActivate; + MoveTask->TaskLocation = UTIL_GetButtonFloorLocation(pBot->Edict->v.origin, EntityToWeld); +} + +void NAV_ClearMovementTask(AvHAIPlayer* pBot) +{ + pBot->BotNavInfo.MovementTask.TaskType = MOVE_TASK_NONE; + pBot->BotNavInfo.MovementTask.TaskLocation = ZERO_VECTOR; + pBot->BotNavInfo.MovementTask.TaskTarget = nullptr; + pBot->BotNavInfo.MovementTask.TriggerToActivate = nullptr; +} + +void NAV_ProgressMovementTask(AvHAIPlayer* pBot) +{ + AvHAIPlayerMoveTask* MoveTask = &pBot->BotNavInfo.MovementTask; + + if (MoveTask->TaskType == MOVE_TASK_NONE) { return; } + + if (MoveTask->TaskType == MOVE_TASK_USE) + { + if (IsPlayerInUseRange(pBot->Edict, MoveTask->TaskTarget)) + { + BotUseObject(pBot, MoveTask->TaskTarget, false); + ClearBotStuck(pBot); + return; + } + } + + if (MoveTask->TaskType == MOVE_TASK_BREAK) + { + AvHAIWeapon Weapon = WEAPON_INVALID; + + if (IsPlayerMarine(pBot->Edict)) + { + Weapon = BotMarineChooseBestWeaponForStructure(pBot, MoveTask->TaskTarget); + } + else + { + Weapon = BotAlienChooseBestWeaponForStructure(pBot, MoveTask->TaskTarget); + } + + BotAttackResult AttackResult = PerformAttackLOSCheck(pBot, Weapon, MoveTask->TaskTarget); + + if (AttackResult == ATTACK_SUCCESS) + { + // If we were ducking before then keep ducking + if (pBot->Edict->v.oldbuttons & IN_DUCK) + { + pBot->Button |= IN_DUCK; + } + + BotShootTarget(pBot, Weapon, MoveTask->TaskTarget); + + ClearBotStuck(pBot); + + return; + } + } + + if (MoveTask->TaskType == MOVE_TASK_WELD) + { + if (IsPlayerInUseRange(pBot->Edict, MoveTask->TaskTarget)) + { + Vector BBMin = MoveTask->TaskTarget->v.absmin; + Vector BBMax = MoveTask->TaskTarget->v.absmax; + + vScaleBB(BBMin, BBMax, 0.75f); + + BotLookAt(pBot, vClosestPointOnBB(pBot->CurrentEyePosition, BBMin, BBMax)); + pBot->DesiredCombatWeapon = WEAPON_MARINE_WELDER; + + if (GetPlayerCurrentWeapon(pBot->Player) != WEAPON_MARINE_WELDER) + { + return; + } + + pBot->Button |= IN_ATTACK; + + ClearBotStuck(pBot); + + return; + } + } + + MoveTo(pBot, MoveTask->TaskLocation, MOVESTYLE_NORMAL); + +} + +bool NAV_IsMovementTaskStillValid(AvHAIPlayer* pBot) +{ + AvHAIPlayerMoveTask* MoveTask = &pBot->BotNavInfo.MovementTask; + + if (MoveTask->TaskType == MOVE_TASK_NONE) { return false; } + + if (MoveTask->TriggerToActivate) + { + if (!MoveTask->TriggerToActivate->bIsActivated) { return false; } + if (MoveTask->TriggerToActivate->NextActivationTime > gpGlobals->time) { return false; } + } + + if (MoveTask->TaskType == MOVE_TASK_USE) + { + return (vDist2DSq(pBot->Edict->v.origin, MoveTask->TaskLocation) > sqrf(GetPlayerRadius(pBot->Player)) || fabsf(pBot->Edict->v.origin.z - MoveTask->TaskLocation.z) > 50.0f); + } + + if (MoveTask->TaskType == MOVE_TASK_PICKUP) + { + return (!FNullEnt(MoveTask->TaskTarget) && !(MoveTask->TaskTarget->v.effects & EF_NODRAW)); + } + + if (MoveTask->TaskType == MOVE_TASK_TOUCH) + { + return (!FNullEnt(MoveTask->TaskTarget) && !IsPlayerTouchingEntity(pBot->Edict, MoveTask->TaskTarget)); + } + + if (MoveTask->TaskType == MOVE_TASK_BREAK) + { + return (!FNullEnt(MoveTask->TaskTarget) && MoveTask->TaskTarget->v.deadflag == DEAD_NO && MoveTask->TaskTarget->v.health > 0.0f); + } + + if (MoveTask->TaskType == MOVE_TASK_WELD) + { + AvHWeldable* WeldableRef = dynamic_cast(CBaseEntity::Instance(MoveTask->TaskTarget)); + + if (WeldableRef) + { + return !WeldableRef->GetIsWelded(); + } + } + + return false; + +} + +void NAV_SetPickupMovementTask(AvHAIPlayer* pBot, edict_t* ThingToPickup, DoorTrigger* TriggerToActivate) +{ + AvHAIPlayerMoveTask* MoveTask = &pBot->BotNavInfo.MovementTask; + + if (MoveTask->TaskType == MOVE_TASK_PICKUP && MoveTask->TaskTarget == ThingToPickup) { return; } + + NAV_ClearMovementTask(pBot); + + MoveTask->TaskType = MOVE_TASK_PICKUP; + MoveTask->TaskTarget = ThingToPickup; + MoveTask->TriggerToActivate = TriggerToActivate; + MoveTask->TaskLocation = ThingToPickup->v.origin; } \ No newline at end of file diff --git a/main/source/mod/AIPlayers/AvHAINavigation.h b/main/source/mod/AIPlayers/AvHAINavigation.h index 2092bd10..fde63030 100644 --- a/main/source/mod/AIPlayers/AvHAINavigation.h +++ b/main/source/mod/AIPlayers/AvHAINavigation.h @@ -71,40 +71,6 @@ enum SamplePolyFlags SAMPLE_POLYFLAGS_ALL = -1 // All abilities. }; -// Door type. Not currently used, future feature so bots know how to open a door -enum DoorActivationType -{ - DOOR_NONE, // No type, cannot be activated (permanently open/shut) - DOOR_USE, // Door activated by using it directly - DOOR_TRIGGER,// Door activated by touching a trigger_once or trigger_multiple - DOOR_BUTTON, // Door activated by pressing a button - DOOR_WELD, // Door activated by welding something - DOOR_SHOOT, // Door activated by being shot - DOOR_BREAK // Door activated by breaking something -}; - -// Door type. Not currently used, future feature so bots know how to open a door -enum NavDoorType -{ - DOORTYPE_DOOR, // No type, cannot be activated (permanently open/shut) - DOORTYPE_PLAT, // Door activated by using it directly - DOORTYPE_TRAIN // Door activated by touching a trigger_once or trigger_multiple -}; - -typedef struct _DOOR_TRIGGER -{ - CBaseEntity* Entity = nullptr; - CBaseToggle* ToggleEnt = nullptr; - edict_t* Edict = nullptr; - DoorActivationType TriggerType = DOOR_NONE; - bool bIsActivated = false; - CBaseEntity* TriggerChangeTargetRef = nullptr; - float ActivationDelay = 0.0f; - float LastActivatedTime = 0.0f; - TOGGLE_STATE LastToggleState = TS_AT_BOTTOM; - float LastNextThink = 0.0f; - float NextActivationTime = 0.0f; -} DoorTrigger; // Door reference. Not used, but is a future feature to allow bots to track if a door is open or not, and how to open it etc. typedef struct _NAV_DOOR @@ -505,5 +471,17 @@ void OnOffMeshConnectionAdded(dtOffMeshConnection* NewConnection); const dtOffMeshConnection* DEBUG_FindNearestOffMeshConnectionToPoint(const Vector Point, unsigned int FilterFlags); +void NAV_SetPickupMovementTask(AvHAIPlayer* pBot, edict_t* ThingToPickup, DoorTrigger* TriggerToActivate); +void NAV_SetMoveMovementTask(AvHAIPlayer* pBot, Vector MoveLocation, DoorTrigger* TriggerToActivate); +void NAV_SetTouchMovementTask(AvHAIPlayer* pBot, edict_t* EntityToTouch, DoorTrigger* TriggerToActivate); +void NAV_SetUseMovementTask(AvHAIPlayer* pBot, edict_t* EntityToUse, DoorTrigger* TriggerToActivate); +void NAV_SetBreakMovementTask(AvHAIPlayer* pBot, edict_t* EntityToBreak, DoorTrigger* TriggerToActivate); +void NAV_SetWeldMovementTask(AvHAIPlayer* pBot, edict_t* EntityToWeld, DoorTrigger* TriggerToActivate); + +void NAV_ClearMovementTask(AvHAIPlayer* pBot); + +void NAV_ProgressMovementTask(AvHAIPlayer* pBot); +bool NAV_IsMovementTaskStillValid(AvHAIPlayer* pBot); + #endif // BOT_NAVIGATION_H diff --git a/main/source/mod/AIPlayers/AvHAIPlayer.cpp b/main/source/mod/AIPlayers/AvHAIPlayer.cpp index b9c98c86..03da0a47 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayer.cpp +++ b/main/source/mod/AIPlayers/AvHAIPlayer.cpp @@ -365,40 +365,39 @@ void BotSay(AvHAIPlayer* pBot, bool bTeamSay, float Delay, char* textToSay) } } -void BotReloadWeapons(AvHAIPlayer* pBot) +bool BotReloadWeapons(AvHAIPlayer* pBot) { // Aliens and commander don't reload - if (!IsPlayerMarine(pBot->Edict) || !IsPlayerActiveInGame(pBot->Edict)) { return; } + if (!IsPlayerMarine(pBot->Edict) || !IsPlayerActiveInGame(pBot->Edict)) { return false; } - if (gpGlobals->time - pBot->LastCombatTime > 5.0f) + AvHAIWeapon PrimaryWeapon = UTIL_GetPlayerPrimaryWeapon(pBot->Player); + AvHAIWeapon SecondaryWeapon = GetBotMarineSecondaryWeapon(pBot); + AvHAIWeapon CurrentWeapon = GetPlayerCurrentWeapon(pBot->Player); + + if (WeaponCanBeReloaded(PrimaryWeapon) && UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) < UTIL_GetPlayerPrimaryWeaponMaxClipSize(pBot->Player) && UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0) { - AvHAIWeapon PrimaryWeapon = UTIL_GetPlayerPrimaryWeapon(pBot->Player); - AvHAIWeapon SecondaryWeapon = GetBotMarineSecondaryWeapon(pBot); - AvHAIWeapon CurrentWeapon = GetPlayerCurrentWeapon(pBot->Player); + pBot->DesiredCombatWeapon = PrimaryWeapon; - if (WeaponCanBeReloaded(PrimaryWeapon) && UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) < UTIL_GetPlayerPrimaryWeaponMaxClipSize(pBot->Player) && UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0) + if (CurrentWeapon == PrimaryWeapon) { - pBot->DesiredCombatWeapon = PrimaryWeapon; - - if (CurrentWeapon == PrimaryWeapon) - { - BotReloadCurrentWeapon(pBot); - } - - return; + BotReloadCurrentWeapon(pBot); } - if (WeaponCanBeReloaded(SecondaryWeapon) && BotGetSecondaryWeaponClipAmmo(pBot) < BotGetSecondaryWeaponMaxClipSize(pBot) && BotGetSecondaryWeaponAmmoReserve(pBot) > 0) - { - pBot->DesiredCombatWeapon = SecondaryWeapon; - - if (CurrentWeapon == SecondaryWeapon) - { - BotReloadCurrentWeapon(pBot); - } - return; - } + return true; } + + if (WeaponCanBeReloaded(SecondaryWeapon) && BotGetSecondaryWeaponClipAmmo(pBot) < BotGetSecondaryWeaponMaxClipSize(pBot) && BotGetSecondaryWeaponAmmoReserve(pBot) > 0) + { + pBot->DesiredCombatWeapon = SecondaryWeapon; + + if (CurrentWeapon == SecondaryWeapon) + { + BotReloadCurrentWeapon(pBot); + } + return true; + } + + return false; } void BotDropWeapon(AvHAIPlayer* pBot) @@ -661,7 +660,7 @@ void BotShootTarget(AvHAIPlayer* pBot, AvHAIWeapon AttackWeapon, edict_t* Target return; } - if (CurrentWeapon == WEAPON_NONE) { return; } + if (CurrentWeapon == WEAPON_INVALID) { return; } if (CurrentWeapon == WEAPON_SKULK_XENOCIDE || CurrentWeapon == WEAPON_LERK_PRIMALSCREAM) @@ -1410,6 +1409,12 @@ void BotUpdateView(AvHAIPlayer* pBot) TrackingInfo->bIsAwareOfPlayer = true; TrackingInfo->LastSeenLocation = (bHasLOS) ? VisiblePoint : Enemy->v.origin; + + if (bHasLOS) + { + TrackingInfo->LastVisibleLocation = Enemy->v.origin; + } + TrackingInfo->LastFloorPosition = FloorLocation; if (bHasLOS) @@ -1666,21 +1671,20 @@ void CustomThink(AvHAIPlayer* pBot) { if (IsPlayerMarine(pBot->Player)) { - if (PlayerHasWeapon(pBot->Player, WEAPON_MARINE_GRENADE)) + pBot->CurrentEnemy = BotGetNextEnemyTarget(pBot); + + if (pBot->CurrentEnemy < 0) { - if (!vIsZero(AIDEBUG_GetDebugVector1())) - { - BotThrowGrenadeAtTarget(pBot, AIDEBUG_GetDebugVector1()); - } + MoveTo(pBot, AITAC_GetTeamStartingLocation(AIMGR_GetEnemyTeam(pBot->Player->GetTeam())), MOVESTYLE_NORMAL); } else { - pBot->DesiredCombatWeapon = UTIL_GetPlayerPrimaryWeapon(pBot->Player); + MarineCombatThink(pBot); } AvHAIWeapon DesiredWeapon = (pBot->DesiredMoveWeapon != WEAPON_NONE) ? pBot->DesiredMoveWeapon : pBot->DesiredCombatWeapon; - if (DesiredWeapon != WEAPON_NONE && GetPlayerCurrentWeapon(pBot->Player) != DesiredWeapon) + if (DesiredWeapon != WEAPON_INVALID && GetPlayerCurrentWeapon(pBot->Player) != DesiredWeapon) { BotSwitchToWeapon(pBot, DesiredWeapon); } @@ -1688,14 +1692,14 @@ void CustomThink(AvHAIPlayer* pBot) return; } - if (!IsPlayerFade(pBot->Edict)) + if (!IsPlayerLerk(pBot->Edict)) { - if (pBot->Player->GetResources() < BALANCE_VAR(kFadeCost)) + if (pBot->Player->GetResources() < BALANCE_VAR(kLerkCost)) { - pBot->Player->GiveResources(50.0f); + pBot->Player->GiveResources(30.0f); } - BotEvolveLifeform(pBot, pBot->Edict->v.origin, ALIEN_LIFEFORM_FOUR); + BotEvolveLifeform(pBot, pBot->Edict->v.origin, ALIEN_LIFEFORM_THREE); return; } @@ -1769,6 +1773,31 @@ void UpdateAIPlayerDMRole(AvHAIPlayer* pBot) } +void AIPlayerTakeDamage(AvHAIPlayer* pBot, int damageTaken, edict_t* aggressor) +{ + int aggressorIndex = ENTINDEX(aggressor) - 1; + + if (aggressorIndex > -1 && aggressor->v.team != pBot->Edict->v.team && IsPlayerActiveInGame(aggressor)) + { + pBot->TrackedEnemies[aggressorIndex].LastSeenTime = gpGlobals->time; + + // If the bot can't see the enemy (bCurrentlyVisible is false) then set the last seen location to a random point in the vicinity so the bot doesn't immediately know where they are + if (pBot->TrackedEnemies[aggressorIndex].bIsVisible || vDist2DSq(pBot->TrackedEnemies[aggressorIndex].EnemyEdict->v.origin, pBot->Edict->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(3.0f))) + { + pBot->TrackedEnemies[aggressorIndex].LastSeenLocation = aggressor->v.origin; + } + else + { + // The further the enemy is, the more inaccurate the bot's guess will be where they are + pBot->TrackedEnemies[aggressorIndex].LastSeenLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(SKULK_BASE_NAV_PROFILE), aggressor->v.origin, UTIL_MetresToGoldSrcUnits(5.0f)); + } + + pBot->TrackedEnemies[aggressorIndex].LastSeenVelocity = aggressor->v.velocity; + pBot->TrackedEnemies[aggressorIndex].bIsAwareOfPlayer = true; + pBot->TrackedEnemies[aggressorIndex].bHasLOS = true; + } +} + bool ShouldAIPlayerTakeCommand(AvHAIPlayer* pBot) { AvHAICommanderMode CurrentCommanderMode = AIMGR_GetCommanderMode(); @@ -2160,7 +2189,7 @@ AvHAICombatStrategy GetLerkCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_stat { return COMBAT_STRATEGY_SKIRMISH; } - } +} AvHAICombatStrategy GetFadeCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_status* CurrentEnemy) { @@ -2347,6 +2376,17 @@ AvHAICombatStrategy GetMarineCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_st float CurrentHealthPercent = (pBot->Edict->v.health / pBot->Edict->v.max_health); + float DistToEnemy = vDist2DSq(pBot->Edict->v.origin, CurrentEnemy->LastSeenLocation); + + // If we are doing something important, don't get distracted by enemies that aren't an immediate threat + if (pBot->CurrentTask && pBot->CurrentTask->TaskType == TASK_DEFEND || pBot->CommanderTask.TaskType != TASK_NONE) + { + if ((!CurrentEnemy->bHasLOS || DistToEnemy > sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) && (!vIsZero(pBot->CurrentTask->TaskLocation) && !UTIL_PlayerHasLOSToLocation(CurrentEnemy->EnemyEdict, pBot->CurrentTask->TaskLocation, UTIL_MetresToGoldSrcUnits(30.0f)))) + { + return COMBAT_STRATEGY_IGNORE; + } + } + if (pBot->CurrentCombatStrategy == COMBAT_STRATEGY_RETREAT) { int MinDesiredAmmo = imini(UTIL_GetPlayerPrimaryMaxAmmoReserve(pBot->Player), UTIL_GetPlayerPrimaryWeaponMaxClipSize(pBot->Player) * 2); @@ -2358,18 +2398,28 @@ AvHAICombatStrategy GetMarineCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_st } int NumEnemyAllies = AITAC_GetNumPlayersOnTeamWithLOS(EnemyTeam, EnemyEdict->v.origin, UTIL_MetresToGoldSrcUnits(10.0f), EnemyEdict); + int NumFriendlies = AITAC_GetNumPlayersOnTeamWithLOS(BotTeam, pBot->Edict->v.origin, UTIL_MetresToGoldSrcUnits(10.0f), pBot->Edict); if (CurrentHealthPercent < 0.3f || (CurrentHealthPercent < 0.5f && NumEnemyAllies > 0) || UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) < UTIL_GetPlayerPrimaryWeaponMaxClipSize(pBot->Player)) { return COMBAT_STRATEGY_RETREAT; } - if (PlayerHasWeapon(pBot->Player, WEAPON_MARINE_GL)) + // Shotty users should attack, can't really skirmish with a shotgun + if (PlayerHasWeapon(pBot->Player, WEAPON_MARINE_SHOTGUN) && (UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0 || UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0)) + { + return COMBAT_STRATEGY_ATTACK; + } + + bool bIsEnemyRanged = IsPlayerMarine(CurrentEnemy->EnemyPlayer); + + if (bIsEnemyRanged || PlayerHasWeapon(pBot->Player, WEAPON_MARINE_GL)) { return COMBAT_STRATEGY_SKIRMISH; } - if (!PlayerHasHeavyArmour(pBot->Edict) && (CurrentEnemy->EnemyPlayer->GetUser3() > AVH_USER3_ALIEN_PLAYER3 || NumEnemyAllies > 0)) + // If we're up against a stronger enemy than us, skirmish instead to avoid getting wiped out + if (!PlayerHasHeavyArmour(pBot->Edict) && (CurrentEnemy->EnemyPlayer->GetUser3() > AVH_USER3_ALIEN_PLAYER3 || NumEnemyAllies > 0 || PlayerHasHeavyArmour(EnemyEdict))) { return COMBAT_STRATEGY_SKIRMISH; } @@ -2556,6 +2606,16 @@ bool RegularMarineCombatThink(AvHAIPlayer* pBot) AvHAIWeapon DesiredCombatWeapon = BotMarineChooseBestWeapon(pBot, CurrentEnemy); + bool bBotIsGrenadier = (DesiredCombatWeapon == WEAPON_MARINE_GL); + + float DistToEnemy = vDist2DSq(pBot->Edict->v.origin, CurrentEnemy->v.origin); + + bool bEnemyIsRanged = IsPlayerMarine(TrackedEnemyRef->EnemyPlayer) || ((GetPlayerCurrentWeapon(TrackedEnemyRef->EnemyPlayer) == WEAPON_FADE_ACIDROCKET) && DistToEnemy > sqrf(UTIL_MetresToGoldSrcUnits(5.0f))); + + float LastEnemySeenTime = (TrackedEnemyRef->LastTrackedTime > 0.0f) ? TrackedEnemyRef->LastTrackedTime : TrackedEnemyRef->LastSeenTime; + Vector LastEnemySeenLocation = TrackedEnemyRef->LastSeenLocation; + + // Run away and restock if (pBot->CurrentCombatStrategy == COMBAT_STRATEGY_RETREAT) { if (NearestHealthPack && (pBot->Edict->v.health < pBot->Edict->v.max_health * 0.7f)) @@ -2585,7 +2645,7 @@ bool RegularMarineCombatThink(AvHAIPlayer* pBot) if (IsPlayerInUseRange(pBot->Edict, NearestArmouryRef->edict)) { BotUseObject(pBot, NearestArmouryRef->edict, true); - return; + return true; } } @@ -2602,20 +2662,263 @@ bool RegularMarineCombatThink(AvHAIPlayer* pBot) { BotAttackResult LOSCheck = PerformAttackLOSCheck(pBot, DesiredCombatWeapon, CurrentEnemy); + if (DesiredCombatWeapon != WEAPON_MARINE_KNIFE) + { + if (DistToEnemy < sqrf(100.0f)) + { + if (IsPlayerReloading(pBot->Player) && CanInterruptWeaponReload(GetPlayerCurrentWeapon(pBot->Player)) && GetPlayerCurrentWeaponClipAmmo(pBot->Player) > 0) + { + InterruptReload(pBot); + } + BotJump(pBot); + } + } + if (LOSCheck == ATTACK_SUCCESS) { - BotShootTarget(pBot, DesiredCombatWeapon, CurrentEnemy); - return true; + if (!bBotIsGrenadier || DistToEnemy > sqrf(BALANCE_VAR(kGrenadeRadius))) + { + BotShootTarget(pBot, DesiredCombatWeapon, CurrentEnemy); + return true; + } } } - if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) == 0 && vDist2DSq(pBot->Edict->v.origin, CurrentEnemy->v.origin) > sqrf(UTIL_MetresToGoldSrcUnits(3.0f))) + if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) == 0 && DistToEnemy > sqrf(UTIL_MetresToGoldSrcUnits(3.0f))) { - BotReloadWeapons(pBot); + BotAttackResult LOSCheck = PerformAttackLOSCheck(pBot, DesiredCombatWeapon, CurrentEnemy); + + if (LOSCheck == ATTACK_SUCCESS) + { + BotShootTarget(pBot, DesiredCombatWeapon, CurrentEnemy); + } + else + { + BotReloadWeapons(pBot); + } } return true; } + + // Maintain distance, pop and shoot + if (pBot->CurrentCombatStrategy == COMBAT_STRATEGY_SKIRMISH || pBot->CurrentCombatStrategy == COMBAT_STRATEGY_AMBUSH) + { + if (vIsZero(pBot->LastSafeLocation)) + { + pBot->LastSafeLocation = AITAC_GetTeamStartingLocation(BotTeam); + } + + if (TrackedEnemyRef->bHasLOS) + { + if (GetPlayerCurrentWeaponClipAmmo(pBot->Player) == 0) + { + MoveTo(pBot, pBot->LastSafeLocation, MOVESTYLE_NORMAL); + BotReloadWeapons(pBot); + return true; + } + + if (vDist2DSq(pBot->Edict->v.origin, pBot->LastSafeLocation) > sqrf(UTIL_MetresToGoldSrcUnits(3.0f))) + { + MoveTo(pBot, pBot->LastSafeLocation, MOVESTYLE_NORMAL); + } + else + { + if (PlayerHasWeapon(pBot->Player, WEAPON_MARINE_GRENADE) && DesiredCombatWeapon != WEAPON_MARINE_GL) + { + // Plus 1 to include the target themselves + int NumTargets = AITAC_GetNumPlayersOnTeamWithLOS(EnemyTeam, CurrentEnemy->v.origin, BALANCE_VAR(kGrenadeRadius), nullptr); + + if (NumTargets > 1) + { + BotThrowGrenadeAtTarget(pBot, CurrentEnemy->v.origin); + return true; + } + } + + if (bEnemyIsRanged) + { + 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(RightDir) : UTIL_GetVectorNormal2D(-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); + } + else + { + if (DesiredCombatWeapon != WEAPON_MARINE_KNIFE) + { + if (DistToEnemy < sqrf(100.0f)) + { + if (IsPlayerReloading(pBot->Player) && CanInterruptWeaponReload(GetPlayerCurrentWeapon(pBot->Player)) && GetPlayerCurrentWeaponClipAmmo(pBot->Player) > 0) + { + InterruptReload(pBot); + } + BotJump(pBot); + } + } + } + } + + BotShootTarget(pBot, DesiredCombatWeapon, CurrentEnemy); + } + else + { + if (PlayerHasWeapon(pBot->Player, WEAPON_MARINE_GRENADE) || (PlayerHasWeapon(pBot->Player, WEAPON_MARINE_GL) && UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0)) + { + Vector GrenadeTarget = UTIL_GetGrenadeThrowTarget(pBot->Edict, LastEnemySeenLocation, BALANCE_VAR(kGrenadeRadius), true); + + if (!vIsZero(GrenadeTarget)) + { + BotThrowGrenadeAtTarget(pBot, GrenadeTarget); + return true; + } + } + + if (BotReloadWeapons(pBot)) { return true; } + + MoveTo(pBot, LastEnemySeenLocation, MOVESTYLE_NORMAL); + } + + return true; + } + + // Go for the kill. Maintain desired distance and pursue when needed + if (pBot->CurrentCombatStrategy == COMBAT_STRATEGY_ATTACK) + { + AvHAIWeapon IdealAttackWeapon = (UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0 || UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0) ? UTIL_GetPlayerPrimaryWeapon(pBot->Player) : DesiredCombatWeapon; + + float DesiredDistance = GetMinIdealWeaponRange(IdealAttackWeapon) + ((GetMaxIdealWeaponRange(IdealAttackWeapon) - GetMinIdealWeaponRange(IdealAttackWeapon)) * 0.5f); + + bool bCanReloadCurrentWeapon = (WeaponCanBeReloaded(DesiredCombatWeapon) && GetPlayerCurrentWeaponClipAmmo(pBot->Player) < GetPlayerCurrentWeaponMaxClipAmmo(pBot->Player) && GetPlayerCurrentWeaponReserveAmmo(pBot->Player) > 0); + bool bMustReloadCurrentWeapon = bCanReloadCurrentWeapon && GetPlayerCurrentWeaponClipAmmo(pBot->Player) == 0; + + if (vIsZero(pBot->LastSafeLocation)) + { + pBot->LastSafeLocation = AITAC_GetTeamStartingLocation(BotTeam); + } + + if (!TrackedEnemyRef->bHasLOS) + { + if (PlayerHasWeapon(pBot->Player, WEAPON_MARINE_GRENADE) || (PlayerHasWeapon(pBot->Player, WEAPON_MARINE_GL) && UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0)) + { + Vector GrenadeTarget = UTIL_GetGrenadeThrowTarget(pBot->Edict, LastEnemySeenLocation, BALANCE_VAR(kGrenadeRadius), true); + + if (!vIsZero(GrenadeTarget)) + { + BotThrowGrenadeAtTarget(pBot, GrenadeTarget); + return true; + } + } + + if ((IdealAttackWeapon != DesiredCombatWeapon || bCanReloadCurrentWeapon) && gpGlobals->time - TrackedEnemyRef->LastSeenTime > 3.0f) + { + BotReloadWeapons(pBot); + if (vDist2DSq(pBot->Edict->v.origin, TrackedEnemyRef->LastVisibleLocation) < sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) + { + MoveTo(pBot, AITAC_GetTeamStartingLocation(BotTeam), MOVESTYLE_NORMAL); + } + return true; + } + + MoveTo(pBot, TrackedEnemyRef->LastSeenLocation, MOVESTYLE_NORMAL); + + return true; + } + + BotAttackResult LOSCheck = PerformAttackLOSCheck(pBot, DesiredCombatWeapon, CurrentEnemy); + + if (bMustReloadCurrentWeapon) + { + MoveTo(pBot, pBot->LastSafeLocation, MOVESTYLE_NORMAL); + BotReloadWeapons(pBot); + return true; + } + + if (DistToEnemy > sqrf(DesiredDistance)) + { + if (IdealAttackWeapon != DesiredCombatWeapon) + { + BotReloadWeapons(pBot); + MoveTo(pBot, pBot->LastSafeLocation, MOVESTYLE_NORMAL); + return true; + } + + MoveTo(pBot, LastEnemySeenLocation, MOVESTYLE_NORMAL); + + } + else + { + + if (bEnemyIsRanged) + { + 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(RightDir) : UTIL_GetVectorNormal2D(-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); + } + else + { + + float MinDesiredDist = GetMinIdealWeaponRange(DesiredCombatWeapon); + Vector Orientation = UTIL_GetVectorNormal2D(CurrentEnemy->v.origin - pBot->Edict->v.origin); + + float EnemyMoveDot = UTIL_GetDotProduct2D(UTIL_GetVectorNormal2D(CurrentEnemy->v.velocity), -Orientation); + + // Enemy is too close for comfort, or is moving towards us. Back up + if (DistToEnemy < MinDesiredDist || EnemyMoveDot > 0.7f) + { + Vector RetreatLocation = pBot->CurrentFloorPosition - (Orientation * 50.0f); + + if (UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, RetreatLocation)) + { + MoveDirectlyTo(pBot, RetreatLocation); + } + + if (DesiredCombatWeapon != WEAPON_MARINE_KNIFE) + { + if (DistToEnemy < sqrf(100.0f)) + { + if (IsPlayerReloading(pBot->Player) && CanInterruptWeaponReload(GetPlayerCurrentWeapon(pBot->Player)) && GetPlayerCurrentWeaponClipAmmo(pBot->Player) > 0) + { + InterruptReload(pBot); + return true; + } + BotJump(pBot); + } + } + + } + else + { + MoveTo(pBot, TrackedEnemyRef->LastSeenLocation, MOVESTYLE_NORMAL); + } + } + + BotShootTarget(pBot, DesiredCombatWeapon, CurrentEnemy); + } + } + + return false; } @@ -2631,136 +2934,10 @@ bool MarineCombatThink(AvHAIPlayer* pBot) pBot->LastCombatTime = gpGlobals->time; - if (PlayerHasWeapon(pBot->Player, WEAPON_MARINE_GL)) - { - return BombardierCombatThink(pBot); - } - else - { - return RegularMarineCombatThink(pBot); - } + return RegularMarineCombatThink(pBot); } return false; - - - edict_t* pEdict = pBot->Edict; - - edict_t* CurrentEnemy = pBot->TrackedEnemies[pBot->CurrentEnemy].EnemyEdict; - enemy_status* TrackedEnemyRef = &pBot->TrackedEnemies[pBot->CurrentEnemy]; - - pBot->LastCombatTime = gpGlobals->time; - - // ENEMY IS OUT OF SIGHT - - if (!TrackedEnemyRef->bHasLOS) - { - MarineHuntEnemy(pBot, TrackedEnemyRef); - return true; - } - - // ENEMY IS VISIBLE - - AvHAIWeapon DesiredCombatWeapon = BotMarineChooseBestWeapon(pBot, CurrentEnemy); - AvHAIWeapon PrimaryWeapon = UTIL_GetPlayerPrimaryWeapon(pBot->Player); - - BotAttackResult LOSCheck = PerformAttackLOSCheck(pBot, DesiredCombatWeapon, TrackedEnemyRef->LastSeenLocation, CurrentEnemy); - - if (LOSCheck == ATTACK_SUCCESS) - { - BotShootLocation(pBot, DesiredCombatWeapon, TrackedEnemyRef->LastSeenLocation); - } - - float DistFromEnemy = vDist2DSq(pBot->Edict->v.origin, CurrentEnemy->v.origin); - - if (DesiredCombatWeapon != WEAPON_MARINE_KNIFE) - { - if (DistFromEnemy < sqrf(100.0f)) - { - if (IsPlayerReloading(pBot->Player) && CanInterruptWeaponReload(GetPlayerCurrentWeapon(pBot->Player)) && GetPlayerCurrentWeaponClipAmmo(pBot->Player) > 0) - { - InterruptReload(pBot); - } - BotJump(pBot); - } - } - - // We're going to have the marine always try and use their primary weapon, which means - // that they will try and put enough distance between themselves and the enemy to use it effectively, - // and retreat if they need to reload or are out of ammo - - - // We are using our primary weapon right now (has ammo left in the clip) - if (DesiredCombatWeapon == PrimaryWeapon) - { - BotLookAt(pBot, CurrentEnemy); - if (LOSCheck == ATTACK_OUTOFRANGE) - { - MoveTo(pBot, TrackedEnemyRef->LastFloorPosition, MOVESTYLE_NORMAL); - if (gpGlobals->time - TrackedEnemyRef->LastSeenTime > 5.0f) - { - BotReloadWeapons(pBot); - } - return true; - } - - // Note that we already do visibility checks above, so blocked here means there is another player or structure in the way - if (LOSCheck == ATTACK_BLOCKED) - { - edict_t* TracedEntity = UTIL_TraceEntity(pEdict, pBot->CurrentEyePosition, UTIL_GetCentreOfEntity(CurrentEnemy)); - - // Just blast through an alien structure if it's in the way - if (!FNullEnt(TracedEntity) && TracedEntity != CurrentEnemy) - { - if (TracedEntity->v.team != 0 && TracedEntity->v.team != pEdict->v.team) - { - BotShootTarget(pBot, DesiredCombatWeapon, TracedEntity); - } - } - - float MinDesiredDist = GetMinIdealWeaponRange(DesiredCombatWeapon); - - Vector EngagementLocation = pBot->BotNavInfo.TargetDestination; - - float EngagementLocationDist = vDist2DSq(EngagementLocation, CurrentEnemy->v.origin); - - if (!EngagementLocation || EngagementLocationDist < sqrf(MinDesiredDist) || PerformAttackLOSCheck(EngagementLocation + Vector(0.0f, 0.0f, 50.0f), DesiredCombatWeapon, CurrentEnemy) != ATTACK_SUCCESS) - { - EngagementLocation = UTIL_GetRandomPointOnNavmeshInRadius(pBot->BotNavInfo.NavProfile, CurrentEnemy->v.origin, UTIL_MetresToGoldSrcUnits(5.0f)); - - if (EngagementLocation != ZERO_VECTOR && PerformAttackLOSCheck(EngagementLocation + Vector(0.0f, 0.0f, 50.0f), DesiredCombatWeapon, CurrentEnemy) != ATTACK_SUCCESS) - { - EngagementLocation = ZERO_VECTOR; - } - } - - MoveTo(pBot, EngagementLocation, MOVESTYLE_NORMAL); - return true; - } - - if (LOSCheck == ATTACK_SUCCESS) - { - float MinDesiredDist = GetMinIdealWeaponRange(DesiredCombatWeapon); - Vector Orientation = UTIL_GetVectorNormal2D(CurrentEnemy->v.origin - pBot->Edict->v.origin); - - float EnemyMoveDot = UTIL_GetDotProduct2D(UTIL_GetVectorNormal2D(CurrentEnemy->v.velocity), -Orientation); - - // Enemy is too close for comfort, or is moving towards us. Back up - if (DistFromEnemy < MinDesiredDist || EnemyMoveDot > 0.7f) - { - Vector RetreatLocation = pBot->CurrentFloorPosition - (Orientation * 50.0f); - - if (UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, RetreatLocation)) - { - MoveDirectlyTo(pBot, RetreatLocation); - } - } - } - - return true; - } - - return true; } void AIPlayerSetPrimaryMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) @@ -4703,23 +4880,23 @@ bool LerkCombatThink(AvHAIPlayer* pBot) if (pBot->CurrentCombatStrategy == COMBAT_STRATEGY_ATTACK) { - pBot->DesiredCombatWeapon = WEAPON_LERK_BITE; + AvHAIWeapon DesiredWeapon = WEAPON_LERK_BITE; if (vDist2DSq(pBot->Edict->v.origin, CurrentEnemy->v.origin) > sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) { if (!IsAreaAffectedBySpores(CurrentEnemy->v.origin)) { - pBot->DesiredCombatWeapon = WEAPON_LERK_SPORES; + DesiredWeapon = WEAPON_LERK_SPORES; } } MoveTo(pBot, CurrentEnemy->v.origin, MOVESTYLE_NORMAL); - BotAttackResult LOSCheck = PerformAttackLOSCheck(pBot, pBot->DesiredCombatWeapon, CurrentEnemy); + BotAttackResult LOSCheck = PerformAttackLOSCheck(pBot, DesiredWeapon, CurrentEnemy); if (LOSCheck == ATTACK_SUCCESS) { - BotShootTarget(pBot, pBot->DesiredCombatWeapon, CurrentEnemy); + BotShootTarget(pBot, DesiredWeapon, CurrentEnemy); } return true; diff --git a/main/source/mod/AIPlayers/AvHAIPlayer.h b/main/source/mod/AIPlayers/AvHAIPlayer.h index 9faa1301..b131c9d3 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayer.h +++ b/main/source/mod/AIPlayers/AvHAIPlayer.h @@ -20,7 +20,8 @@ bool CanBotLeap(AvHAIPlayer* pBot); void BotLeap(AvHAIPlayer* pBot, const Vector TargetLocation); float GetLeapCost(AvHAIPlayer* pBot); -void BotReloadWeapons(AvHAIPlayer* pBot); +// Returns true if the bot needs to reload +bool BotReloadWeapons(AvHAIPlayer* pBot); // Make the bot type something in either global or team chat void BotSay(AvHAIPlayer* pBot, bool bTeamSay, float Delay, char* textToSay); @@ -105,6 +106,8 @@ void UpdateAIPlayerDMRole(AvHAIPlayer* pBot); bool ShouldAIPlayerTakeCommand(AvHAIPlayer* pBot); +void AIPlayerTakeDamage(AvHAIPlayer* pBot, int damageTaken, edict_t* aggressor); + int BotGetNextEnemyTarget(AvHAIPlayer* pBot); AvHMessageID AlienGetDesiredUpgrade(AvHAIPlayer* pBot, HiveTechStatus DesiredTech); diff --git a/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp b/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp index 82d6182c..5b97eebb 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp +++ b/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp @@ -234,7 +234,7 @@ void AIMGR_UpdateFillTeams() } } - if (TeamSizeA < NumDesiredTeamA) + if (TeamSizeA < NumDesiredTeamA && TeamSizeA <= TeamSizeB) { AIMGR_AddAIPlayerToTeam(1); return; @@ -249,7 +249,7 @@ void AIMGR_UpdateFillTeams() } } - if (TeamSizeB < NumDesiredTeamB) + if (TeamSizeB < NumDesiredTeamB && TeamSizeB <= TeamSizeA) { AIMGR_AddAIPlayerToTeam(2); return; @@ -616,52 +616,21 @@ void AIMGR_UpdateAIPlayers() BotIt++; } - vector AlienPlayers = AIMGR_GetAIPlayersOnTeam(TEAM_TWO); - - int NumBuilders = 0; - int NumCappers = 0; - int NumHarassers = 0; - int NumAssault = 0; - - for (auto it = AlienPlayers.begin(); it != AlienPlayers.end(); it++) + if (!vIsZero(DebugVector1) && !vIsZero(DebugVector2)) { - AvHAIPlayer* NewCapper = (*it); + vector path; - if (NewCapper) + nav_profile NavProfile = GetBaseNavProfile(MARINE_BASE_NAV_PROFILE); + NavProfile.Filters.addIncludeFlags(SAMPLE_POLYFLAGS_WELD); + + dtStatus PathStatus = FindPathClosestToPoint(NavProfile, DebugVector1, DebugVector2, path, 100.0f); + + if (dtStatusSucceed(PathStatus)) { - switch (NewCapper->BotRole) - { - case BOT_ROLE_BUILDER: - NumBuilders++; - break; - case BOT_ROLE_FIND_RESOURCES: - NumCappers++; - break; - case BOT_ROLE_HARASS: - NumHarassers++; - break; - case BOT_ROLE_ASSAULT: - NumAssault++; - break; - default: - break; - } + AIDEBUG_DrawPath(path, 0.1f); } } - char buf[256]; - char interbuf[32]; - - sprintf(buf, "Builders: %d\n", NumBuilders); - sprintf(interbuf, "Cappers: %d\n", NumCappers); - strcat(buf, interbuf); - sprintf(interbuf, "Harassers: %d\n", NumHarassers); - strcat(buf, interbuf); - sprintf(interbuf, "Assault: %d\n", NumAssault); - strcat(buf, interbuf); - - UTIL_DrawHUDText(INDEXENT(1), 0, 0.1f, 0.1f, 255, 255, 255, buf); - PrevTime = CurrTime; } @@ -676,6 +645,16 @@ int AIMGR_GetNumAIPlayers() return ActiveAIPlayers.size(); } +AvHTeamNumber AIMGR_GetTeamANumber() +{ + return GetGameRules()->GetTeamANumber(); +} + +AvHTeamNumber AIMGR_GetTeamBNumber() +{ + return GetGameRules()->GetTeamANumber(); +} + vector AIMGR_GetAllPlayersOnTeam(AvHTeamNumber Team) { vector Result; @@ -684,7 +663,7 @@ vector AIMGR_GetAllPlayersOnTeam(AvHTeamNumber Team) { edict_t* PlayerEdict = INDEXENT(i); - if (!FNullEnt(PlayerEdict) && PlayerEdict->v.team == Team) + if (!FNullEnt(PlayerEdict) && (Team == TEAM_IND || PlayerEdict->v.team == Team)) { AvHPlayer* PlayerRef = dynamic_cast(CBaseEntity::Instance(PlayerEdict)); @@ -832,6 +811,8 @@ void AIMGR_RoundStarted() AITAC_RefreshHiveData(); + UTIL_UpdateDoors(true); + UTIL_UpdateTileCache(); } @@ -905,6 +886,16 @@ AvHAIPlayer* AIMGR_GetAICommander(AvHTeamNumber Team) return nullptr; } +AvHAIPlayer* AIMGR_GetBotRefFromPlayer(AvHPlayer* PlayerRef) +{ + for (auto BotIt = ActiveAIPlayers.begin(); BotIt != ActiveAIPlayers.end(); BotIt++) + { + if (BotIt->Player == PlayerRef) { return &(*BotIt); } + } + + return nullptr; +} + AvHTeamNumber AIMGR_GetEnemyTeam(const AvHTeamNumber FriendlyTeam) { AvHTeamNumber TeamANumber = GetGameRules()->GetTeamANumber(); diff --git a/main/source/mod/AIPlayers/AvHAIPlayerManager.h b/main/source/mod/AIPlayers/AvHAIPlayerManager.h index dd6c1950..6c7df2bf 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayerManager.h +++ b/main/source/mod/AIPlayers/AvHAIPlayerManager.h @@ -64,11 +64,13 @@ int AIMGR_GetNumHumansOfClassOnTeam(AvHTeamNumber Team, AvHUser3 PlayerType); AvHAIPlayer* AIMGR_GetAICommander(AvHTeamNumber Team); - +AvHAIPlayer* AIMGR_GetBotRefFromPlayer(AvHPlayer* PlayerRef); AvHTeamNumber AIMGR_GetEnemyTeam(const AvHTeamNumber FriendlyTeam); AvHClassType AIMGR_GetEnemyTeamType(const AvHTeamNumber FriendlyTeam); AvHClassType AIMGR_GetTeamType(const AvHTeamNumber Team); +AvHTeamNumber AIMGR_GetTeamANumber(); +AvHTeamNumber AIMGR_GetTeamBNumber(); // Returns all NS AI players. Does not include third-party bots vector AIMGR_GetAllAIPlayers(); diff --git a/main/source/mod/AIPlayers/AvHAITactical.cpp b/main/source/mod/AIPlayers/AvHAITactical.cpp index 9ef8dfd8..52220697 100644 --- a/main/source/mod/AIPlayers/AvHAITactical.cpp +++ b/main/source/mod/AIPlayers/AvHAITactical.cpp @@ -739,6 +739,8 @@ void AITAC_RefreshHiveData() it->FloorLocation = AITAC_GetFloorLocationForHive(&(*it)); it->NextFloorLocationCheck = gpGlobals->time + (5.0f + (0.1f * NextRefresh)); + + AITAC_RefreshReachabilityForHive(&(*it)); } NextRefresh++; @@ -991,6 +993,139 @@ void AITAC_RefreshAllResNodeReachability() } } +void AITAC_RefreshReachabilityForHive(AvHAIHiveDefinition* Hive) +{ + + if (!bTileCacheUpToDate) { return; } + + Hive->TeamAReachabilityFlags = AI_REACHABILITY_NONE; + Hive->TeamBReachabilityFlags = AI_REACHABILITY_NONE; + + Vector HiveLocation = Hive->FloorLocation; + + bool bOnNavMesh = UTIL_PointIsOnNavmesh(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), HiveLocation, Vector(max_player_use_reach, max_player_use_reach, max_player_use_reach)); + + if (!bOnNavMesh) + { + Hive->TeamAReachabilityFlags = AI_REACHABILITY_UNREACHABLE; + Hive->TeamBReachabilityFlags = AI_REACHABILITY_UNREACHABLE; + return; + } + + Vector TeamAStart = AITAC_GetTeamStartingLocation(GetGameRules()->GetTeamANumber()); + Vector TeamBStart = AITAC_GetTeamStartingLocation(GetGameRules()->GetTeamBNumber()); + + if (GetGameRules()->GetTeamA()->GetTeamType() == AVH_CLASS_TYPE_MARINE) + { + bool bIsReachableMarine = UTIL_PointIsReachable(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), TeamAStart, HiveLocation, max_player_use_reach); + + if (bIsReachableMarine) + { + Hive->TeamAReachabilityFlags |= AI_REACHABILITY_MARINE; + Hive->TeamAReachabilityFlags |= AI_REACHABILITY_WELDER; + } + else + { + nav_profile WelderProfile; + memcpy(&WelderProfile, &BaseNavProfiles[MARINE_BASE_NAV_PROFILE], sizeof(nav_profile)); + + WelderProfile.Filters.addIncludeFlags(SAMPLE_POLYFLAGS_WELD); + + bool bIsReachableWelder = UTIL_PointIsReachable(WelderProfile, TeamAStart, HiveLocation, max_player_use_reach); + + if (bIsReachableWelder) + { + Hive->TeamAReachabilityFlags |= AI_REACHABILITY_WELDER; + } + else + { + Hive->TeamAReachabilityFlags = AI_REACHABILITY_UNREACHABLE; + } + } + } + else + { + bool bIsReachableSkulk = UTIL_PointIsReachable(GetBaseNavProfile(SKULK_BASE_NAV_PROFILE), TeamAStart, HiveLocation, max_player_use_reach); + bool bIsReachableGorge = UTIL_PointIsReachable(GetBaseNavProfile(GORGE_BASE_NAV_PROFILE), TeamAStart, HiveLocation, max_player_use_reach); + bool bIsReachableOnos = UTIL_PointIsReachable(GetBaseNavProfile(ONOS_BASE_NAV_PROFILE), TeamAStart, HiveLocation, max_player_use_reach); + + if (bIsReachableSkulk) + { + Hive->TeamAReachabilityFlags |= AI_REACHABILITY_SKULK; + } + + if (bIsReachableGorge) + { + Hive->TeamAReachabilityFlags |= AI_REACHABILITY_GORGE; + } + + if (bIsReachableOnos) + { + Hive->TeamAReachabilityFlags |= AI_REACHABILITY_ONOS; + } + + if (Hive->TeamAReachabilityFlags == AI_REACHABILITY_NONE) + { + Hive->TeamAReachabilityFlags = AI_REACHABILITY_UNREACHABLE; + } + } + + if (GetGameRules()->GetTeamB()->GetTeamType() == AVH_CLASS_TYPE_MARINE) + { + bool bIsReachableMarine = UTIL_PointIsReachable(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), TeamBStart, HiveLocation, max_player_use_reach); + + if (bIsReachableMarine) + { + Hive->TeamBReachabilityFlags |= AI_REACHABILITY_MARINE; + Hive->TeamBReachabilityFlags |= AI_REACHABILITY_WELDER; + } + else + { + nav_profile WelderProfile; + memcpy(&WelderProfile, &BaseNavProfiles[MARINE_BASE_NAV_PROFILE], sizeof(nav_profile)); + + WelderProfile.Filters.addIncludeFlags(SAMPLE_POLYFLAGS_WELD); + + bool bIsReachableWelder = UTIL_PointIsReachable(WelderProfile, TeamBStart, HiveLocation, max_player_use_reach); + + if (bIsReachableWelder) + { + Hive->TeamBReachabilityFlags |= AI_REACHABILITY_WELDER; + } + else + { + Hive->TeamBReachabilityFlags = AI_REACHABILITY_UNREACHABLE; + } + } + } + else + { + bool bIsReachableSkulk = UTIL_PointIsReachable(GetBaseNavProfile(SKULK_BASE_NAV_PROFILE), TeamBStart, HiveLocation, max_player_use_reach); + bool bIsReachableGorge = UTIL_PointIsReachable(GetBaseNavProfile(GORGE_BASE_NAV_PROFILE), TeamBStart, HiveLocation, max_player_use_reach); + bool bIsReachableOnos = UTIL_PointIsReachable(GetBaseNavProfile(ONOS_BASE_NAV_PROFILE), TeamBStart, HiveLocation, max_player_use_reach); + + if (bIsReachableSkulk) + { + Hive->TeamBReachabilityFlags |= AI_REACHABILITY_SKULK; + } + + if (bIsReachableGorge) + { + Hive->TeamBReachabilityFlags |= AI_REACHABILITY_GORGE; + } + + if (bIsReachableOnos) + { + Hive->TeamBReachabilityFlags |= AI_REACHABILITY_ONOS; + } + + if (Hive->TeamBReachabilityFlags == AI_REACHABILITY_NONE) + { + Hive->TeamBReachabilityFlags = AI_REACHABILITY_UNREACHABLE; + } + } +} + void AITAC_RefreshReachabilityForResNode(AvHAIResourceNode* ResNode) { if (Hives.size() == 0) @@ -1129,7 +1264,6 @@ void AITAC_RefreshReachabilityForResNode(AvHAIResourceNode* ResNode) ResNode->TeamBReachabilityFlags = AI_REACHABILITY_UNREACHABLE; } } - } void AITAC_PopulateResourceNodes() @@ -2951,6 +3085,27 @@ bool UTIL_DroppedItemIsPrimaryWeapon(const AvHAIDeployableItemType ItemType) return false; } +AvHAIWeapon UTIL_GetWeaponTypeFromDroppedItem(const AvHAIDeployableItemType ItemType) +{ + switch (ItemType) + { + case DEPLOYABLE_ITEM_GRENADELAUNCHER: + return WEAPON_MARINE_GL; + case DEPLOYABLE_ITEM_HMG: + return WEAPON_MARINE_HMG; + case DEPLOYABLE_ITEM_SHOTGUN: + return WEAPON_MARINE_SHOTGUN; + case DEPLOYABLE_ITEM_WELDER: + return WEAPON_MARINE_WELDER; + case DEPLOYABLE_ITEM_MINES: + return WEAPON_MARINE_MINES; + default: + return WEAPON_INVALID; + } + + return WEAPON_INVALID; +} + Vector UTIL_GetNextMinePosition(edict_t* StructureToMine) { if (FNullEnt(StructureToMine)) { return ZERO_VECTOR; } @@ -4226,4 +4381,50 @@ bool AITAC_IsAlienUpgradeAvailableForTeam(AvHTeamNumber Team, HiveTechStatus Des ChamberFilter.DeployableTypes = SearchType; return (AITAC_DeployableExistsAtLocation(ZERO_VECTOR, &ChamberFilter)); +} + +int AITAC_GetNumWeaponsInPlay(AvHTeamNumber Team, AvHAIWeapon WeaponType) +{ + int Result = 0; + + vector PlayerList = AIMGR_GetAllPlayersOnTeam(Team); + + for (auto it = PlayerList.begin(); it != PlayerList.end(); it++) + { + AvHPlayer* PlayerRef = (*it); + + if (!PlayerRef) { continue; } + + edict_t* PlayerEdict = PlayerRef->edict(); + + if (PlayerRef && !FNullEnt(PlayerEdict) && IsPlayerActiveInGame(PlayerEdict) && PlayerHasWeapon(PlayerRef, WeaponType)) + { + Result++; + } + } + + + for (auto it = MarineDroppedItemMap.begin(); it != MarineDroppedItemMap.end(); it++) + { + AvHAIWeapon ThisWeaponType = UTIL_GetWeaponTypeFromDroppedItem(it->second.ItemType); + + if (ThisWeaponType != WeaponType) { continue; } + + unsigned int ReachabilityFlags = (Team == TEAM_IND) ? (it->second.TeamAReachabilityFlags | it->second.TeamBReachabilityFlags) : ((Team == GetGameRules()->GetTeamANumber()) ? it->second.TeamAReachabilityFlags : it->second.TeamBReachabilityFlags); + + if (ReachabilityFlags != AI_REACHABILITY_UNREACHABLE) + { + DeployableSearchFilter ArmouryFilter; + ArmouryFilter.DeployableTypes = (STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY); + ArmouryFilter.DeployableTeam = Team; + ArmouryFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f); + + if (AITAC_DeployableExistsAtLocation(it->second.Location, &ArmouryFilter)) + { + Result++; + } + } + } + + return Result; } \ No newline at end of file diff --git a/main/source/mod/AIPlayers/AvHAITactical.h b/main/source/mod/AIPlayers/AvHAITactical.h index bd3f9363..808ecf3c 100644 --- a/main/source/mod/AIPlayers/AvHAITactical.h +++ b/main/source/mod/AIPlayers/AvHAITactical.h @@ -37,6 +37,7 @@ void AITAC_RefreshBuildableStructures(); AvHAIBuildableStructure* AITAC_UpdateBuildableStructure(CBaseEntity* Structure); void AITAC_RefreshReachabilityForStructure(AvHAIBuildableStructure* Structure); void AITAC_RefreshReachabilityForResNode(AvHAIResourceNode* ResNode); +void AITAC_RefreshReachabilityForHive(AvHAIHiveDefinition* Hive); void AITAC_RefreshAllResNodeReachability(); void AITAC_RefreshReachabilityForItem(AvHAIDroppedItem* Item); void AITAC_OnStructureCreated(AvHAIBuildableStructure* NewStructure); @@ -131,6 +132,8 @@ bool AITAC_TeamHiveWithTechExists(const AvHTeamNumber Team, const AvHMessageID T AvHAIDeployableItemType UTIL_GetItemTypeFromEdict(const edict_t* ItemEdict); bool UTIL_DroppedItemIsPrimaryWeapon(const AvHAIDeployableItemType ItemType); +AvHAIWeapon UTIL_GetWeaponTypeFromDroppedItem(const AvHAIDeployableItemType ItemType); + bool UTIL_StructureIsResearching(edict_t* Structure); bool UTIL_StructureIsResearching(edict_t* Structure, const AvHMessageID Research); bool UTIL_StructureIsUpgrading(edict_t* Structure); @@ -177,4 +180,6 @@ edict_t* AITAC_AlienFindNearestHealingSource(AvHTeamNumber Team, Vector SearchLo bool AITAC_IsAlienUpgradeAvailableForTeam(AvHTeamNumber Team, HiveTechStatus DesiredTech); +int AITAC_GetNumWeaponsInPlay(AvHTeamNumber Team, AvHAIWeapon WeaponType); + #endif \ No newline at end of file diff --git a/main/source/mod/AIPlayers/AvHAIWeaponHelper.cpp b/main/source/mod/AIPlayers/AvHAIWeaponHelper.cpp index 6125be74..bd63b909 100644 --- a/main/source/mod/AIPlayers/AvHAIWeaponHelper.cpp +++ b/main/source/mod/AIPlayers/AvHAIWeaponHelper.cpp @@ -397,19 +397,19 @@ float GetMaxIdealWeaponRange(const AvHAIWeapon Weapon) case WEAPON_SKULK_XENOCIDE: return (float)BALANCE_VAR(kDivineWindRadius) * 0.8f; case WEAPON_ONOS_GORE: - return (float)BALANCE_VAR(kClawsRange); + return (float)BALANCE_VAR(kClawsRange) + 20.0f; case WEAPON_ONOS_DEVOUR: return (float)BALANCE_VAR(kDevourRange); case WEAPON_FADE_SWIPE: - return (float)BALANCE_VAR(kSwipeRange); + return (float)BALANCE_VAR(kSwipeRange) + 30.0f; case WEAPON_SKULK_BITE: - return (float)BALANCE_VAR(kBiteRange); + return (float)BALANCE_VAR(kBiteRange) + 20.0f; case WEAPON_LERK_BITE: - return (float)BALANCE_VAR(kBite2Range); + return (float)BALANCE_VAR(kBite2Range) + 20.0f; case WEAPON_GORGE_HEALINGSPRAY: return (float)BALANCE_VAR(kHealingSprayRange) * 0.5f; case WEAPON_MARINE_WELDER: - return (float)BALANCE_VAR(kWelderRange); + return (float)BALANCE_VAR(kWelderRange) + 10.0f; default: return max_player_use_reach; } diff --git a/main/source/mod/AvHPlayer.cpp b/main/source/mod/AvHPlayer.cpp index 6beaf259..49aaed2c 100644 --- a/main/source/mod/AvHPlayer.cpp +++ b/main/source/mod/AvHPlayer.cpp @@ -255,6 +255,8 @@ #include "AvHNetworkMessages.h" #include "AvHNexusServer.h" +#include "AIPlayers/AvHAIPlayerManager.h" + std::string GetLogStringForPlayer( edict_t *pEntity ); extern int gJetpackEventID; @@ -9162,6 +9164,17 @@ int AvHPlayer::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, floa if(pevAttacker) { CBasePlayer* inAttackingPlayer = dynamic_cast(CBaseEntity::Instance(ENT(pevAttacker))); + + if (inAttackingPlayer) + { + AvHAIPlayer* VictimBot = AIMGR_GetBotRefFromPlayer(this); + + if (VictimBot) + { + AIPlayerTakeDamage(VictimBot, flDamage, inAttackingPlayer->edict()); + } + } + const char* inWeaponName = STRING(pevInflictor->classname); if(inAttackingPlayer && inWeaponName) {