mirror of
https://github.com/ENSL/NS.git
synced 2025-03-14 14:41:15 +00:00
Alien Combat
This commit is contained in:
parent
f31e2bbf1c
commit
3a1a92c505
9 changed files with 1928 additions and 119 deletions
|
@ -201,6 +201,15 @@ typedef enum _AVHAIBOTROLE
|
|||
BOT_ROLE_HARASS // Focuses on taking down enemy resource nodes and hunting the enemy
|
||||
} AvHAIBotRole;
|
||||
|
||||
typedef enum _AVHAICOMBATSTRATEGY
|
||||
{
|
||||
COMBAT_STRATEGY_IGNORE = 0, // Don't engage this enemy
|
||||
COMBAT_STRATEGY_AMBUSH, // Set up an ambush for this enemy
|
||||
COMBAT_STRATEGY_RETREAT, // Retreat and find health
|
||||
COMBAT_STRATEGY_SKIRMISH, // Maintain distance, whittle down their health from range and generally be a pain the arse
|
||||
COMBAT_STRATEGY_ATTACK // Attack the enemy
|
||||
} AvHAICombatStrategy;
|
||||
|
||||
typedef struct _OFF_MESH_CONN
|
||||
{
|
||||
unsigned int ConnectionRefs[2];
|
||||
|
@ -406,6 +415,7 @@ typedef struct _BOT_PATH_NODE
|
|||
// Represents a bot's current understanding of an enemy player's status
|
||||
typedef struct _ENEMY_STATUS
|
||||
{
|
||||
AvHPlayer* EnemyPlayer = nullptr;
|
||||
edict_t* EnemyEdict = nullptr; // Reference to the enemy player edict
|
||||
Vector LastSeenLocation = g_vecZero; // The last visibly-confirmed location of the player
|
||||
Vector LastFloorPosition = g_vecZero; // Nearest point on the floor where the enemy was (for moving towards it)
|
||||
|
@ -634,6 +644,7 @@ typedef struct AVH_AI_PLAYER
|
|||
|
||||
enemy_status TrackedEnemies[32];
|
||||
int CurrentEnemy = -1;
|
||||
AvHAICombatStrategy CurrentCombatStrategy = COMBAT_STRATEGY_ATTACK;
|
||||
edict_t* CurrentEnemyRef = nullptr;
|
||||
|
||||
AvHAIPlayerTask* CurrentTask = nullptr; // Bot's current task they're performing
|
||||
|
|
|
@ -1995,7 +1995,7 @@ bool HasBotReachedPathPoint(const AvHAIPlayer* pBot)
|
|||
|
||||
if (DirectionDot >= -0.5f)
|
||||
{
|
||||
return bAtOrPastDestination && UTIL_PointIsDirectlyReachable(pBot, pBot->CurrentFloorPosition, NextPathPoint->Location) && UTIL_QuickHullTrace(pBot->Edict, pBot->Edict->v.origin, NextPathPoint->Location + Vector(0.0f, 0.0f, 10.0f), head_hull);
|
||||
return bAtOrPastDestination && UTIL_PointIsDirectlyReachable(pBot, pBot->CurrentFloorPosition, NextPathPoint->Location) && UTIL_QuickTrace(pBot->Edict, pBot->Edict->v.origin, NextPathPoint->Location);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -2007,7 +2007,7 @@ bool HasBotReachedPathPoint(const AvHAIPlayer* pBot)
|
|||
return (vDist2D(pEdict->v.origin, MoveTo) <= playerRadius && (pEdict->v.origin.z - MoveTo.z) < 50.0f && pBot->BotNavInfo.IsOnGround);
|
||||
}
|
||||
case SAMPLE_POLYFLAGS_WALLCLIMB:
|
||||
return (bAtOrPastDestination && pBot->CollisionHullTopLocation.z > MoveTo.z);
|
||||
return (bAtOrPastDestination && fabs(pBot->CollisionHullTopLocation.z - MoveTo.z) < fabs(MoveFrom.z - MoveTo.z));
|
||||
case SAMPLE_POLYFLAGS_LADDER:
|
||||
if (MoveTo.z > MoveFrom.z)
|
||||
{
|
||||
|
@ -3836,7 +3836,7 @@ bool IsBotOffPath(const AvHAIPlayer* pBot)
|
|||
{
|
||||
|
||||
// Can't be off the path if we don't have one...
|
||||
if (pBot->BotNavInfo.CurrentPath.size() == 0) { return false; }
|
||||
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; }
|
||||
|
||||
|
@ -3847,15 +3847,16 @@ bool IsBotOffPath(const AvHAIPlayer* pBot)
|
|||
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;
|
||||
}
|
||||
|
||||
// This checks to ensure the target phase gate hasn't been destroyed since the bot initially calculated its path. If so, then this will force it to calculate a new path
|
||||
// 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;
|
||||
|
@ -3867,10 +3868,8 @@ bool IsBotOffPath(const AvHAIPlayer* pBot)
|
|||
edict_t* pEdict = pBot->Edict;
|
||||
|
||||
Vector MoveTo = pBot->BotNavInfo.CurrentPathPoint->Location;
|
||||
|
||||
Vector MoveFrom = pBot->CurrentFloorPosition;
|
||||
|
||||
|
||||
float PlayerRadiusSq = sqrf(GetPlayerRadius(pBot->Player));
|
||||
float PlayerHeight = GetPlayerHeight(pBot->Edict, false);
|
||||
|
||||
|
@ -3891,7 +3890,13 @@ bool IsBotOffPath(const AvHAIPlayer* pBot)
|
|||
|
||||
if (!UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, MoveTo))
|
||||
{
|
||||
return true;
|
||||
Vector TraceEnd = MoveTo;
|
||||
TraceEnd.z = pBot->Edict->v.origin.z;
|
||||
|
||||
if (!UTIL_QuickHullTrace(pBot->Edict, pBot->Edict->v.origin, TraceEnd, GetPlayerHullIndex(pBot->Edict)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool bAtMoveStart = vEquals(PointOnPath, MoveFrom, 2.0f);
|
||||
|
@ -4158,6 +4163,8 @@ void MoveToWithoutNav(AvHAIPlayer* pBot, const Vector Destination)
|
|||
|
||||
void MoveDirectlyTo(AvHAIPlayer* pBot, const Vector Destination)
|
||||
{
|
||||
if (vIsZero(Destination)) { return; }
|
||||
|
||||
Vector CurrentPos = (pBot->BotNavInfo.IsOnGround) ? pBot->Edict->v.origin : pBot->CurrentFloorPosition;
|
||||
|
||||
const Vector vForward = UTIL_GetVectorNormal2D(Destination - CurrentPos);
|
||||
|
@ -5203,10 +5210,10 @@ void SkulkUpdateBotMoveProfile(AvHAIPlayer* pBot, BotMoveStyle MoveStyle)
|
|||
{
|
||||
if (MoveStyle == pBot->BotNavInfo.PreviousMoveStyle) { return; }
|
||||
|
||||
pBot->BotNavInfo.MoveStyle = MoveStyle;
|
||||
pBot->BotNavInfo.PreviousMoveStyle = MoveStyle;
|
||||
|
||||
pBot->BotNavInfo.bNavProfileChanged = true;
|
||||
pBot->BotNavInfo.MoveStyle = MoveStyle;
|
||||
pBot->BotNavInfo.bNavProfileChanged = true;
|
||||
|
||||
nav_profile* NavProfile = &pBot->BotNavInfo.NavProfile;
|
||||
|
||||
|
@ -5313,6 +5320,135 @@ void OnosUpdateBotMoveProfile(AvHAIPlayer* pBot, BotMoveStyle MoveStyle)
|
|||
}
|
||||
|
||||
bool MoveTo(AvHAIPlayer* pBot, const Vector Destination, const BotMoveStyle MoveStyle, const float MaxAcceptableDist)
|
||||
{
|
||||
if (vIsZero(Destination) || (vDist2D(pBot->Edict->v.origin, Destination) <= 8.0f && (fabs(pBot->CollisionHullBottomLocation.z - Destination.z) < 50.0f)))
|
||||
{
|
||||
ClearBotMovement(pBot);
|
||||
return true;
|
||||
}
|
||||
|
||||
nav_status* BotNavInfo = &pBot->BotNavInfo;
|
||||
|
||||
pBot->BotNavInfo.MoveStyle = MoveStyle;
|
||||
UTIL_UpdateBotMovementStatus(pBot);
|
||||
|
||||
UpdateBotMoveProfile(pBot, MoveStyle);
|
||||
|
||||
bool bIsFlyingProfile = pBot->BotNavInfo.NavProfile.bFlyingProfile;
|
||||
bool bNavProfileChanged = pBot->BotNavInfo.bNavProfileChanged;
|
||||
bool bForceRecalculation = (pBot->BotNavInfo.NextForceRecalc > 0.0f && gpGlobals->time >= pBot->BotNavInfo.NextForceRecalc);
|
||||
|
||||
// Only recalculate the path if there isn't a path, or something has changed and enough time has elapsed since the last path calculation
|
||||
bool bShouldCalculatePath = (bNavProfileChanged || bForceRecalculation || BotNavInfo->CurrentPath.size() == 0 || !vEquals(Destination, BotNavInfo->TargetDestination, GetPlayerRadius(pBot->Player)));
|
||||
|
||||
if (bShouldCalculatePath)
|
||||
{
|
||||
if (!AbortCurrentMove(pBot, Destination)) { return true; }
|
||||
|
||||
if (!bIsFlyingProfile && !pBot->BotNavInfo.IsOnGround) { return true; }
|
||||
|
||||
dtStatus PathFindingStatus = DT_FAILURE;
|
||||
|
||||
if (bIsFlyingProfile)
|
||||
{
|
||||
PathFindingStatus = FindFlightPathToPoint(pBot->BotNavInfo.NavProfile, pBot->Edict->v.origin, Destination, BotNavInfo->CurrentPath, MaxAcceptableDist);
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector NavAdjustedDestination = AdjustPointForPathfinding(Destination);
|
||||
if (vIsZero(NavAdjustedDestination)) { return false; }
|
||||
|
||||
PathFindingStatus = FindPathClosestToPoint(pBot, pBot->BotNavInfo.MoveStyle, pBot->CollisionHullBottomLocation, NavAdjustedDestination, BotNavInfo->CurrentPath, MaxAcceptableDist);
|
||||
}
|
||||
|
||||
pBot->BotNavInfo.NextForceRecalc = 0.0f;
|
||||
pBot->BotNavInfo.bNavProfileChanged = false;
|
||||
|
||||
if (dtStatusSucceed(PathFindingStatus))
|
||||
{
|
||||
BotNavInfo->PathDestination = BotNavInfo->CurrentPath.back().Location;
|
||||
ClearBotStuckMovement(pBot);
|
||||
pBot->BotNavInfo.TotalStuckTime = 0.0f;
|
||||
BotNavInfo->TargetDestination = Destination;
|
||||
BotNavInfo->CurrentPathPoint = BotNavInfo->CurrentPath.begin();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!vIsZero(BotNavInfo->LastNavMeshPosition))
|
||||
{
|
||||
MoveDirectlyTo(pBot, BotNavInfo->LastNavMeshPosition);
|
||||
|
||||
if (vDist2DSq(pBot->CurrentFloorPosition, BotNavInfo->LastNavMeshPosition) < sqrf(8.0f))
|
||||
{
|
||||
BotNavInfo->LastNavMeshPosition = g_vecZero;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (vIsZero(BotNavInfo->UnstuckMoveLocation))
|
||||
{
|
||||
BotNavInfo->UnstuckMoveLocation = FindClosestPointBackOnPath(pBot);
|
||||
}
|
||||
|
||||
if (!vIsZero(BotNavInfo->UnstuckMoveLocation))
|
||||
{
|
||||
MoveDirectlyTo(pBot, BotNavInfo->UnstuckMoveLocation);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsBotPermaStuck(pBot))
|
||||
{
|
||||
BotSuicide(pBot);
|
||||
return false;
|
||||
}
|
||||
|
||||
ClearBotPath(pBot);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (BotNavInfo->CurrentPath.size() > 0)
|
||||
{
|
||||
if (IsBotPermaStuck(pBot))
|
||||
{
|
||||
BotSuicide(pBot);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pBot->Edict->v.flags & FL_INWATER)
|
||||
{
|
||||
BotFollowSwimPath(pBot);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (bIsFlyingProfile)
|
||||
{
|
||||
BotFollowFlightPath(pBot);
|
||||
}
|
||||
else
|
||||
{
|
||||
BotFollowPath(pBot);
|
||||
}
|
||||
}
|
||||
|
||||
// Check to ensure BotFollowFlightPath or BotFollowPath haven't cleared the path (will happen if reached end of path)
|
||||
if (BotNavInfo->CurrentPathPoint != BotNavInfo->CurrentPath.end())
|
||||
{
|
||||
HandlePlayerAvoidance(pBot, BotNavInfo->CurrentPathPoint->Location);
|
||||
BotMovementInputs(pBot);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
bool MoveTo_OLD(AvHAIPlayer* pBot, const Vector Destination, const BotMoveStyle MoveStyle, const float MaxAcceptableDist)
|
||||
{
|
||||
nav_status* BotNavInfo = &pBot->BotNavInfo;
|
||||
|
||||
|
@ -5563,9 +5699,12 @@ Vector FindClosestPointBackOnPath(AvHAIPlayer* pBot)
|
|||
|
||||
AvHAIResourceNode* NearestResNode = AITAC_FindNearestResourceNodeToLocation(pBot->Edict->v.origin, &ResNodeFilter);
|
||||
|
||||
if (!NearestResNode) { return g_vecZero; }
|
||||
Vector ValidNavmeshPoint = AITAC_GetTeamStartingLocation(pBot->Player->GetTeam());
|
||||
|
||||
Vector ValidNavmeshPoint = NearestResNode->Location;
|
||||
if (NearestResNode && vDist2D(pBot->Edict->v.origin, NearestResNode->Location) < vDist2D(pBot->Edict->v.origin, ValidNavmeshPoint))
|
||||
{
|
||||
ValidNavmeshPoint = NearestResNode->Location;
|
||||
}
|
||||
|
||||
ValidNavmeshPoint = UTIL_ProjectPointToNavmesh(ValidNavmeshPoint, pBot->BotNavInfo.NavProfile);
|
||||
|
||||
|
@ -6393,8 +6532,6 @@ void ClearBotMovement(AvHAIPlayer* pBot)
|
|||
ClearBotStuck(pBot);
|
||||
ClearBotStuckMovement(pBot);
|
||||
|
||||
AITASK_ClearBotTask(pBot, &pBot->BotNavInfo.MovementTask);
|
||||
|
||||
pBot->LastPosition = pBot->Edict->v.origin;
|
||||
pBot->TimeSinceLastMovement = 0.0f;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
*/
|
||||
|
||||
constexpr auto MIN_PATH_RECALC_TIME = 0.33f; // How frequently can a bot recalculate its path? Default to max 3 times per second
|
||||
constexpr auto MAX_BOT_STUCK_TIME = 0.0f; // How long a bot can be stuck, unable to move, before giving up and suiciding
|
||||
constexpr auto MAX_BOT_STUCK_TIME = 30.0f; // How long a bot can be stuck, unable to move, before giving up and suiciding
|
||||
|
||||
constexpr auto MARINE_BASE_NAV_PROFILE = 0;
|
||||
constexpr auto SKULK_BASE_NAV_PROFILE = 1;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -104,4 +104,26 @@ void UpdateAIPlayerDMRole(AvHAIPlayer* pBot);
|
|||
|
||||
bool ShouldAIPlayerTakeCommand(AvHAIPlayer* pBot);
|
||||
|
||||
int BotGetNextEnemyTarget(AvHAIPlayer* pBot);
|
||||
|
||||
AvHAICombatStrategy GetBotCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_status* CurrentEnemy);
|
||||
AvHAICombatStrategy GetAlienCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_status* CurrentEnemy);
|
||||
AvHAICombatStrategy GetSkulkCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_status* CurrentEnemy);
|
||||
AvHAICombatStrategy GetGorgeCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_status* CurrentEnemy);
|
||||
AvHAICombatStrategy GetLerkCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_status* CurrentEnemy);
|
||||
AvHAICombatStrategy GetFadeCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_status* CurrentEnemy);
|
||||
AvHAICombatStrategy GetOnosCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_status* CurrentEnemy);
|
||||
AvHAICombatStrategy GetMarineCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_status* CurrentEnemy);
|
||||
|
||||
bool MarineCombatThink(AvHAIPlayer* pBot);
|
||||
void MarineHuntEnemy(AvHAIPlayer* pBot, enemy_status* TrackedEnemy);
|
||||
void BotThrowGrenadeAtTarget(AvHAIPlayer* pBot, const Vector TargetPoint);
|
||||
|
||||
bool AlienCombatThink(AvHAIPlayer* pBot);
|
||||
bool SkulkCombatThink(AvHAIPlayer* pBot);
|
||||
bool GorgeCombatThink(AvHAIPlayer* pBot);
|
||||
bool LerkCombatThink(AvHAIPlayer* pBot);
|
||||
bool FadeCombatThink(AvHAIPlayer* pBot);
|
||||
bool OnosCombatThink(AvHAIPlayer* pBot);
|
||||
|
||||
#endif
|
|
@ -3561,13 +3561,13 @@ const vector<AvHAIHiveDefinition*> AITAC_GetAllHives()
|
|||
return Results;
|
||||
}
|
||||
|
||||
const vector<AvHAIHiveDefinition*> AITAC_GetAllTeamHives(AvHTeamNumber Team)
|
||||
const vector<AvHAIHiveDefinition*> AITAC_GetAllTeamHives(AvHTeamNumber Team, bool bFullyBuiltOnly)
|
||||
{
|
||||
vector<AvHAIHiveDefinition*> Results;
|
||||
|
||||
for (auto it = Hives.begin(); it != Hives.end(); it++)
|
||||
{
|
||||
if (it->OwningTeam == Team)
|
||||
if (it->OwningTeam == Team && (!bFullyBuiltOnly || it->Status == HIVE_STATUS_BUILT))
|
||||
{
|
||||
Results.push_back(&(*it));
|
||||
}
|
||||
|
@ -4139,4 +4139,64 @@ AvHAIDeployableStructureType AITAC_GetNextMissingUpgradeChamberForTeam(AvHTeamNu
|
|||
}
|
||||
|
||||
return STRUCTURE_NONE;
|
||||
}
|
||||
|
||||
edict_t* AITAC_AlienFindNearestHealingSource(AvHTeamNumber Team, Vector SearchLocation, edict_t* SearchingPlayer, bool bIncludeGorges)
|
||||
{
|
||||
edict_t* Result = nullptr;
|
||||
float MinDist = 0.0f;
|
||||
|
||||
vector<AvHAIHiveDefinition*> AllTeamHives = AITAC_GetAllTeamHives(Team, true);
|
||||
|
||||
for (auto it = AllTeamHives.begin(); it != AllTeamHives.end(); it++)
|
||||
{
|
||||
float ThisDist = vDist2DSq((*it)->Location, SearchLocation);
|
||||
// Factor healing radius into the distance checks, we don't have to be right at the hive to heal
|
||||
ThisDist -= BALANCE_VAR(kHiveHealRadius) * 0.75f;
|
||||
|
||||
// We're already in healing distance of a hive, that's our healing source
|
||||
if (ThisDist <= 0.0f) { return (*it)->HiveEntity->edict(); }
|
||||
|
||||
if (FNullEnt(Result) || ThisDist < MinDist)
|
||||
{
|
||||
Result = (*it)->HiveEntity->edict();
|
||||
MinDist = ThisDist;
|
||||
}
|
||||
}
|
||||
|
||||
DeployableSearchFilter DCFilter;
|
||||
DCFilter.DeployableTeam = Team;
|
||||
DCFilter.DeployableTypes = STRUCTURE_ALIEN_DEFENCECHAMBER;
|
||||
DCFilter.MaxSearchRadius = (!FNullEnt(Result)) ? MinDist : 0.0f; // We should always have a result, unless we have no hives left. That's our benchmark: only look for DCs closer than the hive
|
||||
|
||||
vector<AvHAIBuildableStructure*> AllDCs = AITAC_FindAllDeployables(SearchLocation, &DCFilter);
|
||||
|
||||
for (auto it = AllDCs.begin(); it != AllDCs.end(); it++)
|
||||
{
|
||||
AvHAIBuildableStructure* ThisDC = (*it);
|
||||
|
||||
float ThisDist = vDist2DSq(ThisDC->Location, SearchLocation);
|
||||
// Factor healing radius into the distance checks, we don't have to be sat on top of the DC to heal
|
||||
ThisDist -= BALANCE_VAR(kHiveHealRadius) * 0.75f;
|
||||
|
||||
// We're already in healing distance of a DC, that's our healing source
|
||||
if (ThisDist <= 0.0f) { return ThisDC->edict; }
|
||||
|
||||
if (FNullEnt(Result) || ThisDist < MinDist)
|
||||
{
|
||||
Result = ThisDC->edict;
|
||||
MinDist = ThisDist;
|
||||
}
|
||||
}
|
||||
|
||||
edict_t* FriendlyGorge = nullptr;
|
||||
|
||||
if (bIncludeGorges)
|
||||
{
|
||||
float PlayerSearchDist = (!FNullEnt(Result)) ? MinDist : 0.0f; // As before, we only want players closer than our current "winner"
|
||||
FriendlyGorge = AITAC_GetNearestPlayerOfClassInArea(Team, SearchLocation, PlayerSearchDist, false, SearchingPlayer, AVH_USER3_ALIEN_PLAYER2);
|
||||
}
|
||||
|
||||
return (!FNullEnt(FriendlyGorge) ? FriendlyGorge : Result);
|
||||
|
||||
}
|
|
@ -159,7 +159,7 @@ edict_t* AITAC_GetNearestHiddenPlayerInLocation(AvHTeamNumber Team, const Vector
|
|||
const vector<AvHAIResourceNode*> AITAC_GetAllResourceNodes();
|
||||
const vector<AvHAIResourceNode*> AITAC_GetAllReachableResourceNodes(AvHTeamNumber Team);
|
||||
const vector<AvHAIHiveDefinition*> AITAC_GetAllHives();
|
||||
const vector<AvHAIHiveDefinition*> AITAC_GetAllTeamHives(AvHTeamNumber Team);
|
||||
const vector<AvHAIHiveDefinition*> AITAC_GetAllTeamHives(AvHTeamNumber Team, bool bFullyBuiltOnly);
|
||||
|
||||
bool AITAC_AnyPlayerOnTeamWithLOS(AvHTeamNumber Team, const Vector& Location, float SearchRadius);
|
||||
|
||||
|
@ -173,4 +173,6 @@ AvHAIDeployableStructureType AITAC_GetNextMissingUpgradeChamberForTeam(AvHTeamNu
|
|||
|
||||
void AITAC_OnTeamStartsModified();
|
||||
|
||||
edict_t* AITAC_AlienFindNearestHealingSource(AvHTeamNumber Team, Vector SearchLocation, edict_t* SearchingPlayer, bool bIncludeGorges);
|
||||
|
||||
#endif
|
|
@ -111,49 +111,49 @@ float GetEnergyCostForWeapon(const AvHAIWeapon Weapon)
|
|||
switch (Weapon)
|
||||
{
|
||||
case WEAPON_SKULK_BITE:
|
||||
return kBiteEnergyCost;
|
||||
return BALANCE_VAR(kBiteEnergyCost);
|
||||
case WEAPON_SKULK_PARASITE:
|
||||
return kParasiteEnergyCost;
|
||||
return BALANCE_VAR(kParasiteEnergyCost);
|
||||
case WEAPON_SKULK_LEAP:
|
||||
return kLeapEnergyCost;
|
||||
return BALANCE_VAR(kLeapEnergyCost);
|
||||
case WEAPON_SKULK_XENOCIDE:
|
||||
return kDivineWindEnergyCost;
|
||||
return BALANCE_VAR(kDivineWindEnergyCost);
|
||||
|
||||
case WEAPON_GORGE_SPIT:
|
||||
return kSpitEnergyCost;
|
||||
return BALANCE_VAR(kSpitEnergyCost);
|
||||
case WEAPON_GORGE_HEALINGSPRAY:
|
||||
return kHealingSprayEnergyCost;
|
||||
return BALANCE_VAR(kHealingSprayEnergyCost);
|
||||
case WEAPON_GORGE_BILEBOMB:
|
||||
return kBileBombEnergyCost;
|
||||
return BALANCE_VAR(kBileBombEnergyCost);
|
||||
case WEAPON_GORGE_WEB:
|
||||
return kWebEnergyCost;
|
||||
return BALANCE_VAR(kWebEnergyCost);
|
||||
|
||||
case WEAPON_LERK_BITE:
|
||||
return kBite2EnergyCost;
|
||||
return BALANCE_VAR(kBite2EnergyCost);
|
||||
case WEAPON_LERK_SPORES:
|
||||
return kSporesEnergyCost;
|
||||
return BALANCE_VAR(kSporesEnergyCost);
|
||||
case WEAPON_LERK_UMBRA:
|
||||
return kUmbraEnergyCost;
|
||||
return BALANCE_VAR(kUmbraEnergyCost);
|
||||
case WEAPON_LERK_PRIMALSCREAM:
|
||||
return kPrimalScreamEnergyCost;
|
||||
return BALANCE_VAR(kPrimalScreamEnergyCost);
|
||||
|
||||
case WEAPON_FADE_SWIPE:
|
||||
return kSwipeEnergyCost;
|
||||
return BALANCE_VAR(kSwipeEnergyCost);
|
||||
case WEAPON_FADE_BLINK:
|
||||
return kBlinkEnergyCost;
|
||||
return BALANCE_VAR(kBlinkEnergyCost);
|
||||
case WEAPON_FADE_METABOLIZE:
|
||||
return kMetabolizeEnergyCost;
|
||||
return BALANCE_VAR(kMetabolizeEnergyCost);
|
||||
case WEAPON_FADE_ACIDROCKET:
|
||||
return kAcidRocketEnergyCost;
|
||||
return BALANCE_VAR(kAcidRocketEnergyCost);
|
||||
|
||||
case WEAPON_ONOS_GORE:
|
||||
return kClawsEnergyCost;
|
||||
return BALANCE_VAR(kClawsEnergyCost);
|
||||
case WEAPON_ONOS_DEVOUR:
|
||||
return kDevourEnergyCost;
|
||||
return BALANCE_VAR(kDevourEnergyCost);
|
||||
case WEAPON_ONOS_STOMP:
|
||||
return kStompEnergyCost;
|
||||
return BALANCE_VAR(kStompEnergyCost);
|
||||
case WEAPON_ONOS_CHARGE:
|
||||
return kChargeEnergyCost;
|
||||
return BALANCE_VAR(kChargeEnergyCost);
|
||||
|
||||
default:
|
||||
return 0.0f;
|
||||
|
@ -207,6 +207,14 @@ bool IsHitscanWeapon(AvHAIWeapon Weapon)
|
|||
return false;
|
||||
}
|
||||
|
||||
float GetTimeUntilPlayerNextRefire(const AvHPlayer* Player)
|
||||
{
|
||||
AvHBasePlayerWeapon* WeaponRef = dynamic_cast<AvHBasePlayerWeapon*>(Player->m_pActiveItem);
|
||||
|
||||
if (!WeaponRef) { return 0.0f; }
|
||||
|
||||
return WeaponRef->m_flNextPrimaryAttack;
|
||||
}
|
||||
|
||||
AvHAIWeapon GetBotMarineSecondaryWeapon(const AvHAIPlayer* pBot)
|
||||
{
|
||||
|
@ -387,21 +395,21 @@ float GetMaxIdealWeaponRange(const AvHAIWeapon Weapon)
|
|||
case WEAPON_ONOS_STOMP:
|
||||
return UTIL_MetresToGoldSrcUnits(8.0f);
|
||||
case WEAPON_SKULK_XENOCIDE:
|
||||
return UTIL_MetresToGoldSrcUnits(5.0f);
|
||||
return (float)BALANCE_VAR(kDivineWindRadius) * 0.8f;
|
||||
case WEAPON_ONOS_GORE:
|
||||
return BALANCE_VAR(kClawsRange);
|
||||
return (float)BALANCE_VAR(kClawsRange);
|
||||
case WEAPON_ONOS_DEVOUR:
|
||||
return BALANCE_VAR(kDevourRange);
|
||||
return (float)BALANCE_VAR(kDevourRange);
|
||||
case WEAPON_FADE_SWIPE:
|
||||
return BALANCE_VAR(kSwipeRange);
|
||||
return (float)BALANCE_VAR(kSwipeRange);
|
||||
case WEAPON_SKULK_BITE:
|
||||
return BALANCE_VAR(kBiteRange);
|
||||
return (float)BALANCE_VAR(kBiteRange);
|
||||
case WEAPON_LERK_BITE:
|
||||
return BALANCE_VAR(kBite2Range);
|
||||
return (float)BALANCE_VAR(kBite2Range);
|
||||
case WEAPON_GORGE_HEALINGSPRAY:
|
||||
return BALANCE_VAR(kHealingSprayRange) * 0.5f;
|
||||
return (float)BALANCE_VAR(kHealingSprayRange) * 0.5f;
|
||||
case WEAPON_MARINE_WELDER:
|
||||
return BALANCE_VAR(kWelderRange);
|
||||
return (float)BALANCE_VAR(kWelderRange);
|
||||
default:
|
||||
return max_player_use_reach;
|
||||
}
|
||||
|
@ -692,7 +700,7 @@ AvHAIWeapon BotAlienChooseBestWeaponForStructure(AvHAIPlayer* pBot, edict_t* tar
|
|||
return WEAPON_GORGE_BILEBOMB;
|
||||
}
|
||||
|
||||
if (PlayerHasWeapon(pBot->Player, WEAPON_FADE_ACIDROCKET) && StructureType == STRUCTURE_ALIEN_HIVE || IsDamagingStructure(StructureType))
|
||||
if (PlayerHasWeapon(pBot->Player, WEAPON_FADE_ACIDROCKET) && (StructureType == STRUCTURE_ALIEN_HIVE || IsDamagingStructure(StructureType)))
|
||||
{
|
||||
return WEAPON_FADE_ACIDROCKET;
|
||||
}
|
||||
|
@ -781,21 +789,27 @@ AvHAIWeapon SkulkGetBestWeaponForCombatTarget(AvHAIPlayer* pBot, edict_t* Target
|
|||
if (PlayerHasWeapon(pBot->Player, WEAPON_SKULK_XENOCIDE))
|
||||
{
|
||||
AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(pBot->Player->GetTeam());
|
||||
float XenocideRadius = GetMaxIdealWeaponRange(WEAPON_SKULK_XENOCIDE);
|
||||
|
||||
int NumEnemyTargetsInArea = AITAC_GetNumPlayersOfTeamInArea(EnemyTeam, Target->v.origin, UTIL_MetresToGoldSrcUnits(5.0f), false, nullptr, AVH_USER3_NONE);
|
||||
// Add one to include the target themselves
|
||||
int NumEnemyTargetsInArea = AITAC_GetNumPlayersOnTeamWithLOS(EnemyTeam, Target->v.origin, XenocideRadius, Target) + 1;
|
||||
|
||||
AvHTeam* EnemyTeamRef = GetGameRules()->GetTeam(EnemyTeam);
|
||||
|
||||
if (EnemyTeamRef)
|
||||
if (NumEnemyTargetsInArea <= 2)
|
||||
{
|
||||
AvHAIDeployableStructureType StructureSearchType = (EnemyTeamRef->GetTeamType() == AVH_CLASS_TYPE_MARINE) ? SEARCH_ALL_MARINE_STRUCTURES : SEARCH_ALL_ALIEN_STRUCTURES;
|
||||
|
||||
DeployableSearchFilter SearchFilter;
|
||||
SearchFilter.DeployableTypes = StructureSearchType;
|
||||
SearchFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f);
|
||||
SearchFilter.DeployableTeam = EnemyTeam;
|
||||
AvHTeam* EnemyTeamRef = GetGameRules()->GetTeam(EnemyTeam);
|
||||
|
||||
NumEnemyTargetsInArea += AITAC_GetNumDeployablesNearLocation(Target->v.origin, &SearchFilter);
|
||||
if (EnemyTeamRef)
|
||||
{
|
||||
AvHAIDeployableStructureType StructureSearchType = (EnemyTeamRef->GetTeamType() == AVH_CLASS_TYPE_MARINE) ? SEARCH_ALL_MARINE_STRUCTURES : SEARCH_ALL_ALIEN_STRUCTURES;
|
||||
|
||||
DeployableSearchFilter SearchFilter;
|
||||
SearchFilter.DeployableTypes = StructureSearchType;
|
||||
SearchFilter.MaxSearchRadius = XenocideRadius;
|
||||
SearchFilter.DeployableTeam = EnemyTeam;
|
||||
|
||||
NumEnemyTargetsInArea += AITAC_GetNumDeployablesNearLocation(Target->v.origin, &SearchFilter);
|
||||
}
|
||||
}
|
||||
|
||||
if (NumEnemyTargetsInArea > 2)
|
||||
|
@ -883,12 +897,23 @@ AvHAIWeapon OnosGetBestWeaponForCombatTarget(AvHAIPlayer* pBot, edict_t* Target)
|
|||
return WEAPON_ONOS_STOMP;
|
||||
}
|
||||
|
||||
if (!IsPlayerDigesting(pBot->Edict) && DistFromTarget < sqrf(UTIL_MetresToGoldSrcUnits(2.0f)))
|
||||
AvHAIWeapon AttackWeapon = WEAPON_ONOS_GORE;
|
||||
|
||||
if (!IsPlayerDigesting(pBot->Edict))
|
||||
{
|
||||
return WEAPON_ONOS_DEVOUR;
|
||||
AttackWeapon = WEAPON_ONOS_DEVOUR;
|
||||
}
|
||||
|
||||
return WEAPON_ONOS_GORE;
|
||||
float AttackWeaponRange = GetMaxIdealWeaponRange(AttackWeapon);
|
||||
|
||||
BotAttackResult WeaponAttackResult = PerformAttackLOSCheck(pBot, AttackWeapon, Target);
|
||||
|
||||
if (PlayerHasWeapon(pBot->Player, WEAPON_ONOS_CHARGE) && UTIL_PointIsDirectlyReachable(pBot->Edict->v.origin, Target->v.origin) && WeaponAttackResult == ATTACK_OUTOFRANGE)
|
||||
{
|
||||
return WEAPON_ONOS_CHARGE;
|
||||
}
|
||||
|
||||
return AttackWeapon;
|
||||
}
|
||||
|
||||
AvHAIWeapon FadeGetBestWeaponForCombatTarget(AvHAIPlayer* pBot, edict_t* Target)
|
||||
|
@ -1009,6 +1034,132 @@ BotAttackResult PerformAttackLOSCheck(AvHAIPlayer* pBot, const AvHAIWeapon Weapo
|
|||
return ATTACK_SUCCESS;
|
||||
}
|
||||
|
||||
BotAttackResult PerformAttackLOSCheck(AvHAIPlayer* pBot, const AvHAIWeapon Weapon, const Vector TargetLocation, const edict_t* Target)
|
||||
{
|
||||
if (!TargetLocation) { return ATTACK_INVALIDTARGET; }
|
||||
|
||||
if (Weapon == WEAPON_NONE) { return ATTACK_NOWEAPON; }
|
||||
|
||||
// Don't need aiming or special LOS checks for primal scream as it's AoE buff
|
||||
if (Weapon == WEAPON_LERK_PRIMALSCREAM)
|
||||
{
|
||||
return ATTACK_SUCCESS;
|
||||
}
|
||||
|
||||
// Add a LITTLE bit of give to avoid edge cases where the bot is a smidge out of range
|
||||
float MaxWeaponRange = GetMaxIdealWeaponRange(Weapon) - 5.0f;
|
||||
|
||||
// Don't need aiming or special LOS checks for Xenocide as it's an AOE attack, just make sure we're close enough and don't have a wall in the way
|
||||
if (Weapon == WEAPON_SKULK_XENOCIDE)
|
||||
{
|
||||
if (vDist3DSq(pBot->Edict->v.origin, TargetLocation) <= sqrf(MaxWeaponRange) && UTIL_QuickTrace(pBot->Edict, pBot->Edict->v.origin, TargetLocation))
|
||||
{
|
||||
return ATTACK_SUCCESS;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ATTACK_OUTOFRANGE;
|
||||
}
|
||||
}
|
||||
|
||||
// For charge and stomp, we can go through stuff so don't need to check for being blocked
|
||||
if (Weapon == WEAPON_ONOS_CHARGE || Weapon == WEAPON_ONOS_STOMP)
|
||||
{
|
||||
if (vDist3DSq(pBot->Edict->v.origin, TargetLocation) > sqrf(MaxWeaponRange)) { return ATTACK_OUTOFRANGE; }
|
||||
|
||||
if (!UTIL_QuickTrace(pBot->Edict, pBot->Edict->v.origin, TargetLocation) || fabsf(TargetLocation.z - TargetLocation.z) > 50.0f) { return ATTACK_OUTOFRANGE; }
|
||||
|
||||
return ATTACK_SUCCESS;
|
||||
}
|
||||
|
||||
TraceResult hit;
|
||||
|
||||
Vector StartTrace = pBot->CurrentEyePosition;
|
||||
|
||||
Vector AttackDir = UTIL_GetVectorNormal(TargetLocation - StartTrace);
|
||||
|
||||
Vector EndTrace = pBot->CurrentEyePosition + (AttackDir * MaxWeaponRange);
|
||||
|
||||
UTIL_TraceLine(StartTrace, EndTrace, dont_ignore_monsters, dont_ignore_glass, pBot->Edict->v.pContainingEntity, &hit);
|
||||
|
||||
if (FNullEnt(hit.pHit)) { return ATTACK_OUTOFRANGE; }
|
||||
|
||||
if (hit.pHit != Target)
|
||||
{
|
||||
if (vDist3DSq(pBot->CurrentEyePosition, TargetLocation) > sqrf(MaxWeaponRange))
|
||||
{
|
||||
return ATTACK_OUTOFRANGE;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ATTACK_BLOCKED;
|
||||
}
|
||||
}
|
||||
|
||||
return ATTACK_SUCCESS;
|
||||
}
|
||||
|
||||
BotAttackResult PerformAttackLOSCheck(const Vector Location, const AvHAIWeapon Weapon, const edict_t* Target)
|
||||
{
|
||||
if (FNullEnt(Target) || (Target->v.deadflag != DEAD_NO)) { return ATTACK_INVALIDTARGET; }
|
||||
|
||||
if (Weapon == WEAPON_NONE) { return ATTACK_NOWEAPON; }
|
||||
|
||||
float MaxWeaponRange = GetMaxIdealWeaponRange(Weapon);
|
||||
|
||||
// Don't need aiming or special LOS checks for Xenocide as it's an AOE attack, just make sure we're close enough and don't have a wall in the way
|
||||
if (Weapon == WEAPON_SKULK_XENOCIDE)
|
||||
{
|
||||
if (vDist3DSq(Location, Target->v.origin) <= sqrf(MaxWeaponRange) && UTIL_QuickTrace(nullptr, Location, Target->v.origin))
|
||||
{
|
||||
return ATTACK_SUCCESS;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ATTACK_OUTOFRANGE;
|
||||
}
|
||||
}
|
||||
|
||||
// For charge and stomp, we can go through stuff so don't need to check for being blocked
|
||||
if (Weapon == WEAPON_ONOS_CHARGE || Weapon == WEAPON_ONOS_STOMP)
|
||||
{
|
||||
if (vDist3DSq(Location, Target->v.origin) > sqrf(MaxWeaponRange)) { return ATTACK_OUTOFRANGE; }
|
||||
|
||||
if (!UTIL_QuickTrace(nullptr, Location, Target->v.origin) || fabsf(Target->v.origin.z - Target->v.origin.z) > 50.0f) { return ATTACK_OUTOFRANGE; }
|
||||
|
||||
return ATTACK_SUCCESS;
|
||||
}
|
||||
|
||||
bool bIsMeleeWeapon = IsMeleeWeapon(Weapon);
|
||||
|
||||
TraceResult hit;
|
||||
|
||||
Vector StartTrace = Location;
|
||||
|
||||
Vector AttackDir = UTIL_GetVectorNormal(UTIL_GetCentreOfEntity(Target) - StartTrace);
|
||||
|
||||
Vector EndTrace = Location + (AttackDir * MaxWeaponRange);
|
||||
|
||||
UTIL_TraceLine(StartTrace, EndTrace, dont_ignore_monsters, dont_ignore_glass, nullptr, &hit);
|
||||
|
||||
if (FNullEnt(hit.pHit)) { return ATTACK_OUTOFRANGE; }
|
||||
|
||||
if (hit.pHit != Target)
|
||||
{
|
||||
if (vDist3DSq(Location, Target->v.origin) > sqrf(MaxWeaponRange))
|
||||
{
|
||||
return ATTACK_OUTOFRANGE;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ATTACK_BLOCKED;
|
||||
}
|
||||
}
|
||||
|
||||
return ATTACK_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
bool IsAreaAffectedBySpores(const Vector Location)
|
||||
{
|
||||
bool Result = false;
|
||||
|
|
|
@ -64,8 +64,11 @@ bool CanInterruptWeaponReload(AvHAIWeapon Weapon);
|
|||
void InterruptReload(AvHAIPlayer* pBot);
|
||||
|
||||
bool IsHitscanWeapon(AvHAIWeapon Weapon);
|
||||
float GetTimeUntilPlayerNextRefire(const AvHPlayer* Player);
|
||||
|
||||
BotAttackResult PerformAttackLOSCheck(AvHAIPlayer* pBot, const AvHAIWeapon Weapon, const edict_t* Target);
|
||||
BotAttackResult PerformAttackLOSCheck(AvHAIPlayer* pBot, const AvHAIWeapon Weapon, const Vector TargetLocation, const edict_t* Target);
|
||||
BotAttackResult PerformAttackLOSCheck(const Vector Location, const AvHAIWeapon Weapon, const edict_t* Target);
|
||||
|
||||
float UTIL_GetProjectileVelocityForWeapon(const AvHAIWeapon Weapon);
|
||||
bool IsAreaAffectedBySpores(const Vector Location);
|
||||
|
|
Loading…
Reference in a new issue