Bot improvements

This commit is contained in:
RGreenlees 2024-03-06 22:31:48 +00:00 committed by pierow
parent 0df89d9abe
commit 8c9b150ba5
19 changed files with 813 additions and 392 deletions

View File

@ -131,6 +131,7 @@ cvar_t avh_botminplayers = { kvBotMinPlayers,"0", FCVAR_SERVER }; // If bots
cvar_t avh_botskill = { kvBotSkill,"0", FCVAR_SERVER }; // Sets the skill for the bots (0 = easiest, 3 = hardest)
cvar_t avh_botusemapdefaults = { kvBotUseMapDefaults,"0", FCVAR_SERVER }; // If bot auto mode == 1 then the min players will be taken from the config
cvar_t avh_botcommandermode = { kvBotCommanderMode,"0", FCVAR_SERVER }; // 0 = Bots never command, 1 = If nobody takes charge, 2 = Only if no humans on team
cvar_t avh_botdebugmode = { kvBotDebugMode,"0", FCVAR_SERVER }; // 0 = Regular play, 1 = Drone mode, 2 = Test Navigation mode
//playtest cvars
@ -222,6 +223,7 @@ void GameDLLInit( void )
CVAR_REGISTER(&avh_botusemapdefaults);
CVAR_REGISTER(&avh_botskill);
CVAR_REGISTER(&avh_botcommandermode);
CVAR_REGISTER(&avh_botdebugmode);
// Register AvH variables
CVAR_REGISTER (&avh_drawdamage);

View File

@ -897,6 +897,10 @@ bool AICOMM_IsRequestValid(ai_commander_request* Request)
return !PlayerHasWeapon(PlayerRef, WEAPON_MARINE_HMG)
&& !AITAC_ItemExistsInLocation(Requestor->v.origin, DEPLOYABLE_ITEM_HMG, RequestorTeam, AI_REACHABILITY_MARINE, 0.0f, UTIL_MetresToGoldSrcUnits(5.0f), false)
&& AITAC_IsCompletedStructureOfTypeNearLocation(RequestorTeam, STRUCTURE_MARINE_ADVARMOURY, Requestor->v.origin, UTIL_MetresToGoldSrcUnits(5.0f));
case BUILD_GRENADE_GUN:
return !PlayerHasWeapon(PlayerRef, WEAPON_MARINE_GL)
&& !AITAC_ItemExistsInLocation(Requestor->v.origin, DEPLOYABLE_ITEM_GRENADELAUNCHER, RequestorTeam, AI_REACHABILITY_MARINE, 0.0f, UTIL_MetresToGoldSrcUnits(5.0f), false)
&& AITAC_IsCompletedStructureOfTypeNearLocation(RequestorTeam, STRUCTURE_MARINE_ADVARMOURY, Requestor->v.origin, UTIL_MetresToGoldSrcUnits(5.0f));
case BUILD_MINES:
return !PlayerHasWeapon(PlayerRef, WEAPON_MARINE_MINES)
&& !AITAC_ItemExistsInLocation(Requestor->v.origin, DEPLOYABLE_ITEM_MINES, RequestorTeam, AI_REACHABILITY_MARINE, 0.0f, UTIL_MetresToGoldSrcUnits(5.0f), false)
@ -952,8 +956,6 @@ bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot)
AvHAIBuildableStructure* BaseArmoury = AITAC_FindClosestDeployableToLocation(CommChair->v.origin, &StructureFilter);
if (!BaseArmoury && !FNullEnt(BaseBuilder))
{
@ -2529,7 +2531,45 @@ bool AICOMM_CheckForNextSupportAction(AvHAIPlayer* pBot)
NextRequest->bResponded = bSuccess;
return true;
}
if (NextRequest->RequestType == BUILD_GRENADE_GUN)
{
if (pBot->Player->GetResources() < BALANCE_VAR(kGrenadeLauncherCost)) { return false; }
DeployableSearchFilter ArmouryFilter;
ArmouryFilter.DeployableTeam = CommanderTeam;
ArmouryFilter.DeployableTypes = STRUCTURE_MARINE_ADVARMOURY;
ArmouryFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
ArmouryFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
AvHAIBuildableStructure* NearestArmoury = AITAC_FindClosestDeployableToLocation(Requestor->v.origin, &ArmouryFilter);
if (!NearestArmoury)
{
NextRequest->bResponded = true;
return false;
}
Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), NearestArmoury->Location, UTIL_MetresToGoldSrcUnits(4.0f));
if (vIsZero(DeployLocation))
{
DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), NearestArmoury->Location, UTIL_MetresToGoldSrcUnits(4.0f));
}
if (vIsZero(DeployLocation))
{
NextRequest->bResponded = true;
return false;
}
bool bSuccess = AICOMM_DeployItem(pBot, DEPLOYABLE_ITEM_GRENADELAUNCHER, DeployLocation);
NextRequest->ResponseAttempts++;
NextRequest->bResponded = bSuccess;
return true;
}
if (NextRequest->RequestType == BUILD_PHASEGATE)
@ -3002,7 +3042,6 @@ bool AICOMM_ShouldBeacon(AvHAIPlayer* pBot)
return false;
}
void AICOMM_ReceiveChatRequest(AvHAIPlayer* Commander, edict_t* Requestor, const char* Request)
@ -3021,6 +3060,10 @@ void AICOMM_ReceiveChatRequest(AvHAIPlayer* Commander, edict_t* Requestor, const
{
NewRequestType = BUILD_HMG;
}
else if (!stricmp(Request, "gl"))
{
NewRequestType = BUILD_GRENADE_GUN;
}
else if (!stricmp(Request, "mines"))
{
NewRequestType = BUILD_MINES;

View File

@ -563,6 +563,7 @@ typedef struct _NAV_STATUS
Vector LastNavMeshCheckPosition = g_vecZero;
Vector LastNavMeshPosition = g_vecZero; // Tracks the last place the bot was on the nav mesh. Useful if accidentally straying off it
Vector LastOpenLocation = g_vecZero; // Tracks the last place the bot had enough room to move around people. Useful if in a vent and need to back up somewhere to let another player past.
int CurrentMoveType = MOVETYPE_NONE; // Tracks the edict's current movement type

View File

@ -20,12 +20,12 @@ bool UTIL_CommanderTrace(const edict_t* pEdict, const Vector& start, const Vecto
return (hit.flFraction >= 1.0f);
}
bool UTIL_QuickTrace(const edict_t* pEdict, const Vector& start, const Vector& end)
bool UTIL_QuickTrace(const edict_t* pEdict, const Vector& start, const Vector& end, bool bAllowStartSolid)
{
TraceResult hit;
edict_t* IgnoreEdict = (!FNullEnt(pEdict)) ? pEdict->v.pContainingEntity : NULL;
UTIL_TraceLine(start, end, ignore_monsters, ignore_glass, IgnoreEdict, &hit);
return (hit.flFraction >= 1.0f && !hit.fAllSolid);
return (hit.flFraction >= 1.0f && !hit.fAllSolid && (bAllowStartSolid || !hit.fStartSolid));
}
bool UTIL_QuickHullTrace(const edict_t* pEdict, const Vector& start, const Vector& end, bool bAllowStartSolid)
@ -175,12 +175,12 @@ Vector UTIL_GetFloorUnderEntity(const edict_t* Edict)
return Edict->v.origin;
}
Vector UTIL_GetClosestPointOnEntityToLocation(const Vector Location, edict_t* Entity)
Vector UTIL_GetClosestPointOnEntityToLocation(const Vector Location, const edict_t* Entity)
{
return Vector(clampf(Location.x, Entity->v.absmin.x, Entity->v.absmax.x), clampf(Location.y, Entity->v.absmin.y, Entity->v.absmax.y), clampf(Location.z, Entity->v.absmin.z, Entity->v.absmax.z));
}
Vector UTIL_GetClosestPointOnEntityToLocation(const Vector Location, edict_t* Entity, const Vector EntityLocation)
Vector UTIL_GetClosestPointOnEntityToLocation(const Vector Location, const edict_t* Entity, const Vector EntityLocation)
{
Vector MinVec = EntityLocation - (Entity->v.size * 0.5f);
Vector MaxVec = EntityLocation + (Entity->v.size * 0.5f);

View File

@ -7,7 +7,7 @@
#include "AvHAIConstants.h"
bool UTIL_CommanderTrace(const edict_t* pEdict, const Vector& start, const Vector& end);
bool UTIL_QuickTrace(const edict_t* pEdict, const Vector& start, const Vector& end);
bool UTIL_QuickTrace(const edict_t* pEdict, const Vector& start, const Vector& end, bool bAllowStartSolid = false);
bool UTIL_QuickHullTrace(const edict_t* pEdict, const Vector& start, const Vector& end, bool bAllowStartSolid = false);
bool UTIL_QuickHullTrace(const edict_t* pEdict, const Vector& start, const Vector& end, int hullNum, bool bAllowStartSolid = false);
edict_t* UTIL_TraceEntity(const edict_t* pEdict, const Vector& start, const Vector& end);
@ -19,8 +19,8 @@ Vector UTIL_GetEntityGroundLocation(const edict_t* pEntity);
Vector UTIL_GetCentreOfEntity(const edict_t* Entity);
Vector UTIL_GetFloorUnderEntity(const edict_t* Edict);
Vector UTIL_GetClosestPointOnEntityToLocation(const Vector UserLocation, edict_t* Entity);
Vector UTIL_GetClosestPointOnEntityToLocation(const Vector Location, edict_t* Entity, const Vector EntityLocation);
Vector UTIL_GetClosestPointOnEntityToLocation(const Vector UserLocation, const edict_t* Entity);
Vector UTIL_GetClosestPointOnEntityToLocation(const Vector Location, const edict_t* Entity, const Vector EntityLocation);
AvHAIDeployableStructureType IUSER3ToStructureType(const int inIUSER3);

View File

@ -65,6 +65,61 @@ bool vBBOverlaps2D(const Vector MinBBA, const Vector MaxBBA, const Vector MinBBB
&& (MinBBA.y < MaxBBB.y && MaxBBA.y > MinBBB.y));
}
// Given three collinear points p, q, r, the function checks if
// point q lies on line segment 'pr'
bool onSegment(Vector p, Vector q, Vector r)
{
if (q.x <= fmaxf(p.x, r.x) && q.x >= fminf(p.x, r.x) &&
q.y <= fmaxf(p.y, r.y) && q.y >= fminf(p.y, r.y))
return true;
return false;
}
// To find orientation of ordered triplet (p, q, r).
// The function returns following values
// 0 --> p, q and r are collinear
// 1 --> Clockwise
// 2 --> Counterclockwise
int orientation(Vector p, Vector q, Vector r)
{
// See https://www.geeksforgeeks.org/orientation-3-ordered-points/
// for details of below formula.
int val = (q.y - p.y) * (r.x - q.x) -
(q.x - p.x) * (r.y - q.y);
if (val == 0) return 0; // collinear
return (val > 0) ? 1 : 2; // clock or counterclock wise
}
bool vIntersects2D(const Vector LineAStart, const Vector LineAEnd, const Vector LineBStart, const Vector LineBEnd)
{
int o1 = orientation(LineAStart, LineAEnd, LineBStart);
int o2 = orientation(LineAStart, LineAEnd, LineBEnd);
int o3 = orientation(LineBStart, LineBEnd, LineAStart);
int o4 = orientation(LineBStart, LineBEnd, LineAEnd);
// General case
if (o1 != o2 && o3 != o4)
return true;
// Special Cases
// p1, q1 and p2 are collinear and p2 lies on segment p1q1
if (o1 == 0 && onSegment(LineAStart, LineBStart, LineAEnd)) return true;
// p1, q1 and q2 are collinear and q2 lies on segment p1q1
if (o2 == 0 && onSegment(LineAStart, LineBEnd, LineAEnd)) return true;
// p2, q2 and p1 are collinear and p1 lies on segment p2q2
if (o3 == 0 && onSegment(LineBStart, LineAStart, LineBEnd)) return true;
// p2, q2 and q1 are collinear and q1 lies on segment p2q2
if (o4 == 0 && onSegment(LineBStart, LineAEnd, LineBEnd)) return true;
return false; // Doesn't fall in any of the above cases
}
Vector vClosestPointOnBB(const Vector Point, const Vector MinBB, const Vector MaxBB)
{
return Vector(clampf(Point.x, MinBB.x, MaxBB.x), clampf(Point.y, MinBB.y, MaxBB.y), clampf(Point.z, MinBB.z, MaxBB.z));

View File

@ -118,6 +118,9 @@ Vector UTIL_GetSurfaceNormal(const Vector v1, const Vector v2, const Vector v3);
bool vPointOverlaps3D(const Vector Point, const Vector MinBB, const Vector MaxBB);
bool vPointOverlaps2D(const Vector Point, const Vector MinBB, const Vector MaxBB);
bool vBBOverlaps2D(const Vector MinBBA, const Vector MaxBBA, const Vector MinBBB, const Vector MaxBBB);
// For the two lines provided, returns true if they cross each other on the X and Y axis
bool vIntersects2D(const Vector LineAStart, const Vector LineAEnd, const Vector LineBStart, const Vector LineBEnd);
Vector vClosestPointOnBB(const Vector Point, const Vector MinBB, const Vector MaxBB);

View File

@ -313,15 +313,21 @@ void AIDEBUG_DrawOffMeshConnections(float DrawTime)
}
}
void UTIL_UpdateTileCache()
bool UTIL_UpdateTileCache()
{
bTileCacheUpToDate = true;
for (int i = 0; i < MAX_NAV_MESHES; i++)
{
if (NavMeshes[i].tileCache)
{
NavMeshes[i].tileCache->update(0.0f, NavMeshes[i].navMesh, &bTileCacheUpToDate);
bool bUpToDate;
NavMeshes[i].tileCache->update(0.0f, NavMeshes[i].navMesh, &bUpToDate);
if (!bUpToDate) { bTileCacheUpToDate = false; }
}
}
}
return bTileCacheUpToDate;
}
Vector UTIL_AdjustPointAwayFromNavWall(const Vector Location, const float MaxDistanceFromWall)
@ -1274,16 +1280,16 @@ Vector AdjustPointForPathfinding(const Vector Point)
{
Vector TraceStart = ProjectedPoint + Vector(0.0f, 0.0f, 32.0f);
Vector TraceEnd = TraceStart - Vector(0.0f, 0.0f, 50.0f);
Vector NewPoint = UTIL_GetHullTraceHitLocation(TraceStart, TraceEnd, head_hull);
Vector NewPoint = UTIL_GetHullTraceHitLocation(TraceStart, TraceEnd, point_hull);
if (!vIsZero(NewPoint)) { return NewPoint; }
}
}
else
{
Vector TraceStart = ProjectedPoint + Vector(0.0f, 0.0f, 32.0f);
Vector TraceEnd = TraceStart - Vector(0.0f, 0.0f, 50.0f);
Vector NewPoint = UTIL_GetHullTraceHitLocation(TraceStart, TraceEnd, head_hull);
Vector TraceStart = ProjectedPoint + Vector(0.0f, 0.0f, 5.0f);
Vector TraceEnd = TraceStart - Vector(0.0f, 0.0f, 32.0f);
Vector NewPoint = UTIL_GetHullTraceHitLocation(TraceStart, TraceEnd, point_hull);
if (!vIsZero(NewPoint)) { return NewPoint; }
}
@ -1369,31 +1375,14 @@ dtStatus FindFlightPathToPoint(const nav_profile &NavProfile, Vector FromLocatio
m_navQuery->closestPointOnPoly(PolyPath[nPathCount - 1], EndNearest, epos, 0);
float DistToEnd = dtVdist(EndNearest, epos);
if (DistToEnd > MaxAcceptableDistance)
if (dtVdistSqr(EndNearest, epos) > sqrf(MaxAcceptableDistance))
{
// For flight paths, if we can directly reach the end point from where our path finding got to, then see if we can fly directly to our desired target
Vector StartTrace = Vector(EndNearest[0], -EndNearest[2], EndNearest[1]);
StartTrace.z += 20.0f;
TraceResult hit;
UTIL_TraceHull(StartTrace, ToLocation, ignore_monsters, head_hull, nullptr, &hit);
float Reached = (!hit.fAllSolid && !hit.fStartSolid) ? hit.flFraction : 0.0f;
float DesiredDist = vDist3D(StartTrace, ToLocation);
float ActualDist = DesiredDist * Reached;
// We couldn't reach our end goal
if (DesiredDist - ActualDist > MaxAcceptableDistance)
{
return DT_FAILURE;
}
return DT_FAILURE;
}
else
{
dtVcopy(EndNearest, epos);
}
dtVcopy(EndNearest, epos);
}
status = m_navQuery->findStraightPath(StartNearest, EndNearest, PolyPath, nPathCount, StraightPath, straightPathFlags, StraightPolyPath, &nVertCount, MAX_AI_PATH_SIZE, DT_STRAIGHTPATH_ALL_CROSSINGS);
@ -1559,7 +1548,7 @@ Vector UTIL_FindHighestSuccessfulTracePoint(const Vector TraceFrom, const Vector
}
else
{
if (NextPoint != ZERO_VECTOR && UTIL_QuickHullTrace(nullptr, CurrentTarget, NextPoint, head_hull))
if (!vIsZero(NextPoint) && UTIL_QuickHullTrace(nullptr, CurrentTarget, NextPoint, head_hull, false))
{
CurrentHighest = CurrentTarget;
}
@ -1841,7 +1830,6 @@ dtStatus FindPathClosestToPoint(AvHAIPlayer* pBot, const BotMoveStyle MoveStyle,
// At this point we have our path. Copy it to the path store
int nIndex = 0;
TraceResult hit;
Vector TraceStart;
pBot->BotNavInfo.SpecialMovementFlags = 0;
@ -1860,16 +1848,11 @@ dtStatus FindPathClosestToPoint(AvHAIPlayer* pBot, const BotMoveStyle MoveStyle,
NextPathNode.Location = UTIL_AdjustPointAwayFromNavWall(NextPathNode.Location, 16.0f);
TraceStart.x = NextPathNode.Location.x;
TraceStart.y = NextPathNode.Location.y;
TraceStart.z = NextPathNode.Location.z + 18.0f;
NextPathNode.Location = AdjustPointForPathfinding(NextPathNode.Location);
if ((CurrFlags != SAMPLE_POLYFLAGS_JUMP && CurrFlags != SAMPLE_POLYFLAGS_WALLCLIMB) || NextPathNode.FromLocation.z > NextPathNode.Location.z)
{
NextPathNode.Location.z += 17.0f;
NextPathNode.Location.z += GetPlayerOriginOffsetFromFloor(pBot->Edict, (CurrArea == SAMPLE_POLYAREA_CROUCH)).z;
}
pBot->BotNavInfo.SpecialMovementFlags |= CurrFlags;
@ -1897,54 +1880,6 @@ dtStatus FindPathClosestToPoint(AvHAIPlayer* pBot, const BotMoveStyle MoveStyle,
NextPathNode.requiredZ += 5.0f;
}
/*if (IsPlayerSkulk(pBot->Edict) && CurrFlags == SAMPLE_POLYFLAGS_LADDER)
{
bool bIsGoingUpLadder = (NextPathNode.Location.z > NodeFromLocation.z);
Vector NearestLadderTopPoint = UTIL_GetNearestLadderTopPoint(NextPathNode.FromLocation);
Vector NearestLadderCentre = UTIL_GetNearestLadderCentrePoint(NextPathNode.FromLocation);
Vector LadderNormal = UTIL_GetVectorNormal2D(NextPathNode.FromLocation - NearestLadderCentre);
Vector OnLadderPoint = NearestLadderTopPoint;
OnLadderPoint.z = FromLocation.z;
OnLadderPoint = OnLadderPoint + (LadderNormal * 16.0f);
bot_path_node GetOnLadderNode;
GetOnLadderNode.area = CurrArea;
GetOnLadderNode.flag = SAMPLE_POLYFLAGS_WALLCLIMB;
GetOnLadderNode.FromLocation = FromLocation;
GetOnLadderNode.Location = OnLadderPoint;
GetOnLadderNode.requiredZ = FromLocation.z;
path.push_back(GetOnLadderNode);
bot_path_node EndLadderNode;
EndLadderNode.area = CurrArea;
EndLadderNode.flag = SAMPLE_POLYFLAGS_WALLCLIMB;
EndLadderNode.FromLocation = OnLadderPoint;
Vector EndClimbPoint = NearestLadderTopPoint;
EndClimbPoint.z = NewRequiredZ;
if (vDist2DSq(EndClimbPoint, OnLadderPoint) < sqrf(8.0f))
{
EndClimbPoint = EndClimbPoint - (LadderNormal * 8.0f);
}
EndLadderNode.Location = EndClimbPoint;
EndLadderNode.requiredZ = NewRequiredZ;
path.push_back(EndLadderNode);
FromLocation = EndClimbPoint;
CurrFlags = SAMPLE_POLYFLAGS_WALLCLIMB;
NextPathNode.FromLocation = FromLocation;
}*/
}
else
{
@ -2029,120 +1964,182 @@ bool UTIL_PointIsReachable(const nav_profile &NavProfile, const Vector FromLocat
bool HasBotReachedPathPoint(const AvHAIPlayer* pBot)
{
if (pBot->BotNavInfo.CurrentPath.size() == 0)
if (pBot->BotNavInfo.CurrentPath.size() == 0 || pBot->BotNavInfo.CurrentPathPoint == pBot->BotNavInfo.CurrentPath.end())
{
return true;
}
if (pBot->BotNavInfo.CurrentPathPoint == pBot->BotNavInfo.CurrentPath.end())
{
return true;
}
Vector CurrentPos = (pBot->BotNavInfo.IsOnGround) ? pBot->Edict->v.origin : pBot->CurrentFloorPosition;
edict_t* pEdict = pBot->Edict;
SamplePolyFlags CurrentNavFlag = (SamplePolyFlags)pBot->BotNavInfo.CurrentPathPoint->flag;
Vector MoveFrom = pBot->BotNavInfo.CurrentPathPoint->FromLocation;
Vector MoveTo = pBot->BotNavInfo.CurrentPathPoint->Location;
float RequiredClimbHeight = pBot->BotNavInfo.CurrentPathPoint->requiredZ;
Vector NextMoveLocation = ZERO_VECTOR;
SamplePolyFlags NextMoveFlag = SAMPLE_POLYFLAGS_DISABLED;
vector<bot_path_node>::iterator NextPathPoint = next(pBot->BotNavInfo.CurrentPathPoint);
bool bIsAtFinalPathPoint = (NextPathPoint == pBot->BotNavInfo.CurrentPath.end());
Vector ClosestPointToPath = vClosestPointOnLine2D(MoveFrom, MoveTo, pEdict->v.origin);
bool bDestIsDirectlyReachable = UTIL_PointIsDirectlyReachable(CurrentPos, MoveTo);
bool bAtOrPastDestination = vEquals2D(ClosestPointToPath, MoveTo, 8.0f) && bDestIsDirectlyReachable;
dtPolyRef BotPoly = pBot->BotNavInfo.CurrentPoly;
dtPolyRef DestinationPoly = pBot->BotNavInfo.CurrentPathPoint->poly;
float playerRadius = GetPlayerRadius(pBot->Player);
if (NextPathPoint != pBot->BotNavInfo.CurrentPath.end())
{
NextMoveLocation = NextPathPoint->Location;
NextMoveFlag = (SamplePolyFlags)NextPathPoint->flag;
}
switch (CurrentNavFlag)
{
case SAMPLE_POLYFLAGS_WALK:
{
if (!bIsAtFinalPathPoint)
{
return (bAtOrPastDestination || (vDist2D(pEdict->v.origin, MoveTo) <= 8.0f && (fabs(pBot->CurrentFloorPosition.z - MoveTo.z) < 50.0f)));
}
else
{
return ((vDist2D(pEdict->v.origin, MoveTo) < playerRadius && bDestIsDirectlyReachable) || bAtOrPastDestination);
}
}
case SAMPLE_POLYFLAGS_BLOCKED:
case SAMPLE_POLYFLAGS_TEAM1STRUCTURE:
case SAMPLE_POLYFLAGS_TEAM2STRUCTURE:
return bAtOrPastDestination;
case SAMPLE_POLYFLAGS_FALL:
case SAMPLE_POLYFLAGS_JUMP:
{
if (!bIsAtFinalPathPoint)
{
if (pBot->BotNavInfo.IsOnGround && fabsf(pEdict->v.origin.z - MoveTo.z) > 50.0f) { return false; }
Vector thisMoveDir = UTIL_GetVectorNormal2D(MoveTo - MoveFrom);
Vector nextMoveDir = UTIL_GetVectorNormal2D(next(pBot->BotNavInfo.CurrentPathPoint)->Location - MoveTo);
float DirectionDot = UTIL_GetDotProduct(thisMoveDir, nextMoveDir);
if (DirectionDot >= -0.5f)
{
return bAtOrPastDestination && UTIL_PointIsDirectlyReachable(pBot, pBot->CurrentFloorPosition, NextPathPoint->Location) && UTIL_QuickTrace(pBot->Edict, pBot->Edict->v.origin, NextPathPoint->Location);
}
else
{
return bAtOrPastDestination && pBot->BotNavInfo.IsOnGround && fabs(pBot->CurrentFloorPosition.z - MoveTo.z) < 50.0f;
}
}
else
{
return (vDist2D(pEdict->v.origin, MoveTo) <= playerRadius && fabsf(pEdict->v.origin.z - MoveTo.z) < 50.0f && pBot->BotNavInfo.IsOnGround);
}
}
case SAMPLE_POLYFLAGS_WALLCLIMB:
{
if (!bIsAtFinalPathPoint && next(pBot->BotNavInfo.CurrentPathPoint)->flag == SAMPLE_POLYFLAGS_WALLCLIMB)
{
Vector ClosestPointToPath = vClosestPointOnLine(MoveFrom, MoveTo, pEdict->v.origin);
return vEquals(ClosestPointToPath, MoveTo, GetPlayerRadius(pBot->Player));
}
return bAtOrPastDestination && fabs(pEdict->v.origin.z - MoveTo.z) < 50.0f;
}
case SAMPLE_POLYFLAGS_LADDER:
{
if (MoveTo.z > MoveFrom.z)
{
return ((BotPoly == DestinationPoly) && UTIL_QuickTrace(pEdict, pEdict->v.origin, MoveTo));
}
else
{
return (fabs(pBot->CollisionHullBottomLocation.z - MoveTo.z) < 50.0f);
}
}
case SAMPLE_POLYFLAGS_TEAM1PHASEGATE:
case SAMPLE_POLYFLAGS_TEAM2PHASEGATE:
return (vDist2DSq(pBot->CurrentFloorPosition, MoveTo) < sqrf(32.0f));
case SAMPLE_POLYFLAGS_LIFT:
return bAtOrPastDestination;
default:
return (bAtOrPastDestination && UTIL_QuickTrace(pEdict, pEdict->v.origin, MoveTo));
{
case SAMPLE_POLYFLAGS_WALK:
return HasBotCompletedWalkMove(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
case SAMPLE_POLYFLAGS_WELD:
case SAMPLE_POLYFLAGS_DOOR:
case SAMPLE_POLYFLAGS_TEAM1STRUCTURE:
case SAMPLE_POLYFLAGS_TEAM2STRUCTURE:
return HasBotCompletedObstacleMove(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
case SAMPLE_POLYFLAGS_LADDER:
return HasBotCompletedLadderMove(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
case SAMPLE_POLYFLAGS_FALL:
return HasBotCompletedFallMove(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
case SAMPLE_POLYFLAGS_WALLCLIMB:
return HasBotCompletedClimbMove(pBot, MoveFrom, MoveTo, RequiredClimbHeight, NextMoveLocation, NextMoveFlag);
case SAMPLE_POLYFLAGS_JUMP:
case SAMPLE_POLYFLAGS_DUCKJUMP:
case SAMPLE_POLYFLAGS_BLOCKED:
return HasBotCompletedJumpMove(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
case SAMPLE_POLYFLAGS_TEAM1PHASEGATE:
case SAMPLE_POLYFLAGS_TEAM2PHASEGATE:
return HasBotCompletedPhaseGateMove(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
case SAMPLE_POLYFLAGS_LIFT:
return HasBotCompletedLiftMove(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
default:
return HasBotCompletedWalkMove(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
}
return false;
return HasBotCompletedWalkMove(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
}
bool HasBotCompletedLadderMove(AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd)
bool HasBotCompletedWalkMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
{
bool bNextPointReachable = false;
if (NextMoveFlag != SAMPLE_POLYFLAGS_DISABLED)
{
bNextPointReachable = UTIL_PointIsDirectlyReachable(pBot->CollisionHullBottomLocation, NextMoveDestination);
}
return vPointOverlaps3D(MoveEnd, pBot->Edict->v.absmin, pBot->Edict->v.absmax) || (bNextPointReachable && vDist2DSq(pBot->Edict->v.origin, MoveEnd) < sqrf(GetPlayerRadius(pBot->Edict) * 2.0f));
}
bool HasBotCompletedObstacleMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
{
return vPointOverlaps3D(MoveEnd, pBot->Edict->v.absmin, pBot->Edict->v.absmax);
}
bool HasBotCompletedLadderMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
{
if (IsPlayerOnLadder(pBot->Edict)) { return false; }
return UTIL_PointIsDirectlyReachable(pBot->CollisionHullBottomLocation, MoveEnd);
if (NextMoveFlag != SAMPLE_POLYFLAGS_DISABLED)
{
if (pBot->BotNavInfo.IsOnGround)
{
if (UTIL_PointIsDirectlyReachable(pBot->CollisionHullBottomLocation, NextMoveDestination)) { return true; }
}
else
{
if (vDist2DSq(pBot->Edict->v.origin, MoveEnd) < sqrf(GetPlayerRadius(pBot->Edict)) && UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, NextMoveDestination)) { return true; }
}
}
return vPointOverlaps3D(MoveEnd, pBot->Edict->v.absmin, pBot->Edict->v.absmax);
}
bool HasBotCompletedFallMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
{
if (NextMoveFlag != SAMPLE_POLYFLAGS_DISABLED)
{
Vector ThisMoveDir = UTIL_GetVectorNormal2D(MoveEnd - MoveStart);
Vector NextMoveDir = UTIL_GetVectorNormal2D(NextMoveDestination - MoveEnd);
float MoveDot = UTIL_GetDotProduct2D(ThisMoveDir, NextMoveDir);
if (MoveDot > 0.0f)
{
if (UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, NextMoveDestination)
&& UTIL_QuickTrace(pBot->Edict, pBot->Edict->v.origin, NextMoveDestination)
&& fabsf(pBot->CollisionHullBottomLocation.z - MoveEnd.z) < 100.0f) { return true; }
}
}
return vPointOverlaps3D(MoveEnd, pBot->Edict->v.absmin, pBot->Edict->v.absmax);
}
bool HasBotCompletedClimbMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, float RequiredClimbHeight, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
{
Vector PositionInMove = vClosestPointOnLine2D(MoveStart, MoveEnd, pBot->Edict->v.origin);
if (NextMoveFlag != SAMPLE_POLYFLAGS_DISABLED)
{
Vector ThisMoveDir = UTIL_GetVectorNormal2D(MoveEnd - MoveStart);
Vector NextMoveDir = UTIL_GetVectorNormal2D(NextMoveDestination - MoveEnd);
float MoveDot = UTIL_GetDotProduct2D(ThisMoveDir, NextMoveDir);
if (MoveDot > 0.0f)
{
if (pBot->Edict->v.origin.z >= RequiredClimbHeight && !pBot->BotNavInfo.IsOnGround)
{
if (UTIL_QuickTrace(pBot->Edict, pBot->Edict->v.origin, NextMoveDestination)
&& fabsf(pBot->CollisionHullBottomLocation.z - MoveEnd.z) < 100.0f)
{
return true;
}
}
}
}
if (!vEquals2D(PositionInMove, MoveEnd, 2.0f)) { return false; }
return vPointOverlaps3D(MoveEnd, pBot->Edict->v.absmin, pBot->Edict->v.absmax) && UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, NextMoveDestination);
}
bool HasBotCompletedJumpMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
{
Vector PositionInMove = vClosestPointOnLine2D(MoveStart, MoveEnd, pBot->Edict->v.origin);
if (!vEquals2D(PositionInMove, MoveEnd, 2.0f)) { return false; }
if (NextMoveFlag != SAMPLE_POLYFLAGS_DISABLED)
{
Vector ThisMoveDir = UTIL_GetVectorNormal2D(MoveEnd - MoveStart);
Vector NextMoveDir = UTIL_GetVectorNormal2D(NextMoveDestination - MoveEnd);
float MoveDot = UTIL_GetDotProduct2D(ThisMoveDir, NextMoveDir);
if (MoveDot >= 0.0f)
{
Vector HullTraceEnd = MoveEnd;
HullTraceEnd.z = pBot->Edict->v.origin.z;
if (UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, NextMoveDestination)
&& UTIL_QuickHullTrace(pBot->Edict, pBot->Edict->v.origin, HullTraceEnd, head_hull, false)
&& fabsf(pBot->CollisionHullBottomLocation.z - MoveEnd.z) < 100.0f)
{
return true;
}
}
}
return vPointOverlaps3D(MoveEnd, pBot->Edict->v.absmin, pBot->Edict->v.absmax);
}
bool HasBotCompletedPhaseGateMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
{
return vPointOverlaps3D(MoveEnd, pBot->Edict->v.absmin, pBot->Edict->v.absmax);
}
bool HasBotCompletedLiftMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
{
return vPointOverlaps3D(MoveEnd, pBot->Edict->v.absmin, pBot->Edict->v.absmax);
}
void CheckAndHandleDoorObstruction(AvHAIPlayer* pBot)
@ -3249,6 +3246,29 @@ void LadderMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoin
if (IsPlayerSkulk(pBot->Edict))
{
pBot->Button &= ~IN_DUCK;
edict_t* Ladder = UTIL_GetNearestLadderAtPoint(StartPoint);
if (!FNullEnt(Ladder))
{
Vector LadderStart = Ladder->v.absmin;
Vector LadderEnd = Ladder->v.absmax;
LadderEnd.z = LadderStart.z;
// Basically, if we're directly climbing up or down the ladder, treat it like a wall. The below test will be false if the ladder is to one side
if (vIntersects2D(StartPoint, EndPoint, LadderStart, LadderEnd))
{
if (bIsGoingUpLadder)
{
WallClimbMove(pBot, StartPoint, EndPoint, RequiredClimbHeight);
}
else
{
FallMove(pBot, StartPoint, EndPoint);
}
return;
}
}
}
if (IsPlayerOnLadder(pBot->Edict))
@ -3489,6 +3509,12 @@ void LadderMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoin
{
LookLocation = EndPoint + (CurrentLadderNormal * 100.0f);
}
// We're close enough to the end that we can jump off the ladder
if (UTIL_QuickTrace(pBot->Edict, pBot->Edict->v.origin, EndPoint) && (pBot->CollisionHullBottomLocation.z - EndPoint.z < 100.0f))
{
BotJump(pBot);
}
}
else
{
@ -3504,6 +3530,16 @@ void LadderMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoin
// We're not yet on the ladder
if (!pBot->BotNavInfo.IsOnGround && !bIsGoingUpLadder)
{
// We're close enough to the end that we can jump off the ladder
if (UTIL_QuickTrace(pBot->Edict, pBot->Edict->v.origin, EndPoint) && (pBot->CollisionHullBottomLocation.z - EndPoint.z < 100.0f))
{
pBot->desiredMovementDir = UTIL_GetVectorNormal2D(EndPoint - pBot->Edict->v.origin);
return;
}
}
// If we're going down the ladder and are approaching it, just keep moving towards it
if ((pBot->BotNavInfo.IsOnGround || IsPlayerSkulk(pBot->Edict)) && !bIsGoingUpLadder)
{
@ -3888,113 +3924,156 @@ void PhaseGateMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndP
bool IsBotOffPath(const AvHAIPlayer* pBot)
{
// Can't be off the path if we don't have one...
if (pBot->BotNavInfo.CurrentPath.size() == 0 || pBot->BotNavInfo.CurrentPathPoint == pBot->BotNavInfo.CurrentPath.end()) { return false; }
if (pBot->BotNavInfo.CurrentPathPoint->flag == SAMPLE_POLYFLAGS_LIFT) { return false; }
// If we're trying to use a phase gate, then we're fine as long as there is a phase gate within reach at the start and end teleport points
if (pBot->BotNavInfo.CurrentPathPoint->flag == SAMPLE_POLYFLAGS_TEAM1PHASEGATE || pBot->BotNavInfo.CurrentPathPoint->flag == SAMPLE_POLYFLAGS_TEAM2PHASEGATE)
if (pBot->BotNavInfo.CurrentPath.size() == 0 || pBot->BotNavInfo.CurrentPathPoint == pBot->BotNavInfo.CurrentPath.end())
{
DeployableSearchFilter PGFilter;
PGFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE;
PGFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(2.0f);
PGFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
PGFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
PGFilter.DeployableTeam = (AvHTeamNumber)pBot->Edict->v.team;
// The phase gate we're meant to be using isn't here any more!
if (!AITAC_DeployableExistsAtLocation(pBot->Edict->v.origin, &PGFilter))
{
return true;
}
// The phase gate we're meant to be warping to isn't there any more!
if (!AITAC_DeployableExistsAtLocation(pBot->BotNavInfo.CurrentPathPoint->Location, &PGFilter))
{
return true;
}
return false;
return true;
}
edict_t* pEdict = pBot->Edict;
SamplePolyFlags CurrentNavFlag = (SamplePolyFlags)pBot->BotNavInfo.CurrentPathPoint->flag;
Vector MoveFrom = pBot->BotNavInfo.CurrentPathPoint->FromLocation;
Vector MoveTo = pBot->BotNavInfo.CurrentPathPoint->Location;
Vector MoveFrom = pBot->CurrentFloorPosition;
float PlayerRadiusSq = sqrf(GetPlayerRadius(pBot->Player));
float PlayerHeight = GetPlayerHeight(pBot->Edict, false);
Vector NextMoveLocation = ZERO_VECTOR;
SamplePolyFlags NextMoveFlag = SAMPLE_POLYFLAGS_DISABLED;
Vector vForward = UTIL_GetVectorNormal2D(MoveTo - MoveFrom);
vector<bot_path_node>::iterator NextPathPoint = next(pBot->BotNavInfo.CurrentPathPoint);
Vector PointOnPath = vClosestPointOnLine2D(MoveFrom, MoveTo, pBot->Edict->v.origin);
if (pBot->BotNavInfo.CurrentPathPoint->flag == SAMPLE_POLYFLAGS_WALLCLIMB)
if (NextPathPoint != pBot->BotNavInfo.CurrentPath.end())
{
return (vEquals(PointOnPath, MoveTo, 2.0f) && !IsPlayerClimbingWall(pBot->Edict) && pBot->CollisionHullTopLocation.z < MoveTo.z);
NextMoveLocation = NextPathPoint->Location;
NextMoveFlag = (SamplePolyFlags)NextPathPoint->flag;
}
// Give us a chance to land before deciding we're off the path
if (!pBot->BotNavInfo.IsOnGround) { return false; }
if (pBot->BotNavInfo.CurrentPathPoint->flag == SAMPLE_POLYFLAGS_WALK)
switch (CurrentNavFlag)
{
if (vDist2DSq(pBot->Edict->v.origin, PointOnPath) > sqrf(100.0f)) { return true; }
bool bAtMoveStart = vEquals(PointOnPath, MoveFrom, GetPlayerRadius(pBot->Player));
// If we're on the from or to move points, but the height is significantly different, we must be under or over the path somehow
if (bAtMoveStart && fabs(pBot->CurrentFloorPosition.z - MoveFrom.z) > PlayerHeight)
{
return true;
}
bool bAtMoveEnd = vEquals(PointOnPath, MoveTo, GetPlayerRadius(pBot->Player));
if (bAtMoveEnd && fabsf(pBot->CurrentFloorPosition.z - MoveTo.z) > PlayerHeight)
{
return true;
}
float MaxDist = (bAtMoveStart || bAtMoveEnd) ? 50.0f : 200.0f;
if (vDistanceFromLine2D(MoveFrom, MoveTo, pBot->CurrentFloorPosition) > MaxDist)
{
return true;
}
return false;
case SAMPLE_POLYFLAGS_WALK:
return IsBotOffWalkNode(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
case SAMPLE_POLYFLAGS_WELD:
case SAMPLE_POLYFLAGS_DOOR:
case SAMPLE_POLYFLAGS_TEAM1STRUCTURE:
case SAMPLE_POLYFLAGS_TEAM2STRUCTURE:
return IsBotOffObstacleNode(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
case SAMPLE_POLYFLAGS_LADDER:
return IsBotOffLadderNode(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
case SAMPLE_POLYFLAGS_FALL:
return IsBotOffFallNode(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
case SAMPLE_POLYFLAGS_WALLCLIMB:
return IsBotOffClimbNode(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
case SAMPLE_POLYFLAGS_JUMP:
case SAMPLE_POLYFLAGS_DUCKJUMP:
case SAMPLE_POLYFLAGS_BLOCKED:
return IsBotOffJumpNode(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
case SAMPLE_POLYFLAGS_TEAM1PHASEGATE:
case SAMPLE_POLYFLAGS_TEAM2PHASEGATE:
return IsBotOffPhaseGateNode(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
case SAMPLE_POLYFLAGS_LIFT:
return IsBotOffLiftNode(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
default:
return IsBotOffWalkNode(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
}
if (pBot->BotNavInfo.CurrentPathPoint->flag == SAMPLE_POLYFLAGS_JUMP)
return IsBotOffWalkNode(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
}
bool IsBotOffLadderNode(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
{
if (!IsPlayerOnLadder(pBot->Edict))
{
Vector ExactJumpTarget = UTIL_GetGroundLocation(MoveTo);
if (IsPlayerClimbingWall(pBot->Edict)) { return true; }
if (pBot->BotNavInfo.IsOnGround && (ExactJumpTarget.z - pBot->CurrentFloorPosition.z) > max_player_jump_height)
if (pBot->BotNavInfo.IsOnGround)
{
return true;
if (!UTIL_PointIsDirectlyReachable(GetPlayerBottomOfCollisionHull(pBot->Edict), MoveStart) && !UTIL_PointIsDirectlyReachable(GetPlayerBottomOfCollisionHull(pBot->Edict), MoveEnd)) { return true; }
}
return false;
}
if (pBot->BotNavInfo.CurrentPathPoint->flag == SAMPLE_POLYFLAGS_FALL)
{
if (vEquals(PointOnPath, MoveTo, 2.0f) && fabs(pBot->CurrentFloorPosition.z - MoveTo.z) > PlayerHeight)
{
return true;
}
return false;
}
return false;
}
bool IsBotOffWalkNode(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
{
if (!pBot->BotNavInfo.IsOnGround) { return false; }
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 (vEquals2D(NearestPointOnLine, MoveStart) && !UTIL_PointIsDirectlyReachable(pBot->CollisionHullBottomLocation, MoveStart)) { return true; }
if (vEquals2D(NearestPointOnLine, MoveEnd) && !UTIL_PointIsDirectlyReachable(pBot->CollisionHullBottomLocation, MoveEnd)) { return true; }
return false;
}
bool IsBotOffFallNode(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
{
if (!pBot->BotNavInfo.IsOnGround) { return false; }
Vector NearestPointOnLine = vClosestPointOnLine2D(MoveStart, MoveEnd, pBot->Edict->v.origin);
if (!UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, MoveStart) && !UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, MoveEnd)) { return true; }
return false;
}
bool IsBotOffClimbNode(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
{
if (pBot->BotNavInfo.IsOnGround)
{
return (!UTIL_PointIsDirectlyReachable(GetPlayerBottomOfCollisionHull(pBot->Edict), MoveStart) && !UTIL_PointIsDirectlyReachable(GetPlayerBottomOfCollisionHull(pBot->Edict), MoveEnd));
}
Vector ClosestPointOnLine = vClosestPointOnLine2D(MoveStart, MoveEnd, pBot->Edict->v.origin);
return vDist2DSq(pBot->Edict->v.origin, ClosestPointOnLine) > sqrf(GetPlayerRadius(pBot->Edict) * 3.0f);
}
bool IsBotOffJumpNode(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
{
if (!pBot->BotNavInfo.IsOnGround) { return false; }
Vector ClosestPointOnLine = vClosestPointOnLine2D(MoveStart, MoveEnd, pBot->Edict->v.origin);
if (vEquals2D(ClosestPointOnLine, MoveStart) || vEquals2D(ClosestPointOnLine, MoveEnd))
{
return (!UTIL_PointIsDirectlyReachable(GetPlayerBottomOfCollisionHull(pBot->Edict), MoveStart) && !UTIL_PointIsDirectlyReachable(GetPlayerBottomOfCollisionHull(pBot->Edict), MoveEnd));
}
return vDist2DSq(pBot->Edict->v.origin, ClosestPointOnLine) > sqrf(GetPlayerRadius(pBot->Edict) * 2.0f);
}
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; }
DeployableSearchFilter PGFilter;
PGFilter.DeployableTeam = pBot->Player->GetTeam();
PGFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
PGFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
PGFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(2.0f);
bool StartPGExists = AITAC_DeployableExistsAtLocation(MoveStart, &PGFilter);
if (!StartPGExists) { return true; }
bool EndPGExists = AITAC_DeployableExistsAtLocation(MoveEnd, &PGFilter);
if (!EndPGExists) { return true; }
return false;
}
bool IsBotOffLiftNode(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
{
return false;
}
bool IsBotOffObstacleNode(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
{
return IsBotOffJumpNode(pBot, MoveStart, MoveEnd, NextMoveDestination, NextMoveFlag);
}
void BlinkClimbMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint, float RequiredClimbHeight)
{
edict_t* pEdict = pBot->Edict;
@ -4025,6 +4104,16 @@ void BlinkClimbMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector End
// Only blink if we're below the target climb height
if (pEdict->v.origin.z < RequiredClimbHeight)
{
float HeightToClimb = (fabsf(pEdict->v.origin.z - RequiredClimbHeight));
if (HeightToClimb > (GetPlayerHeight(pBot->Edict, false) * 2.0f))
{
if (GetPlayerEnergy(pBot->Edict) < 0.15f && pBot->BotNavInfo.IsOnGround)
{
return;
}
}
Vector CurrVelocity = UTIL_GetVectorNormal2D(pBot->Edict->v.velocity);
float Dot = UTIL_GetDotProduct2D(MoveDir, CurrVelocity);
@ -4043,6 +4132,11 @@ void BlinkClimbMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector End
if (pBot->Edict->v.velocity.z < DesiredZVelocity || pBot->Edict->v.velocity.z < 300.0f)
{
// We're going to cheat and give the bot the necessary energy to make the move. Better the fade cheats a bit than gets stuck somewhere
if (GetPlayerEnergy(pBot->Edict) < 0.1f)
{
pBot->Player->Energize(0.1f);
}
BotMoveLookAt(pBot, EndPoint + Vector(0.0f, 0.0f, 100.0f));
pBot->Button |= IN_ATTACK2;
}
@ -5759,9 +5853,9 @@ void SkipAheadInFlightPath(AvHAIPlayer* pBot)
void BotFollowFlightPath(AvHAIPlayer* pBot)
{
if (pBot->BotNavInfo.CurrentPath.size() == 0)
if (pBot->BotNavInfo.CurrentPath.size() == 0 || pBot->BotNavInfo.CurrentPathPoint == pBot->BotNavInfo.CurrentPath.end())
{
ClearBotStuck(pBot);
ClearBotPath(pBot);
return;
}
@ -5800,7 +5894,9 @@ void BotFollowFlightPath(AvHAIPlayer* pBot)
SkipAheadInFlightPath(pBot);
}
if (!UTIL_QuickTrace(pBot->Edict, pBot->Edict->v.origin, BotNavInfo->CurrentPathPoint->Location + Vector(0.0f, 0.0f, 5.0f)))
ClosestPointToPath = vClosestPointOnLine(BotNavInfo->CurrentPathPoint->FromLocation, BotNavInfo->CurrentPathPoint->Location, pEdict->v.origin);
if (vDist3DSq(pBot->Edict->v.origin, ClosestPointToPath) > sqrf(GetPlayerRadius(pBot->Edict) * 3.0f))
{
ClearBotPath(pBot);
return;
@ -5811,9 +5907,7 @@ void BotFollowFlightPath(AvHAIPlayer* pBot)
unsigned char CurrentMoveArea = BotNavInfo->CurrentPathPoint->area;
unsigned char NextMoveArea = (next(BotNavInfo->CurrentPathPoint) != BotNavInfo->CurrentPath.end()) ? next(BotNavInfo->CurrentPathPoint)->area : CurrentMoveArea;
ClosestPointToPath = vClosestPointOnLine(MoveFrom, CurrentMoveDest, pEdict->v.origin);
Vector MoveDir = UTIL_GetVectorNormal(CurrentMoveDest - MoveFrom);
float CurrentSpeed = vSize3D(pEdict->v.velocity);
@ -5837,7 +5931,7 @@ void BotFollowFlightPath(AvHAIPlayer* pBot)
if (!bMustHugGround || MoveFrom.z <= CurrentMoveDest.z)
{
if (CurrentSpeed < sqrf(500.f))
if (CurrentSpeed < 500.f && GetPlayerEnergy(pBot->Edict) > 0.1f)
{
if (!(pEdict->v.oldbuttons & IN_JUMP))
{
@ -5907,6 +6001,13 @@ void BotFollowSwimPath(AvHAIPlayer* pBot)
ClearBotPath(pBot);
return;
}
else
{
if (pBot->BotNavInfo.CurrentPathPoint->flag == SAMPLE_POLYFLAGS_WALK)
{
pBot->BotNavInfo.CurrentPathPoint->FromLocation = pBot->CurrentFloorPosition;
}
}
}
bool TargetPointIsInWater = (UTIL_PointContents(BotNavInfo->CurrentPathPoint->Location) == CONTENTS_WATER || UTIL_PointContents(BotNavInfo->CurrentPathPoint->Location) == CONTENTS_SLIME);
@ -5997,6 +6098,17 @@ void BotFollowPath(AvHAIPlayer* pBot)
}
}
if (IsPlayerStandingOnPlayer(pBot->Edict) && pBot->BotNavInfo.CurrentPathPoint->flag != SAMPLE_POLYFLAGS_WALLCLIMB && pBot->BotNavInfo.CurrentPathPoint->flag != SAMPLE_POLYFLAGS_LADDER)
{
if (pBot->Edict->v.groundentity->v.velocity.Length2D() > 10.0f)
{
pBot->desiredMovementDir = UTIL_GetVectorNormal2D(-pBot->Edict->v.groundentity->v.velocity);
return;
}
MoveToWithoutNav(pBot, pBot->BotNavInfo.CurrentPathPoint->Location);
return;
}
if (IsBotOffPath(pBot))
{
pBot->BotNavInfo.StuckInfo.bPathFollowFailed = true;
@ -6440,9 +6552,6 @@ void HandlePlayerAvoidance(AvHAIPlayer* pBot, const Vector MoveDestination)
float TraceLength = OtherPersonDistFromLine + (fmaxf(MyRadius, OtherPlayerRadius) * 2.0f);
UTIL_DrawLine(INDEXENT(1), pBot->Edict->v.origin, pBot->Edict->v.origin + (PreferredMoveDir * TraceLength), 0, 128, 0);
UTIL_DrawLine(INDEXENT(1), pBot->Edict->v.origin, pBot->Edict->v.origin - (PreferredMoveDir * TraceLength), 255, 0, 0);
// First see if we have enough room to move in our preferred avoidance direction
if (UTIL_TraceNav(pBot->BotNavInfo.NavProfile, BotLocation, BotLocation + (PreferredMoveDir * TraceLength), 0.0f))
@ -6458,17 +6567,14 @@ void HandlePlayerAvoidance(AvHAIPlayer* pBot, const Vector MoveDestination)
return;
}
bool bCanBackUp = UTIL_TraceNav(pBot->BotNavInfo.NavProfile, BotLocation, BotLocation - (MoveDir * (MyRadius * 2.0f)), 0.0f);
if (!bCanBackUp)
// If we have a point we can go back to, and we can reach it, then go for it. Otherwise, keep pushing on and hope the other guy moves
if (!vIsZero(pBot->BotNavInfo.LastOpenLocation))
{
bCanBackUp = UTIL_QuickHullTrace(pBot->Edict, pBot->Edict->v.origin, pBot->Edict->v.origin - (MoveDir * (MyRadius * 2.0f)), head_hull);
}
// Back up since we can't go either side, but only if we can back up. Otherwise, we push forward and demand the OTHER guy back up
if (UTIL_GetDotProduct2D(MoveDir, OtherMoveDir) < 0.0f && bCanBackUp)
{
pBot->desiredMovementDir = MoveDir * -1.0f;
if (UTIL_PointIsReachable(pBot->BotNavInfo.NavProfile, pBot->Edict->v.origin, pBot->BotNavInfo.LastOpenLocation, GetPlayerRadius(pBot->Edict)))
{
NAV_SetMoveMovementTask(pBot, pBot->BotNavInfo.LastOpenLocation, nullptr);
return;
}
}
}
}
@ -6574,7 +6680,6 @@ float UTIL_FindZHeightForWallClimb(const Vector ClimbStart, const Vector ClimbEn
Vector CurrTraceStart = StartTrace;
UTIL_TraceHull(StartTrace, EndTrace, ignore_monsters, HullNum, nullptr, &hit);
if (hit.flFraction >= 1.0f && !hit.fAllSolid && !hit.fStartSolid)
@ -6701,7 +6806,7 @@ void BotMovementInputs(AvHAIPlayer* pBot)
pBot->Button |= IN_MOVELEFT;
}
if (pBot->BotNavInfo.CurrentPath.size() == 0 || pBot->BotNavInfo.CurrentPathPoint->flag != SAMPLE_POLYFLAGS_LADDER)
if (pBot->BotNavInfo.CurrentPath.size() == 0 || pBot->BotNavInfo.CurrentPathPoint == pBot->BotNavInfo.CurrentPath.end() || pBot->BotNavInfo.CurrentPathPoint->flag != SAMPLE_POLYFLAGS_LADDER)
{
if (pBot->Player->IsOnLadder())
{

View File

@ -209,8 +209,25 @@ bool BotIsAtLocation(const AvHAIPlayer* pBot, const Vector Destination);
void NewMove(AvHAIPlayer* pBot);
// Returns true if the bot has completed the current movement along their path
bool HasBotReachedPathPoint(const AvHAIPlayer* pBot);
bool HasBotCompletedLadderMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag);
bool HasBotCompletedWalkMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag);
bool HasBotCompletedFallMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag);
bool HasBotCompletedClimbMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, float RequiredClimbHeight, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag);
bool HasBotCompletedJumpMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag);
bool HasBotCompletedPhaseGateMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag);
bool HasBotCompletedLiftMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag);
bool HasBotCompletedObstacleMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag);
// Returns true if the bot is considered to have strayed off the path (e.g. missed a jump and fallen)
bool IsBotOffPath(const AvHAIPlayer* pBot);
bool IsBotOffLadderNode(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag);
bool IsBotOffWalkNode(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag);
bool IsBotOffFallNode(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag);
bool IsBotOffClimbNode(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag);
bool IsBotOffJumpNode(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag);
bool IsBotOffPhaseGateNode(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag);
bool IsBotOffLiftNode(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag);
bool IsBotOffObstacleNode(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag);
// Called by NewMove, determines the movement direction and inputs required to walk/crouch between start and end points
void GroundMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint);
@ -263,7 +280,7 @@ void ClearBotMovement(AvHAIPlayer* pBot);
bool IsBotStuck(AvHAIPlayer* pBot, const Vector MoveDestination);
// Called every bot frame (default is 60fps). Ensures the tile cache is updated after obstacles are placed
void UTIL_UpdateTileCache();
bool UTIL_UpdateTileCache();
void AIDEBUG_DrawOffMeshConnections(float DrawTime);

View File

@ -1628,6 +1628,49 @@ void StartNewBotFrame(AvHAIPlayer* pBot)
if (UTIL_PointIsReachable(pBot->BotNavInfo.NavProfile, AITAC_GetTeamStartingLocation(pBot->Player->GetTeam()), pBot->CurrentFloorPosition, 16.0f))
{
pBot->BotNavInfo.LastNavMeshPosition = pBot->CurrentFloorPosition;
if (pBot->BotNavInfo.IsOnGround)
{
Vector ForwardVector = UTIL_GetForwardVector2D(pBot->Edict->v.angles);
Vector RightVector = UTIL_GetCrossProduct(ForwardVector, UP_VECTOR);
Vector TraceEndPoints[4];
TraceEndPoints[0] = pBot->Edict->v.origin + (ForwardVector * (GetPlayerRadius(pBot->Edict) * 2.0f));
TraceEndPoints[1] = pBot->Edict->v.origin - (ForwardVector * (GetPlayerRadius(pBot->Edict) * 2.0f));
TraceEndPoints[2] = pBot->Edict->v.origin + (RightVector * (GetPlayerRadius(pBot->Edict) * 2.0f));
TraceEndPoints[3] = pBot->Edict->v.origin - (RightVector * (GetPlayerRadius(pBot->Edict) * 2.0f));
int NumDirectionsChecked = 0;
bool bHasRoom = true;
while (NumDirectionsChecked < 4 && bHasRoom)
{
Vector EndTrace = TraceEndPoints[NumDirectionsChecked];
Vector EndNavTrace = EndTrace;
EndNavTrace.z = pBot->CollisionHullBottomLocation.z;
if (!UTIL_QuickTrace(pBot->Edict, pBot->Edict->v.origin, EndTrace))
{
bHasRoom = false;
break;
}
if (!UTIL_TraceNav(pBot->BotNavInfo.NavProfile, pBot->CollisionHullBottomLocation, EndNavTrace, 0.0f))
{
bHasRoom = false;
break;
}
NumDirectionsChecked++;
}
if (bHasRoom)
{
pBot->BotNavInfo.LastOpenLocation = pBot->CurrentFloorPosition;
}
}
}
pBot->BotNavInfo.LastNavMeshCheckPosition = pBot->CurrentFloorPosition;
@ -4952,7 +4995,7 @@ void TestNavThink(AvHAIPlayer* pBot)
if (!RandomNode) { return; }
Vector RandomPoint = RandomNode->Location;
RandomPoint = RandomNode->Location;
}
else
{
@ -4965,6 +5008,10 @@ void TestNavThink(AvHAIPlayer* pBot)
}
else
{
if (!vIsZero(pBot->BotNavInfo.LastNavMeshPosition))
{
MoveToWithoutNav(pBot, pBot->BotNavInfo.LastNavMeshPosition);
}
AITASK_ClearBotTask(pBot, &pBot->PrimaryBotTask);
}
}

View File

@ -7,6 +7,7 @@
#include "AvHAIWeaponHelper.h"
#include "AvHAIHelper.h"
#include "AvHAICommander.h"
#include "AvHAIPlayerUtil.h"
#include "../AvHGamerules.h"
#include "../dlls/client.h"
#include <time.h>
@ -21,6 +22,7 @@ extern cvar_t avh_botsenabled;
extern cvar_t avh_botminplayers;
extern cvar_t avh_botusemapdefaults;
extern cvar_t avh_botcommandermode;
extern cvar_t avh_botdebugmode;
float LastAIPlayerCountUpdate = 0.0f;
@ -31,9 +33,6 @@ float AIStartedTime = 0.0f; // Used to give 5-second grace period before adding
bool bHasRoundStarted = false;
bool bMapDataInitialised = false;
bool bTestNavigation = false;
bool bDroneMode = false;
float NextCommanderAllowedTimeTeamA = 0.0f;
float NextCommanderAllowedTimeTeamB = 0.0f;
@ -46,6 +45,10 @@ AvHAIPlayer* DebugAIPlayer = nullptr;
vector<bot_path_node> DebugPath;
bool bPlayerSpawned = false;
float CountdownStartedTime = 0.0f;
string BotNames[MAX_PLAYERS] = { "MrRobot",
"Wall-E",
"BeepBoop",
@ -119,7 +122,7 @@ 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; }
if (gpGlobals->time - AIStartedTime < AI_GRACE_PERIOD) { return; }
if (!AIMGR_ShouldStartPlayerBalancing()) { return; }
// If game has ended, kick bots that have dropped back to the ready room
if (GetGameRules()->GetVictoryTeam() != TEAM_IND)
@ -634,10 +637,42 @@ void AIMGR_UpdateAIPlayers()
if (bot->BotNavInfo.CurrentPath.size() > 0 && bot->BotNavInfo.CurrentPathPoint != bot->BotNavInfo.CurrentPath.end())
{
UTIL_DrawLine(INDEXENT(1), bot->Edict->v.origin, bot->BotNavInfo.CurrentPathPoint->Location, 0, 255, 255);
UTIL_DrawLine(INDEXENT(1), bot->Edict->v.origin, bot->BotNavInfo.CurrentPathPoint->FromLocation, 255, 0, 0);
UTIL_DrawLine(INDEXENT(1), bot->Edict->v.origin, bot->BotNavInfo.CurrentPathPoint->Location, 0, 128, 0);
}
}
if (!vIsZero(DebugVector1) && !vIsZero(DebugVector2))
{
UTIL_DrawLine(INDEXENT(1), DebugVector1, DebugVector2);
edict_t* PlayerEdict = INDEXENT(1);
bool bOnGround = (INDEXENT(1)->v.flags & FL_ONGROUND) || IsPlayerOnLadder(PlayerEdict);
bool Result = false;
if (!IsPlayerOnLadder(PlayerEdict))
{
if (IsPlayerClimbingWall(PlayerEdict)) { Result = true; }
if (bOnGround)
{
if (!UTIL_PointIsDirectlyReachable(GetPlayerBottomOfCollisionHull(PlayerEdict), DebugVector1) && !UTIL_PointIsDirectlyReachable(GetPlayerBottomOfCollisionHull(PlayerEdict), DebugVector2)) { Result = true; }
}
}
if (Result)
{
UTIL_DrawHUDText(PlayerEdict, 0, 0.1f, 0.1f, 255, 255, 255, "TRUE");
}
else
{
UTIL_DrawHUDText(PlayerEdict, 0, 0.1f, 0.1f, 255, 255, 255, "FALSE");
}
}
if (bHasRoundStarted)
{
AvHTeamNumber TeamANumber = GetGameRules()->GetTeamANumber();
@ -674,11 +709,11 @@ void AIMGR_UpdateAIPlayers()
UpdateBotChat(bot);
if (bDroneMode)
if (avh_botdebugmode.value == 1)
{
DroneThink(bot);
}
else if (bTestNavigation)
else if (avh_botdebugmode.value == 2)
{
TestNavThink(bot);
}
@ -902,12 +937,19 @@ void AIMGR_ResetRound()
UTIL_UpdateDoors(true);
UTIL_UpdateTileCache();
bool bTileCacheFullyUpdated = UTIL_UpdateTileCache();
while (!bTileCacheFullyUpdated)
{
bTileCacheFullyUpdated = UTIL_UpdateTileCache();
}
ALERT(at_console, "AI Manager Reset Round\n");
bHasRoundStarted = false;
bMapDataInitialised = true;
CountdownStartedTime = 0.0f;
}
void AIMGR_RoundStarted()
@ -936,6 +978,8 @@ void AIMGR_RoundStarted()
{
AIMGR_SetCommanderAllowedTime(TeamBNumber, 0.0f);
}
AITAC_OnNavMeshModified();
}
void AIMGR_SetCommanderAllowedTime(AvHTeamNumber Team, float NewValue)
@ -1009,6 +1053,8 @@ void AIMGR_NewMap()
AIMGR_BotPrecache();
bHasRoundStarted = false;
bPlayerSpawned = false;
}
AvHAIPlayer* AIMGR_GetAICommander(AvHTeamNumber Team)
@ -1118,9 +1164,19 @@ vector<AvHPlayer*> AIMGR_GetNonAIPlayersOnTeam(AvHTeamNumber Team)
return TeamPlayers;
}
bool AIMGR_ShouldStartPlayerBalancing()
{
return (bPlayerSpawned && gpGlobals->time - AIStartedTime > AI_GRACE_PERIOD) || (gpGlobals->time - AIStartedTime > AI_MAX_START_TIMEOUT);
}
void AIMGR_UpdateAIMapData()
{
if (bMapDataInitialised && gpGlobals->time - AIStartedTime > AI_GRACE_PERIOD)
if (GetGameRules()->GetCountdownStarted() && CountdownStartedTime == 0.0f)
{
CountdownStartedTime = gpGlobals->time;
}
if (bMapDataInitialised && (CountdownStartedTime > 0.0f && (gpGlobals->time - 1.0f) > CountdownStartedTime))
{
AITAC_UpdateMapAIData();
UTIL_UpdateTileCache();
@ -1158,34 +1214,6 @@ void AIMGR_SetDebugAIPlayer(edict_t* AIPlayer)
DebugAIPlayer = nullptr;
}
void AIMGR_SetTestNavMode(bool bNewValue)
{
if (bNewValue)
{
bDroneMode = false;
}
bTestNavigation = bNewValue;
}
void AIMGR_SetDroneMode(bool bNewValue)
{
if (bNewValue)
{
bTestNavigation = false;
}
bDroneMode = bNewValue;
}
bool AIMGR_GetTestNavMode()
{
return bTestNavigation;
}
bool AIMGR_GetDroneMode()
{
return bTestNavigation;
}
void AIMGR_ReceiveCommanderRequest(AvHTeamNumber Team, edict_t* Requestor, const char* Request)
{
AvHTeam* TeamRef = GetGameRules()->GetTeam(Team);
@ -1201,4 +1229,14 @@ void AIMGR_ReceiveCommanderRequest(AvHTeamNumber Team, edict_t* Requestor, const
{
AICOMM_ReceiveChatRequest(BotCommander, Requestor, Request);
}
}
void AIMGR_ClientConnected(edict_t* NewClient)
{
}
void AIMGR_PlayerSpawned()
{
bPlayerSpawned = true;
}

View File

@ -6,8 +6,10 @@
// Max rate bot can run its logic, default is 1/60th second. WARNING: Increasing the rate past 100hz causes bots to move and turn slowly due to GoldSrc limits!
static const double BOT_MIN_FRAME_TIME = (1.0 / 60.0);
// At map load / map restart, how long to wait before starting to add bots
// Once the first human player has joined the game, how long to wait before adding bots
static const float AI_GRACE_PERIOD = 5.0f;
// Max time to wait before spawning players if none connect (e.g. empty dedicated server)
static const float AI_MAX_START_TIMEOUT = 20.0f;
void AIMGR_BotPrecache();
@ -46,6 +48,7 @@ int AIMGR_GetNumAIPlayers();
int AIMGR_AIPlayerExistsOnTeam(AvHTeamNumber Team);
void AIMGR_UpdateAIMapData();
bool AIMGR_ShouldStartPlayerBalancing();
AvHAICommanderMode AIMGR_GetCommanderMode();
@ -90,12 +93,9 @@ void AIMGR_ClearBotData();
AvHAIPlayer* AIMGR_GetDebugAIPlayer();
void AIMGR_SetDebugAIPlayer(edict_t* AIPlayer);
void AIMGR_SetTestNavMode(bool bNewValue);
bool AIMGR_GetTestNavMode();
void AIMGR_SetDroneMode(bool bNewValue);
bool AIMGR_GetDroneMode();
void AIMGR_ReceiveCommanderRequest(AvHTeamNumber Team, edict_t* Requestor, const char* Request);
void AIMGR_ClientConnected(edict_t* NewClient);
void AIMGR_PlayerSpawned();
#endif

View File

@ -145,17 +145,10 @@ bool IsPlayerOnLadder(const edict_t* Player)
if (FNullEnt(NearestLadder)) { return false; }
if (vPointOverlaps3D(Player->v.origin, NearestLadder->v.absmin, NearestLadder->v.absmax)) { return true; }
Vector NearestPointOnLadder = UTIL_GetClosestPointOnEntityToLocation(Player->v.origin, NearestLadder);
Vector NearestPointOnPlayer = UTIL_GetClosestPointOnEntityToLocation(NearestPointOnLadder, Player);
trace_t TraceResult;
Vector TraceStart = Player->v.origin;
Vector TraceEnd = UTIL_GetCentreOfEntity(NearestLadder);
TraceEnd.z = TraceStart.z;
NS_TraceLine(TraceStart, TraceEnd, 1, PM_WORLD_ONLY, -1, true, TraceResult);
return (TraceResult.fraction < 0.01f);
return (vDist2DSq(NearestPointOnLadder, NearestPointOnPlayer) <= sqrf(4.0f));
}
return (Player->v.movetype == MOVETYPE_FLY);
@ -784,6 +777,11 @@ bool IsPlayerReloading(const AvHPlayer* Player)
return (theBasePlayerWeapon->m_fInReload > 0 || theBasePlayerWeapon->m_fInSpecialReload > 0);
}
bool IsPlayerStandingOnPlayer(const edict_t* Player)
{
return (IsEdictPlayer(Player->v.groundentity));
}
AvHUser3 GetPlayerActiveClass(const AvHPlayer* Player)
{
if (Player->pev->iuser3 == AVH_USER3_ALIEN_EMBRYO) // If player is gestating...

View File

@ -150,6 +150,8 @@ float GetPlayerCloakAmount(const edict_t* Player);
bool IsPlayerReloading(const AvHPlayer* Player);
bool IsPlayerStandingOnPlayer(const edict_t* Player);
// If the player is gestating, will return the target evolution. Otherwise, returns the iuser3
AvHUser3 GetPlayerActiveClass(const AvHPlayer* Player);

View File

@ -47,7 +47,7 @@ float last_structure_refresh_time = 0.0f;
float last_item_refresh_time = 0.0f;
// Increments by 1 every time the structure list is refreshed. Used to detect if structures have been destroyed and no longer show up
unsigned int StructureRefreshFrame = 0;
unsigned int StructureRefreshFrame = 1;
// Increments by 1 every time the item list is refreshed. Used to detect if items have been removed from play and no longer show up
unsigned int ItemRefreshFrame = 0;
@ -850,6 +850,7 @@ Vector AITAC_GetTeamStartingLocation(AvHTeamNumber Team)
{
Vector CommChairLocation = AITAC_GetCommChairLocation(TeamANum);
TeamAStartingLocation = (!vIsZero(CommChairLocation)) ? CommChairLocation : TeamStartLocation;
TeamAStartingLocation = UTIL_ProjectPointToNavmesh(TeamAStartingLocation, GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE));
}
}
else
@ -885,12 +886,13 @@ Vector AITAC_GetTeamStartingLocation(AvHTeamNumber Team)
if (InfantryPortal)
{
TeamAStartingLocation = InfantryPortal->Location;
TeamBStartingLocation = InfantryPortal->Location;
}
else
{
Vector CommChairLocation = AITAC_GetCommChairLocation(TeamBNum);
TeamAStartingLocation = (!vIsZero(CommChairLocation)) ? CommChairLocation : TeamStartLocation;
TeamBStartingLocation = (!vIsZero(CommChairLocation)) ? CommChairLocation : TeamStartLocation;
TeamBStartingLocation = UTIL_ProjectPointToNavmesh(TeamBStartingLocation, GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE));
}
}
else
@ -1507,6 +1509,9 @@ void AITAC_CheckNavMeshModified()
void AITAC_OnNavMeshModified()
{
TeamAStartingLocation = ZERO_VECTOR;
TeamBStartingLocation = ZERO_VECTOR;
for (auto it = TeamAStructureMap.begin(); it != TeamAStructureMap.end(); it++)
{
it->second.bReachabilityMarkedDirty = true;
@ -2352,6 +2357,19 @@ void AITAC_LinkDeployedItemToAction(AvHAIPlayer* CommanderBot, const AvHAIDroppe
}
void AITAC_ClearStructureNavData()
{
for (auto& it : TeamAStructureMap)
{
AITAC_OnStructureDestroyed(&it.second);
}
for (auto& it : TeamBStructureMap)
{
AITAC_OnStructureDestroyed(&it.second);
}
}
void AITAC_ClearMapAIData()
{
UTIL_ClearLocalizations();
@ -2360,12 +2378,19 @@ void AITAC_ClearMapAIData()
AITAC_ClearHiveInfo();
AITAC_ClearStructureNavData();
while (!bTileCacheUpToDate)
{
UTIL_UpdateTileCache();
}
MarineDroppedItemMap.clear();
TeamAStructureMap.clear();
TeamBStructureMap.clear();
StructureRefreshFrame = 0;
ItemRefreshFrame = 0;
StructureRefreshFrame = 1;
ItemRefreshFrame = 1;
last_structure_refresh_time = 0.0f;
last_item_refresh_time = 0.0f;

View File

@ -88,6 +88,8 @@ void AITAC_ClearMapAIData();
// Clear out all the hive information
void AITAC_ClearHiveInfo();
void AITAC_ClearStructureNavData();
bool AITAC_AlienHiveNeedsReinforcing(const AvHAIHiveDefinition* Hive);
void AITAC_RefreshMarineItems();

View File

@ -1430,6 +1430,18 @@ BOOL AvHGamerules::ClientCommand( CBasePlayer *pPlayer, const char *pcmd )
theSuccess = true;
}
else if (FStrEq(pcmd, "setdebugvector1"))
{
AIDEBUG_SetDebugVector1(UTIL_GetFloorUnderEntity(theAvHPlayer->edict()));
theSuccess = true;
}
else if (FStrEq(pcmd, "setdebugvector2"))
{
AIDEBUG_SetDebugVector2(UTIL_GetFloorUnderEntity(theAvHPlayer->edict()));
theSuccess = true;
}
else if (FStrEq(pcmd, "cometome"))
{
vector<AvHAIPlayer*> AIPlayers = AIMGR_GetAllAIPlayers();
@ -1442,6 +1454,107 @@ BOOL AvHGamerules::ClientCommand( CBasePlayer *pPlayer, const char *pcmd )
}
}
theSuccess = true;
}
else if (FStrEq(pcmd, "showteamstarts"))
{
Vector TeamAStart = AITAC_GetTeamStartingLocation(TEAM_ONE);
Vector TeamBStart = AITAC_GetTeamStartingLocation(TEAM_TWO);
UTIL_DrawLine(INDEXENT(1), theAvHPlayer->pev->origin, TeamAStart, 20.0f, 0, 0, 255);
UTIL_DrawLine(INDEXENT(1), theAvHPlayer->pev->origin, TeamBStart, 20.0f, 0, 128, 0);
theSuccess = true;
}
else if (FStrEq(pcmd, "testoffwalknode"))
{
Vector MoveFrom = AIDEBUG_GetDebugVector1();
Vector MoveTo = AIDEBUG_GetDebugVector2();
if (!vIsZero(MoveFrom) && !vIsZero(MoveTo))
{
bool bOnGround = (theAvHPlayer->pev->flags & FL_ONGROUND) || IsPlayerOnLadder(theAvHPlayer->edict());
bool Result = false;
if (!bOnGround)
{
Result = false;
}
else
{
Vector NearestPointOnLine = vClosestPointOnLine2D(MoveFrom, MoveTo, theAvHPlayer->pev->origin);
if (vDist2DSq(theAvHPlayer->pev->origin, NearestPointOnLine) > sqrf(GetPlayerRadius(theAvHPlayer->edict()) * 3.0f)) { Result = true; }
if (vEquals2D(NearestPointOnLine, MoveFrom) && !UTIL_PointIsDirectlyReachable(GetPlayerBottomOfCollisionHull(theAvHPlayer->edict()), MoveFrom)) { Result = true; }
if (vEquals2D(NearestPointOnLine, MoveTo) && !UTIL_PointIsDirectlyReachable(GetPlayerBottomOfCollisionHull(theAvHPlayer->edict()), MoveTo)) { Result = true; }
}
if (Result)
{
UTIL_SayText("TRUE\n", theAvHPlayer);
}
else
{
UTIL_SayText("FALSE\n", theAvHPlayer);
}
}
theSuccess = true;
}
else if (FStrEq(pcmd, "testoffclimbnode"))
{
Vector MoveStart = AIDEBUG_GetDebugVector1();
Vector MoveEnd = AIDEBUG_GetDebugVector2();
if (!vIsZero(MoveStart) && !vIsZero(MoveEnd))
{
bool bOnGround = (theAvHPlayer->pev->flags & FL_ONGROUND) || IsPlayerOnLadder(theAvHPlayer->edict());
bool Result = false;
edict_t* PlayerEdict = theAvHPlayer->edict();
if (bOnGround)
{
if (!UTIL_PointIsDirectlyReachable(GetPlayerBottomOfCollisionHull(PlayerEdict), MoveStart) && !UTIL_PointIsDirectlyReachable(GetPlayerBottomOfCollisionHull(PlayerEdict), MoveEnd))
{
Result = true;
}
}
else
{
Vector ClosestPointOnLine = vClosestPointOnLine2D(MoveStart, MoveEnd, PlayerEdict->v.origin);
Result = vDist2DSq(PlayerEdict->v.origin, ClosestPointOnLine) > sqrf(GetPlayerRadius(PlayerEdict) * 3.0f);
}
if (Result)
{
UTIL_SayText("TRUE\n", theAvHPlayer);
}
else
{
UTIL_SayText("FALSE\n", theAvHPlayer);
}
}
theSuccess = true;
}
else if (FStrEq(pcmd, "amonladder"))
{
if (IsPlayerOnLadder(theAvHPlayer->edict()))
{
UTIL_SayText("TRUE\n", theAvHPlayer);
}
else
{
UTIL_SayText("FALSE\n", theAvHPlayer);
}
theSuccess = true;
}
else if( FStrEq( pcmd, kcRemoveUpgrade) )

View File

@ -392,39 +392,6 @@ AvHGamerules::AvHGamerules() : mTeamA(TEAM_ONE), mTeamB(TEAM_TWO)
AIMGR_RemoveAIPlayerFromTeam(DesiredTeam);
});
REGISTER_SERVER_FUNCTION("sv_testainavigation", []()
{
if (avh_botsenabled.value == 0)
{
return;
}
bool bNewTestValue = !AIMGR_GetTestNavMode();
AIMGR_SetTestNavMode(bNewTestValue);
});
REGISTER_SERVER_FUNCTION("sv_aidronemode", []()
{
if (avh_botsenabled.value == 0)
{
return;
}
bool bNewTestValue = !AIMGR_GetDroneMode();
AIMGR_SetDroneMode(bNewTestValue);
});
REGISTER_SERVER_FUNCTION("sv_stopaidebug", []()
{
if (avh_botsenabled.value == 0)
{
return;
}
AIMGR_SetTestNavMode(false);
AIMGR_SetDroneMode(false);
});
g_VoiceGameMgr.Init(&gVoiceHelper, gpGlobals->maxClients);
#ifdef DEBUG
@ -2193,6 +2160,8 @@ void AvHGamerules::PlayerSpawn( CBasePlayer *pPlayer )
//theAvHPlayer->InitializeFromTeam();
//this->GetTeam(theAvHPlayer->GetTeam())->AddPlayer(theAvHPlayer->entindex(), theAvHPlayer->GetRole());
}
AIMGR_PlayerSpawned();
}
void AvHGamerules::PlayerThink( CBasePlayer *pPlayer )

View File

@ -148,6 +148,7 @@ float ns_cvar_float(const cvar_t *cvar);
#define kvBotSkill "mp_botskill"
#define kvBotAutoMode "mp_botautomode"
#define kvBotCommanderMode "mp_botcommandermode"
#define kvBotDebugMode "mp_botdebugmode"
#define kvEasterEggChance "mp_eastereggchance"
#define kvUplink "mp_uplink"