Improve bot vision, bug fixes

* Re-added the view frustum for bots
* Hopefully stopped bots getting permanently stuck trying to evolve an upgrade in an invalid place
* Bots will now leave the game after 2 hours if no humans playing (ignores human spectators)
This commit is contained in:
RGreenlees 2024-04-05 15:53:19 +01:00 committed by pierow
parent 75469430ad
commit eba41379e3
10 changed files with 375 additions and 85 deletions

View file

@ -8,6 +8,7 @@
#include "AvHHive.h"
#include "AvHEntities.h"
#include "AvHAIMath.h"
static const float commander_action_cooldown = 1.0f;
static const float min_request_spam_time = 10.0f;
@ -740,6 +741,7 @@ typedef struct AVH_AI_PLAYER
AvHAIWeapon DesiredMoveWeapon = WEAPON_INVALID;
AvHAIWeapon DesiredCombatWeapon = WEAPON_INVALID;
frustum_plane_t viewFrustum[6]; // Bot's view frustum. Essentially, their "screen" for determining visibility of stuff
enemy_status TrackedEnemies[32];
int CurrentEnemy = -1;
AvHAICombatStrategy CurrentCombatStrategy = COMBAT_STRATEGY_ATTACK;

View file

@ -810,4 +810,47 @@ float UTIL_CalculateSlopeAngleBetweenPoints(const Vector StartPoint, const Vecto
float Rise = fabsf(StartPoint.z - EndPoint.z);
return atanf(Rise / Run);
}
// Function to check if a finite line intersects with an AABB
bool vlineIntersectsAABB(Vector lineStart, Vector lineEnd, Vector BoxMinPosition, Vector BoxMaxPosition)
{
Vector RayDir = UTIL_GetVectorNormal(lineEnd - lineStart);
float LineLength = vDist3D(lineStart, lineEnd);
Vector dirfrac;
float t = FLT_MAX;
// r.dir is unit direction vector of ray
dirfrac.x = 1.0f / RayDir.x;
dirfrac.y = 1.0f / RayDir.y;
dirfrac.z = 1.0f / RayDir.z;
// lb is the corner of AABB with minimal coordinates - left bottom, rt is maximal corner
// r.org is origin of ray
float t1 = (BoxMinPosition.x - lineStart.x) * dirfrac.x;
float t2 = (BoxMaxPosition.x - lineStart.x) * dirfrac.x;
float t3 = (BoxMinPosition.y - lineStart.y) * dirfrac.y;
float t4 = (BoxMaxPosition.y - lineStart.y) * dirfrac.y;
float t5 = (BoxMinPosition.z - lineStart.z) * dirfrac.z;
float t6 = (BoxMaxPosition.z - lineStart.z) * dirfrac.z;
float tmin = max(max(min(t1, t2), min(t3, t4)), min(t5, t6));
float tmax = min(min(max(t1, t2), max(t3, t4)), max(t5, t6));
// if tmax < 0, ray (line) is intersecting AABB, but the whole AABB is behind us
if (tmax < 0)
{
t = tmax;
return false;
}
// if tmin > tmax, ray doesn't intersect AABB
if (tmin > tmax)
{
t = tmax;
return false;
}
t = tmin;
return t <= LineLength;
}

View file

@ -205,4 +205,6 @@ unsigned int UTIL_CountSetBitsInInteger(unsigned int n);
float UTIL_CalculateSlopeAngleBetweenPoints(const Vector StartPoint, const Vector EndPoint);
bool vlineIntersectsAABB(Vector lineStart, Vector lineEnd, Vector BoxMinPosition, Vector BoxMaxPosition);
#endif

View file

@ -2404,7 +2404,7 @@ bool HasBotCompletedJumpMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector M
bool HasBotCompletedPhaseGateMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
{
return vPointOverlaps3D(MoveEnd, pBot->Edict->v.absmin, pBot->Edict->v.absmax);
return vPointOverlaps3D(MoveEnd, pBot->Edict->v.absmin, pBot->Edict->v.absmax) || vDist2DSq(pBot->Edict->v.origin, MoveEnd) < sqrf(32.0f);
}
bool HasBotCompletedLiftMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
@ -4345,6 +4345,13 @@ bool IsBotOffWalkNode(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd,
if (vDist2DSq(pBot->Edict->v.origin, NearestPointOnLine) > sqrf(GetPlayerRadius(pBot->Edict) * 3.0f)) { return true; }
if (!FNullEnt(pBot->Edict->v.groundentity))
{
nav_door* Door = UTIL_GetNavDoorByEdict(pBot->Edict->v.groundentity);
if (Door) { return false; }
}
if (vEquals2D(NearestPointOnLine, MoveStart) && !UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, MoveStart)) { return true; }
if (vEquals2D(NearestPointOnLine, MoveEnd) && !UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, MoveEnd)) { return true; }
@ -4358,6 +4365,15 @@ bool IsBotOffFallNode(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd,
Vector NearestPointOnLine = vClosestPointOnLine2D(MoveStart, MoveEnd, pBot->Edict->v.origin);
if (vDist2DSq(pBot->Edict->v.origin, NearestPointOnLine) > sqrf(GetPlayerRadius(pBot->Edict) * 3.0f)) { return true; }
if (!FNullEnt(pBot->Edict->v.groundentity))
{
nav_door* Door = UTIL_GetNavDoorByEdict(pBot->Edict->v.groundentity);
if (Door) { return false; }
}
if (!UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, MoveStart) && !UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, MoveEnd)) { return true; }
return false;
@ -4391,7 +4407,7 @@ bool IsBotOffJumpNode(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd,
bool IsBotOffPhaseGateNode(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
{
if (vDist2DSq(pBot->Edict->v.origin, MoveStart) > sqrf(UTIL_MetresToGoldSrcUnits(3.0f)) && vDist2DSq(pBot->Edict->v.origin, MoveEnd) > sqrf(UTIL_MetresToGoldSrcUnits(3.0f))) { return true; }
if (vDist2DSq(pBot->Edict->v.origin, MoveStart) > sqrf(UTIL_MetresToGoldSrcUnits(2.0f)) && vDist2DSq(pBot->Edict->v.origin, MoveEnd) > sqrf(UTIL_MetresToGoldSrcUnits(2.0f))) { return true; }
DeployableSearchFilter PGFilter;
PGFilter.DeployableTeam = pBot->Player->GetTeam();
@ -6579,6 +6595,7 @@ void BotFollowPath(AvHAIPlayer* pBot)
if (IsBotOffPath(pBot))
{
MoveToWithoutNav(pBot, CurrentNode.Location);
pBot->BotNavInfo.StuckInfo.bPathFollowFailed = true;
ClearBotPath(pBot);
return;
@ -7574,15 +7591,13 @@ void UTIL_PopulateAffectedConnectionsForDoor(nav_door* Door)
{
Vector DoorCentre = (*stopIt);
Vector NearestPointOnLine = vClosestPointOnLine(ConnStart, MidPoint, DoorCentre);
if (vPointOverlaps3D(NearestPointOnLine, DoorCentre - HalfExtents, DoorCentre + HalfExtents))
if (vlineIntersectsAABB(ConnStart, MidPoint, DoorCentre - HalfExtents, DoorCentre + HalfExtents))
{
Door->AffectedConnections.push_back(&(*it));
break;
}
NearestPointOnLine = vClosestPointOnLine(MidPoint, ConnEnd, DoorCentre);
if (vPointOverlaps3D(NearestPointOnLine, DoorCentre - HalfExtents, DoorCentre + HalfExtents))
if (vlineIntersectsAABB(MidPoint, ConnEnd, DoorCentre - HalfExtents, DoorCentre + HalfExtents))
{
Door->AffectedConnections.push_back(&(*it));
break;
@ -8752,7 +8767,6 @@ void NAV_ProgressMovementTask(AvHAIPlayer* pBot)
AimLocation.z = EntityCentre.z;
}
UTIL_DrawLine(INDEXENT(1), pBot->CurrentEyePosition, AimLocation);
}
BotMoveLookAt(pBot, AimLocation);
@ -8771,7 +8785,8 @@ void NAV_ProgressMovementTask(AvHAIPlayer* pBot)
}
}
MoveTo(pBot, MoveTask->TaskLocation, MOVESTYLE_NORMAL);
bool bSuccess = MoveTo(pBot, MoveTask->TaskLocation, MOVESTYLE_NORMAL);
}

View file

@ -197,6 +197,14 @@ bool BotUseObject(AvHAIPlayer* pBot, edict_t* Target, bool bContinuous)
{
pBot->Button |= IN_USE;
pBot->LastUseTime = gpGlobals->time;
CBaseEntity* UsedObject = CBaseEntity::Instance(Target);
if (UsedObject)
{
UsedObject->Use(pBot->Player, pBot->Player, USE_TOGGLE, 0);
}
return true;
}
@ -1323,6 +1331,8 @@ void BotUpdateView(AvHAIPlayer* pBot)
pBot->ViewForwardVector = UTIL_GetForwardVector(pBot->Edict->v.v_angle);
UpdateAIPlayerViewFrustum(pBot);
// Update list of currently visible players
for (int i = 1; i <= gpGlobals->maxClients; i++)
{
@ -1534,13 +1544,55 @@ void BotClearEnemyTrackingInfo(enemy_status* TrackingInfo)
TrackingInfo->LastHiddenPosition = ZERO_VECTOR;
}
void UpdateAIPlayerViewFrustum(AvHAIPlayer* pBot)
{
MAKE_VECTORS(pBot->Edict->v.v_angle);
Vector up = gpGlobals->v_up;
Vector forward = gpGlobals->v_forward;
Vector right = gpGlobals->v_right;
Vector fc = (pBot->Edict->v.origin + pBot->Edict->v.view_ofs) + (forward * BOT_MAX_VIEW);
Vector fbl = fc + (up * f_ffheight / 2.0f) - (right * f_ffwidth / 2.0f);
Vector fbr = fc + (up * f_ffheight / 2.0f) + (right * f_ffwidth / 2.0f);
Vector ftl = fc - (up * f_ffheight / 2.0f) - (right * f_ffwidth / 2.0f);
Vector ftr = fc - (up * f_ffheight / 2.0f) + (right * f_ffwidth / 2.0f);
Vector nc = (pBot->Edict->v.origin + pBot->Edict->v.view_ofs) + (forward * BOT_MIN_VIEW);
Vector nbl = nc + (up * f_fnheight / 2.0f) - (right * f_fnwidth / 2.0f);
Vector nbr = nc + (up * f_fnheight / 2.0f) + (right * f_fnwidth / 2.0f);
Vector ntl = nc - (up * f_fnheight / 2.0f) - (right * f_fnwidth / 2.0f);
Vector ntr = nc - (up * f_fnheight / 2.0f) + (right * f_fnwidth / 2.0f);
UTIL_SetFrustumPlane(&pBot->viewFrustum[FRUSTUM_PLANE_TOP], ftl, ntl, ntr);
UTIL_SetFrustumPlane(&pBot->viewFrustum[FRUSTUM_PLANE_BOTTOM], fbr, nbr, nbl);
UTIL_SetFrustumPlane(&pBot->viewFrustum[FRUSTUM_PLANE_LEFT], fbl, nbl, ntl);
UTIL_SetFrustumPlane(&pBot->viewFrustum[FRUSTUM_PLANE_RIGHT], ftr, ntr, nbr);
UTIL_SetFrustumPlane(&pBot->viewFrustum[FRUSTUM_PLANE_NEAR], nbr, ntr, ntl);
UTIL_SetFrustumPlane(&pBot->viewFrustum[FRUSTUM_PLANE_FAR], fbl, ftl, ftr);
}
bool IsPlayerInBotFOV(AvHAIPlayer* Observer, edict_t* TargetPlayer)
{
Vector TargetVector = (TargetPlayer->v.origin - Observer->CurrentEyePosition).Normalize();
/*Vector TargetVector = (TargetPlayer->v.origin - Observer->CurrentEyePosition).Normalize();
float DotProduct = UTIL_GetDotProduct(Observer->ViewForwardVector, TargetVector);
return DotProduct > 0.65f;
return DotProduct > 0.65f;*/
if (FNullEnt(TargetPlayer) || !IsPlayerActiveInGame(TargetPlayer)) { return false; }
// To make things a little more accurate, we're going to treat players as cylinders rather than boxes
for (int i = 0; i < 6; i++)
{
// Our cylinder must be inside all planes to be visible, otherwise return false
if (!UTIL_CylinderInsidePlane(&Observer->viewFrustum[i], TargetPlayer->v.origin - Vector(0, 0, 5), 60.0f, 16.0f))
{
return false;
}
}
return true;
}
@ -1742,7 +1794,7 @@ void StartNewBotFrame(AvHAIPlayer* pBot)
else
{
// All other structures should appear near-instantly
if ((gpGlobals->time - pBot->PrimaryBotTask.ActiveBuildInfo.BuildAttemptTime) > 0.5f)
if ((gpGlobals->time - pBot->PrimaryBotTask.ActiveBuildInfo.BuildAttemptTime) > 1.0f)
{
pBot->PrimaryBotTask.ActiveBuildInfo.BuildStatus = BUILD_ATTEMPT_FAILED;
}
@ -1765,7 +1817,7 @@ void StartNewBotFrame(AvHAIPlayer* pBot)
else
{
// All other structures should appear near-instantly
if ((gpGlobals->time - pBot->SecondaryBotTask.ActiveBuildInfo.BuildAttemptTime) > 0.5f)
if ((gpGlobals->time - pBot->SecondaryBotTask.ActiveBuildInfo.BuildAttemptTime) > 1.0f)
{
pBot->SecondaryBotTask.ActiveBuildInfo.BuildStatus = BUILD_ATTEMPT_FAILED;
}
@ -3201,6 +3253,7 @@ bool MarineCombatThink(AvHAIPlayer* pBot)
void AIPlayerSetPrimaryMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
{
switch (pBot->BotRole)
{
case BOT_ROLE_SWEEPER:
@ -3890,6 +3943,15 @@ void AIPlayerSetSecondaryMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
}
}
// If we're engaging an enemy turret then finish the job
if (Task->TaskType == TASK_ATTACK && (GetStructureTypeFromEdict(Task->TaskTarget) & (STRUCTURE_ALIEN_OFFENCECHAMBER | STRUCTURE_MARINE_TURRET | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_TURRETFACTORY)))
{
if (vDist2DSq(pBot->Edict->v.origin, Task->TaskTarget->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(20.0f)))
{
return;
}
}
// Find any nearby unbuilt structures
DeployableSearchFilter UnbuiltFilter;
UnbuiltFilter.DeployableTypes = STRUCTURE_MARINE_INFANTRYPORTAL;
@ -4071,6 +4133,35 @@ void AIPlayerSetSecondaryMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
}
}
// If we're engaging an enemy turret then finish the job
if (Task->TaskType == TASK_ATTACK)
{
if (vDist2DSq(pBot->Edict->v.origin, Task->TaskTarget->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(20.0f)))
{
return;
}
}
DeployableSearchFilter EnemyStructures;
EnemyStructures.DeployableTypes = SEARCH_ALL_STRUCTURES;
EnemyStructures.DeployableTeam = BotTeam;
EnemyStructures.ReachabilityTeam = BotTeam;
EnemyStructures.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
EnemyStructures.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f);
vector<AvHAIBuildableStructure> NearbyEnemyStructures = AITAC_FindAllDeployables(pBot->Edict->v.origin, &EnemyStructures);
for (auto it = NearbyEnemyStructures.begin(); it != NearbyEnemyStructures.end(); it++)
{
if (UTIL_PlayerHasLOSToEntity(pBot->Edict, it->edict, UTIL_MetresToGoldSrcUnits(20.0f), false))
{
AITASK_SetAttackTask(pBot, Task, it->edict, false);
return;
}
}
}
bool AIPlayerMustFinishCurrentTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
@ -4079,7 +4170,7 @@ bool AIPlayerMustFinishCurrentTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
if (!AITASK_IsTaskStillValid(pBot, Task)) { return false; }
if (IsPlayerGorge(pBot->Edict))
if (GetPlayerActiveClass(pBot->Player) == AVH_USER3_ALIEN_PLAYER2)
{
AvHTeamNumber BotTeam = pBot->Player->GetTeam();
@ -4149,39 +4240,6 @@ void AIPlayerNSAlienThink(AvHAIPlayer* pBot)
pBot->CurrentTask = AIPlayerGetNextTask(pBot);
if (gpGlobals->time - pBot->LastCombatTime > 5.0f)
{
bool bInMiddleOfMove = false;
if (pBot->BotNavInfo.CurrentPath.size() > 0 && pBot->BotNavInfo.CurrentPathPoint < pBot->BotNavInfo.CurrentPath.size())
{
bot_path_node CurrentMove = pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint];
bInMiddleOfMove = CurrentMove.flag != SAMPLE_POLYFLAGS_WALK;
}
if (!bInMiddleOfMove)
{
if (!PlayerHasAlienUpgradeOfType(pBot->Edict, HIVE_TECH_DEFENCE) && AITAC_IsAlienUpgradeAvailableForTeam(pBot->Player->GetTeam(), HIVE_TECH_DEFENCE))
{
BotEvolveUpgrade(pBot, pBot->CurrentFloorPosition, AlienGetDesiredUpgrade(pBot, HIVE_TECH_DEFENCE));
return;
}
if (!PlayerHasAlienUpgradeOfType(pBot->Edict, HIVE_TECH_MOVEMENT) && AITAC_IsAlienUpgradeAvailableForTeam(pBot->Player->GetTeam(), HIVE_TECH_MOVEMENT))
{
BotEvolveUpgrade(pBot, pBot->CurrentFloorPosition, AlienGetDesiredUpgrade(pBot, HIVE_TECH_MOVEMENT));
return;
}
if (!PlayerHasAlienUpgradeOfType(pBot->Edict, HIVE_TECH_SENSORY) && AITAC_IsAlienUpgradeAvailableForTeam(pBot->Player->GetTeam(), HIVE_TECH_SENSORY))
{
BotEvolveUpgrade(pBot, pBot->CurrentFloorPosition, AlienGetDesiredUpgrade(pBot, HIVE_TECH_SENSORY));
return;
}
}
}
if (pBot->CurrentTask && pBot->CurrentTask->TaskType != TASK_NONE)
{
BotProgressTask(pBot, pBot->CurrentTask);
@ -5488,16 +5546,29 @@ void AIPlayerReceiveMoveOrder(AvHAIPlayer* pBot, Vector Destination)
}
const AvHAIHiveDefinition* HiveRef = AITAC_GetHiveNearestLocation(Destination);
// Have we been asked to go to an empty hive? If so, then treat the order as a "help secure this hive" command
if (HiveRef && HiveRef->Status == HIVE_STATUS_UNBUILT && vDist2DSq(HiveRef->Location, Destination) < sqrf(UTIL_MetresToGoldSrcUnits(15.0f)))
if (HiveRef && vDist2DSq(HiveRef->Location, Destination) < sqrf(UTIL_MetresToGoldSrcUnits(15.0f)))
{
if (!AICOMM_IsHiveFullySecured(pBot, HiveRef, false))
if (HiveRef->Status == HIVE_STATUS_UNBUILT)
{
AITASK_SetSecureHiveTask(pBot, &pBot->CommanderTask, HiveRef->HiveEdict, ActualMoveLocation, false);
pBot->CommanderTask.bIssuedByCommander = true;
return;
if (!AICOMM_IsHiveFullySecured(pBot, HiveRef, false))
{
AITASK_SetSecureHiveTask(pBot, &pBot->CommanderTask, HiveRef->HiveEdict, ActualMoveLocation, false);
pBot->CommanderTask.bIssuedByCommander = true;
return;
}
}
else
{
if (UTIL_QuickTrace(pBot->Edict, Destination + Vector(0.0f, 0.0f, 32.0f), HiveRef->Location))
{
AITASK_SetAttackTask(pBot, &pBot->CommanderTask, HiveRef->HiveEdict, false);
pBot->CommanderTask.bIssuedByCommander = true;
return;
}
}
}
// Otherwise, treat as a normal move order. Go there and wait a bit to see what the commander wants to do next
@ -5816,7 +5887,7 @@ void AIPlayerSetAlienCapperPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
AvHAIResourceNode* NodeToCap = nullptr;
bool bCanAttackTowers = (!IsPlayerGorge(pBot->Edict) || PlayerHasWeapon(pBot->Player, WEAPON_GORGE_BILEBOMB));
bool bCanAttackTowers = (GetPlayerActiveClass(pBot->Player) != AVH_USER3_ALIEN_PLAYER2 || PlayerHasWeapon(pBot->Player, WEAPON_GORGE_BILEBOMB));
// If we're already capping a node, are at the node and there is an unfinished tower on there, then finish the job and don't move on yet
if (Task->TaskType == TASK_CAP_RESNODE)
@ -5844,7 +5915,7 @@ void AIPlayerSetAlienCapperPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
float ResourcesRequired = BALANCE_VAR(kResourceTowerCost);
if (!IsPlayerGorge(pBot->Edict))
if (GetPlayerActiveClass(pBot->Player) != AVH_USER3_ALIEN_PLAYER2)
{
ResourcesRequired += BALANCE_VAR(kGorgeCost);
}
@ -6787,7 +6858,7 @@ void AIPlayerSetSecondaryAlienTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
{
AvHAIPlayer* ThisBot = (*BotIt);
if (ThisBot != pBot && IsPlayerActiveInGame(ThisBot->Edict) && !IsPlayerGorge(ThisBot->Edict) && vDist2DSq(ThisBot->Edict->v.origin, ThisHive->FloorLocation) > sqrf(UTIL_MetresToGoldSrcUnits(15.0f)))
if (ThisBot != pBot && IsPlayerActiveInGame(ThisBot->Edict) && GetPlayerActiveClass(pBot->Player) != AVH_USER3_ALIEN_PLAYER2 && vDist2DSq(ThisBot->Edict->v.origin, ThisHive->FloorLocation) > sqrf(UTIL_MetresToGoldSrcUnits(15.0f)))
{
if (ThisBot->SecondaryBotTask.TaskType == TASK_DEFEND && ThisBot->SecondaryBotTask.TaskTarget == ThisHive->HiveEdict)
{
@ -6913,6 +6984,42 @@ void AIPlayerSetWantsAndNeedsAlienTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
return;
}
if (gpGlobals->time - pBot->LastCombatTime > 10.0f)
{
if (Task->TaskType == TASK_EVOLVE) { return; }
bool bInMiddleOfMove = false;
if (pBot->BotNavInfo.CurrentPath.size() > 0 && pBot->BotNavInfo.CurrentPathPoint < pBot->BotNavInfo.CurrentPath.size())
{
bot_path_node CurrentMove = pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint];
bInMiddleOfMove = CurrentMove.flag != SAMPLE_POLYFLAGS_WALK;
}
if (!bInMiddleOfMove)
{
if (!PlayerHasAlienUpgradeOfType(pBot->Edict, HIVE_TECH_DEFENCE) && AITAC_IsAlienUpgradeAvailableForTeam(pBot->Player->GetTeam(), HIVE_TECH_DEFENCE))
{
AITASK_SetEvolveTask(pBot, Task, pBot->CurrentFloorPosition, AlienGetDesiredUpgrade(pBot, HIVE_TECH_DEFENCE), true);
return;
}
if (!PlayerHasAlienUpgradeOfType(pBot->Edict, HIVE_TECH_MOVEMENT) && AITAC_IsAlienUpgradeAvailableForTeam(pBot->Player->GetTeam(), HIVE_TECH_MOVEMENT))
{
AITASK_SetEvolveTask(pBot, Task, pBot->CurrentFloorPosition, AlienGetDesiredUpgrade(pBot, HIVE_TECH_MOVEMENT), true);
return;
}
if (!PlayerHasAlienUpgradeOfType(pBot->Edict, HIVE_TECH_SENSORY) && AITAC_IsAlienUpgradeAvailableForTeam(pBot->Player->GetTeam(), HIVE_TECH_SENSORY))
{
AITASK_SetEvolveTask(pBot, Task, pBot->CurrentFloorPosition, AlienGetDesiredUpgrade(pBot, HIVE_TECH_SENSORY), true);
return;
}
}
}
if (CurrentHealth >= 1.0f) { return; }
bool bCanSelfHeal = (PlayerHasWeapon(pBot->Player, WEAPON_GORGE_HEALINGSPRAY) || PlayerHasWeapon(pBot->Player, WEAPON_FADE_METABOLIZE));

View file

@ -4,7 +4,24 @@
#include "AvHPlayer.h"
#include "AvHAIConstants.h"
// These define the bot's view frustum sides
#define FRUSTUM_PLANE_TOP 0
#define FRUSTUM_PLANE_BOTTOM 1
#define FRUSTUM_PLANE_LEFT 2
#define FRUSTUM_PLANE_RIGHT 3
#define FRUSTUM_PLANE_NEAR 4
#define FRUSTUM_PLANE_FAR 5
static const float BOT_FOV = 100.0f; // Bot's field of view;
static const float BOT_MAX_VIEW = 9999.0f; // Bot's maximum view distance;
static const float BOT_MIN_VIEW = 5.0f; // Bot's minimum view distance;
static const float BOT_ASPECT_RATIO = 1.77778f; // Bot's view aspect ratio, 1.333333 for 4:3, 1.777778 for 16:9, 1.6 for 16:10;
static const float f_fnheight = 2.0f * tan((BOT_FOV * 0.0174532925f) * 0.5f) * BOT_MIN_VIEW;
static const float f_fnwidth = f_fnheight * BOT_ASPECT_RATIO;
static const float f_ffheight = 2.0f * tan((BOT_FOV * 0.0174532925f) * 0.5f) * BOT_MAX_VIEW;
static const float f_ffwidth = f_ffheight * BOT_ASPECT_RATIO;
void BotJump(AvHAIPlayer* pBot);
@ -47,6 +64,8 @@ void BotUpdateViewRotation(AvHAIPlayer* pBot, float DeltaTime);
void BotUpdateView(AvHAIPlayer* pBot);
void BotClearEnemyTrackingInfo(enemy_status* TrackingInfo);
bool IsPlayerInBotFOV(AvHAIPlayer* Observer, edict_t* TargetPlayer);
void UpdateAIPlayerViewFrustum(AvHAIPlayer* pBot);
Vector GetVisiblePointOnPlayerFromObserver(edict_t* Observer, edict_t* TargetPlayer);

View file

@ -12,6 +12,8 @@
#include "../dlls/client.h"
#include <time.h>
float MAX_MATCH_TIME = 7200.0f;
double last_think_time = 0.0;
vector<AvHAIPlayer> ActiveAIPlayers;
@ -125,6 +127,21 @@ void AIMGR_UpdateAIPlayerCounts()
// Don't add or remove bots too quickly, otherwise it can cause lag or even overflows
if (gpGlobals->time - LastAIPlayerCountUpdate < 0.2f) { return; }
LastAIPlayerCountUpdate = gpGlobals->time;
bool bMatchExceededMaxLength = (gpGlobals->time - AIStartedTime) > MAX_MATCH_TIME;
// If bots are disabled, ensure we've removed all bots from the game
if (!AIMGR_IsBotEnabled() || (bMatchExceededMaxLength && AIMGR_GetNumActiveHumanPlayers() == 0))
{
if (AIMGR_GetNumAIPlayers() > 0)
{
AIMGR_RemoveAIPlayerFromTeam(0);
}
return;
}
if (!AIMGR_ShouldStartPlayerBalancing()) { return; }
// If game has ended, kick bots that have dropped back to the ready room
@ -134,18 +151,7 @@ void AIMGR_UpdateAIPlayerCounts()
return;
}
LastAIPlayerCountUpdate = gpGlobals->time;
// If bots are disabled, ensure we've removed all bots from the game
if (!AIMGR_IsBotEnabled())
{
if (AIMGR_GetNumAIPlayers() > 0)
{
AIMGR_RemoveAIPlayerFromTeam(0);
}
return;
}
BotFillTiming CurrentFillTiming = CONFIG_GetBotFillTiming();
@ -810,6 +816,40 @@ int AIMGR_GetNumHumanPlayersOnTeam(AvHTeamNumber Team)
return Result;
}
int AIMGR_GetNumHumanPlayersOnServer()
{
int Result = 0;
for (int i = 1; i <= gpGlobals->maxClients; i++)
{
edict_t* PlayerEdict = INDEXENT(i);
if (!FNullEnt(PlayerEdict) && IsPlayerHuman(PlayerEdict))
{
Result++;
}
}
return Result;
}
int AIMGR_GetNumActiveHumanPlayers()
{
int Result = 0;
for (int i = 1; i <= gpGlobals->maxClients; i++)
{
edict_t* PlayerEdict = INDEXENT(i);
if (!FNullEnt(PlayerEdict) && IsPlayerHuman(PlayerEdict) && PlayerEdict->v.team != TEAM_IND)
{
Result++;
}
}
return Result;
}
int AIMGR_GetNumAIPlayersWithRoleOnTeam(AvHTeamNumber Team, AvHAIBotRole Role, AvHAIPlayer* IgnoreAIPlayer)
{
int Result = 0;

View file

@ -68,6 +68,8 @@ void AIDEBUG_TestPathFind();
int AIMGR_GetNumAIPlayersOnTeam(AvHTeamNumber Team);
int AIMGR_GetNumHumanPlayersOnTeam(AvHTeamNumber Team);
int AIMGR_GetNumHumanPlayersOnServer();
int AIMGR_GetNumActiveHumanPlayers();
int AIMGR_GetNumAIPlayersWithRoleOnTeam(AvHTeamNumber Team, AvHAIBotRole Role, AvHAIPlayer* IgnoreAIPlayer);

View file

@ -4638,7 +4638,7 @@ bool AITAC_ShouldBotBuildHive(AvHAIPlayer* pBot, AvHAIHiveDefinition** EligibleH
float HiveCost = BALANCE_VAR(kHiveCost);
if (!IsPlayerGorge(pBot->Edict))
if (GetPlayerActiveClass(pBot->Player) != AVH_USER3_ALIEN_PLAYER2)
{
HiveCost += BALANCE_VAR(kGorgeCost);
}
@ -4655,7 +4655,7 @@ bool AITAC_ShouldBotBuildHive(AvHAIPlayer* pBot, AvHAIHiveDefinition** EligibleH
// If we're a higher lifeform, ensure we can't leave this to someone else before considering losing those resources
// We will ignore humans and third party bots, because we don't know if they will drop the hive or not. Not everyone can be as team-spirited as us...
if (!IsPlayerSkulk(pBot->Edict) && !IsPlayerGorge(pBot->Edict))
if (!IsPlayerSkulk(pBot->Edict) && GetPlayerActiveClass(pBot->Player) != AVH_USER3_ALIEN_PLAYER2)
{
vector<AvHAIPlayer*> OtherAITeamMates = AIMGR_GetAIPlayersOnTeam(BotTeam);
@ -4813,7 +4813,7 @@ bool AITAC_IsAlienCapperNeeded(AvHAIPlayer* pBot)
float ResourcesNeeded = BALANCE_VAR(kResourceTowerCost);
if (!IsPlayerGorge(pBot->Edict))
if (GetPlayerActiveClass(pBot->Player) != AVH_USER3_ALIEN_PLAYER2)
{
ResourcesNeeded += BALANCE_VAR(kGorgeCost);
}
@ -4823,8 +4823,8 @@ bool AITAC_IsAlienCapperNeeded(AvHAIPlayer* pBot)
if (EnemyNodeOwnership < 0.35f) { return false; }
}
bool bIsHigherLifeform = (!IsPlayerSkulk(pBot->Edict) && !IsPlayerGorge(pBot->Edict));
bool bIsBuilder = (IsPlayerGorge(pBot->Edict) && pBot->BotRole == BOT_ROLE_BUILDER);
bool bIsHigherLifeform = (!IsPlayerSkulk(pBot->Edict) && GetPlayerActiveClass(pBot->Player) != AVH_USER3_ALIEN_PLAYER2);
bool bIsBuilder = (GetPlayerActiveClass(pBot->Player) == AVH_USER3_ALIEN_PLAYER2 && pBot->BotRole == BOT_ROLE_BUILDER);
if (bIsHigherLifeform || bIsBuilder)
{
@ -4919,7 +4919,7 @@ bool AITAC_IsAlienBuilderNeeded(AvHAIPlayer* pBot)
}
// Don't build if we're a higher lifeform and there are others who could do the job instead
if (!IsPlayerSkulk(pBot->Edict) && !IsPlayerGorge(pBot->Edict))
if (!IsPlayerSkulk(pBot->Edict) && GetPlayerActiveClass(pBot->Player) != AVH_USER3_ALIEN_PLAYER2)
{
int NumSkulks = AITAC_GetNumPlayersOnTeamOfClass(BotTeam, AVH_USER3_ALIEN_PLAYER1, nullptr);
int NumGorges = AITAC_GetNumPlayersOnTeamOfClass(BotTeam, AVH_USER3_ALIEN_PLAYER2, nullptr);

View file

@ -84,6 +84,55 @@ void AITASK_OnCompleteCommanderTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
if (OldTaskType == TASK_MOVE)
{
AvHTeamNumber BotTeam = pBot->Player->GetTeam();
AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam);
if (AIMGR_GetTeamType(EnemyTeam) == AVH_CLASS_TYPE_ALIEN)
{
const AvHAIHiveDefinition* NearestHive = AITAC_GetHiveNearestLocation(pBot->Edict->v.origin);
if (NearestHive && vDist2DSq(NearestHive->FloorLocation, pBot->Edict->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(15.0f)))
{
if (NearestHive->Status == HIVE_STATUS_UNBUILT)
{
AITASK_SetSecureHiveTask(pBot, Task, NearestHive->HiveEdict, NearestHive->FloorLocation, false);
Task->bIssuedByCommander = true;
return;
}
else
{
if (UTIL_PlayerHasLOSToEntity(pBot->Edict, NearestHive->HiveEdict, UTIL_MetresToGoldSrcUnits(15.0f), false))
{
AITASK_SetAttackTask(pBot, Task, NearestHive->HiveEdict, false);
Task->bIssuedByCommander = true;
return;
}
}
}
}
else
{
if (vDist2DSq(pBot->Edict->v.origin, AITAC_GetTeamStartingLocation(EnemyTeam)) < sqrf(UTIL_MetresToGoldSrcUnits(10.0f)))
{
DeployableSearchFilter EnemyStuffFilter;
EnemyStuffFilter.DeployableTypes = SEARCH_ALL_STRUCTURES;
EnemyStuffFilter.DeployableTeam = EnemyTeam;
EnemyStuffFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
EnemyStuffFilter.ReachabilityTeam = (AvHTeamNumber)pBot->Edict->v.team;
EnemyStuffFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f);
AvHAIBuildableStructure NearbyEnemyStructure = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(EnemyTeam), &EnemyStuffFilter);
if (NearbyEnemyStructure.IsValid())
{
AITASK_SetAttackTask(pBot, Task, NearbyEnemyStructure.edict, false);
Task->bIssuedByCommander = true;
return;
}
}
}
DeployableSearchFilter EnemyResTowerFilter;
EnemyResTowerFilter.DeployableTypes = SEARCH_ANY_RES_TOWER;
EnemyResTowerFilter.DeployableTeam = (AIMGR_GetEnemyTeam(pBot->Player->GetTeam()));
@ -972,6 +1021,18 @@ bool AITASK_IsEvolveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
return !IsPlayerFade(pBot->Edict) && pBot->Player->GetResources() >= BALANCE_VAR(kFadeCost);
case ALIEN_LIFEFORM_FIVE:
return !IsPlayerOnos(pBot->Edict) && pBot->Player->GetResources() >= BALANCE_VAR(kOnosCost) && (AITAC_GetNumPlayersOnTeamOfClass(pBot->Player->GetTeam(), AVH_USER3_ALIEN_PLAYER5, nullptr) < 2);
case ALIEN_EVOLUTION_ONE:
case ALIEN_EVOLUTION_TWO:
case ALIEN_EVOLUTION_THREE:
return (!PlayerHasAlienUpgradeOfType(pBot->Edict, HIVE_TECH_DEFENCE) && AITAC_IsAlienUpgradeAvailableForTeam(pBot->Player->GetTeam(), HIVE_TECH_DEFENCE));
case ALIEN_EVOLUTION_SEVEN:
case ALIEN_EVOLUTION_EIGHT:
case ALIEN_EVOLUTION_NINE:
return (!PlayerHasAlienUpgradeOfType(pBot->Edict, HIVE_TECH_MOVEMENT) && AITAC_IsAlienUpgradeAvailableForTeam(pBot->Player->GetTeam(), HIVE_TECH_MOVEMENT));
case ALIEN_EVOLUTION_TEN:
case ALIEN_EVOLUTION_ELEVEN:
case ALIEN_EVOLUTION_TWELVE:
return (!PlayerHasAlienUpgradeOfType(pBot->Edict, HIVE_TECH_SENSORY) && AITAC_IsAlienUpgradeAvailableForTeam(pBot->Player->GetTeam(), HIVE_TECH_SENSORY));
default:
return false;
}
@ -1003,7 +1064,7 @@ bool AITASK_IsAlienHealTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
{
if (!Task->TaskTarget || FNullEnt(Task->TaskTarget) || (Task->TaskTarget->v.effects & EF_NODRAW) || Task->TaskTarget->v.deadflag != DEAD_NO) { return false; }
if (!IsPlayerGorge(pBot->Edict)) { return false; }
if (GetPlayerActiveClass(pBot->Player) != AVH_USER3_ALIEN_PLAYER2) { return false; }
if (GetPlayerOverallHealthPercent(Task->TaskTarget) >= 0.99f) { return false; }
@ -1331,7 +1392,7 @@ void BotProgressReinforceStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
if (!vIsZero(Task->TaskLocation))
{
float ResourceCost = UTIL_GetCostOfStructureType(NextStructure);
if (!IsPlayerGorge(pBot->Edict))
if (GetPlayerActiveClass(pBot->Player) != AVH_USER3_ALIEN_PLAYER2)
{
ResourceCost += BALANCE_VAR(kGorgeCost);
}
@ -1345,7 +1406,7 @@ void BotProgressReinforceStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
}
// We have nothing to build, or we don't have enough resources yet, see if there's any unfinished structures we can finish off
if (IsPlayerGorge(pBot->Edict))
if (GetPlayerActiveClass(pBot->Player) == AVH_USER3_ALIEN_PLAYER2)
{
DeployableSearchFilter UnfinishedFilter;
UnfinishedFilter.DeployableTeam = BotTeam;
@ -1965,7 +2026,7 @@ void AlienProgressBuildHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
int ResRequired = UTIL_GetCostOfStructureType(Task->StructureType);
if (!IsPlayerGorge(pBot->Edict))
if (GetPlayerActiveClass(pBot->Player) != AVH_USER3_ALIEN_PLAYER2)
{
ResRequired += BALANCE_VAR(kGorgeCost);
}
@ -2023,7 +2084,7 @@ void AlienProgressBuildTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
int ResRequired = UTIL_GetCostOfStructureType(Task->StructureType);
if (!IsPlayerGorge(pBot->Edict))
if (GetPlayerActiveClass(pBot->Player) != AVH_USER3_ALIEN_PLAYER2)
{
ResRequired += BALANCE_VAR(kGorgeCost);
}
@ -2064,7 +2125,6 @@ void BotAlienPlaceChamber(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, AvHAIDeploya
{
if (vIsZero(Task->TaskLocation) || DesiredStructure == STRUCTURE_NONE) { return; }
if (Task->ActiveBuildInfo.BuildStatus == BUILD_ATTEMPT_PENDING) { return; }
float DistFromBuildLocation = vDist2DSq(pBot->Edict->v.origin, Task->TaskLocation);
@ -2092,7 +2152,7 @@ void BotAlienPlaceChamber(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, AvHAIDeploya
int ResRequired = UTIL_GetCostOfStructureType(DesiredStructure);
if (!IsPlayerGorge(pBot->Edict))
if (GetPlayerActiveClass(pBot->Player) != AVH_USER3_ALIEN_PLAYER2)
{
ResRequired += BALANCE_VAR(kGorgeCost);
}
@ -2102,7 +2162,7 @@ void BotAlienPlaceChamber(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, AvHAIDeploya
return;
}
if (!IsPlayerGorge(pBot->Edict))
if (GetPlayerActiveClass(pBot->Player) != AVH_USER3_ALIEN_PLAYER2)
{
BotEvolveLifeform(pBot, pBot->CurrentFloorPosition, ALIEN_LIFEFORM_TWO);
return;
@ -2145,7 +2205,7 @@ void BotAlienBuildResTower(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, const AvHAI
int ResRequired = UTIL_GetCostOfStructureType(STRUCTURE_ALIEN_RESTOWER);
if (!IsPlayerGorge(pBot->Edict))
if (GetPlayerActiveClass(pBot->Player) != AVH_USER3_ALIEN_PLAYER2)
{
ResRequired += BALANCE_VAR(kGorgeCost);
}
@ -2157,7 +2217,7 @@ void BotAlienBuildResTower(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, const AvHAI
return;
}
if (!IsPlayerGorge(pBot->Edict))
if (GetPlayerActiveClass(pBot->Player) != AVH_USER3_ALIEN_PLAYER2)
{
BotEvolveLifeform(pBot, pBot->CurrentFloorPosition, ALIEN_LIFEFORM_TWO);
return;
@ -2200,7 +2260,7 @@ void BotAlienBuildHive(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, const AvHAIHive
{
int ResRequired = UTIL_GetCostOfStructureType(STRUCTURE_ALIEN_HIVE);
if (!IsPlayerGorge(pBot->Edict))
if (GetPlayerActiveClass(pBot->Player) != AVH_USER3_ALIEN_PLAYER2)
{
ResRequired += BALANCE_VAR(kGorgeCost);
}
@ -2210,7 +2270,7 @@ void BotAlienBuildHive(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, const AvHAIHive
return;
}
if (!IsPlayerGorge(pBot->Edict))
if (GetPlayerActiveClass(pBot->Player) != AVH_USER3_ALIEN_PLAYER2)
{
BotEvolveLifeform(pBot, pBot->CurrentFloorPosition, ALIEN_LIFEFORM_TWO);
return;
@ -2325,7 +2385,7 @@ void AlienProgressCapResNodeTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
{
// Node is empty and not capped by either side
int NumResourcesRequired = (IsPlayerGorge(pBot->Edict) ? BALANCE_VAR(kResourceTowerCost) : (BALANCE_VAR(kResourceTowerCost) + BALANCE_VAR(kGorgeCost)));
int NumResourcesRequired = (GetPlayerActiveClass(pBot->Player) == AVH_USER3_ALIEN_PLAYER2) ? BALANCE_VAR(kResourceTowerCost) : (BALANCE_VAR(kResourceTowerCost) + BALANCE_VAR(kGorgeCost));
// We have enough resources to place the tower (includes cost of evolving to gorge if necessary)
if (pBot->Player->GetResources() >= NumResourcesRequired)