Lift improvements

* Fixed bots getting into a clusterfuck when multiple try to use a lift at the same time.
* Improved nav meshes for various maps
* Added new console commands for debugging to force bots to evolve
* If there is no CC, infantry portal or hives in NS mode, bots will revert to deathmatch behaviour to finish the game
* Fixed bots flying off target when leaping/blinking sometimes
* If there is no armoury to retreat to, marines won't back off and will attack (stops clustering around the CC for no reason).
This commit is contained in:
RGreenlees 2024-04-16 17:39:23 +01:00
parent 261ff5cef1
commit 3e36057948
13 changed files with 836 additions and 346 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -228,6 +228,7 @@ typedef struct _OFF_MESH_CONN
Vector FromLocation = g_vecZero;
Vector ToLocation = g_vecZero;
edict_t* TargetObject = nullptr;
bool bBiDirectional = false;
} AvHAIOffMeshConnection;
typedef struct _STRUCTURE_OBSTACLE
@ -749,11 +750,11 @@ typedef struct AVH_AI_PLAYER
vector<AvHAIBuildableStructure> DangerTurrets;
AvHAIPlayerTask* CurrentTask = nullptr; // Bot's current task they're performing
AvHAIPlayerTask PrimaryBotTask;
AvHAIPlayerTask SecondaryBotTask;
AvHAIPlayerTask WantsAndNeedsTask;
AvHAIPlayerTask CommanderTask; // Task assigned by the commander
AvHAIPlayerTask* CurrentTask = &PrimaryBotTask; // Bot's current task they're performing
float BotNextTaskEvaluationTime = 0.0f;

File diff suppressed because it is too large Load diff

View file

@ -257,6 +257,9 @@ DoorTrigger* UTIL_GetDoorTriggerByEntity(edict_t* TriggerEntity);
bool UTIL_TriggerHasBeenRecentlyActivated(edict_t* TriggerEntity);
// Directly calls the Use function on this trigger, regardless of where the bot is
void NAV_ForceActivateTrigger(AvHAIPlayer* pBot, DoorTrigger* TriggerRef);
// Will check for any func_breakable which might be in the way (e.g. window, vent) and make the bot aim and attack it to break it. Marines will switch to knife to break it.
void CheckAndHandleBreakableObstruction(AvHAIPlayer* pBot, const Vector MoveFrom, const Vector MoveTo, unsigned int MovementFlags);
@ -349,13 +352,13 @@ void HandlePlayerAvoidance(AvHAIPlayer* pBot, const Vector MoveDestination);
Vector AdjustPointForPathfinding(const Vector Point);
Vector AdjustPointForPathfinding(const Vector Point, const nav_profile& NavProfile);
// Special path finding that takes the presence of phase gates into account
// Special path finding for lerks
dtStatus FindFlightPathToPoint(const nav_profile& NavProfile, Vector FromLocation, Vector ToLocation, vector<bot_path_node>& path, float MaxAcceptableDistance);
Vector UTIL_FindHighestSuccessfulTracePoint(const Vector TraceFrom, const Vector TargetPoint, const Vector NextPoint, const float IterationStep, const float MinIdealHeight, const float MaxHeight);
// Similar to FindPathToPoint, but you can specify a max acceptable distance for partial results. Will return a failure if it can't reach at least MaxAcceptableDistance away from the ToLocation
dtStatus FindPathClosestToPoint(AvHAIPlayer* pBot, const BotMoveStyle MoveStyle, const Vector FromLocation, const Vector ToLocation, vector<bot_path_node>& path, float MaxAcceptableDistance);
dtStatus FindPathClosestToPoint(AvHAIPlayer* pBot, const BotMoveStyle MoveStyle, const Vector ToLocation, vector<bot_path_node>& path, float MaxAcceptableDistance);
dtStatus FindPathClosestToPoint(const nav_profile& NavProfile, const Vector FromLocation, const Vector ToLocation, vector<bot_path_node>& path, float MaxAcceptableDistance);
dtStatus DEBUG_TestFindPath(const nav_profile& NavProfile, const Vector FromLocation, const Vector ToLocation, vector<bot_path_node>& path, float MaxAcceptableDistance);
@ -481,6 +484,7 @@ void UTIL_ApplyTempObstaclesToDoor(nav_door* DoorRef, const int Area);
nav_door* UTIL_GetNavDoorByEdict(const edict_t* DoorEdict);
nav_door* UTIL_GetClosestLiftToPoints(const Vector StartPoint, const Vector EndPoint);
AvHAIOffMeshConnection* UTIL_GetOffMeshConnectionForLift(nav_door* LiftRef);
Vector UTIL_AdjustPointAwayFromNavWall(const Vector Location, const float MaxDistanceFromWall);

View file

@ -195,7 +195,7 @@ bool BotUseObject(AvHAIPlayer* pBot, edict_t* Target, bool bContinuous)
if (AimDot >= 0.95f)
{
pBot->Button |= IN_USE;
//pBot->Button |= IN_USE;
pBot->LastUseTime = gpGlobals->time;
CBaseEntity* UsedObject = CBaseEntity::Instance(Target);
@ -333,8 +333,13 @@ void BotLeap(AvHAIPlayer* pBot, const Vector TargetLocation)
float Dot = UTIL_GetDotProduct2D(FaceAngle, MoveDir);
if (Dot >= 0.98f)
if (Dot >= 0.95f)
{
// Just give the bot a nudge and make sure they don't miss and end up somewhere they don't want to be
float MoveSpeed = vSize2D(pBot->Edict->v.velocity);
Vector NewVelocity = MoveDir * MoveSpeed;
NewVelocity.z = pBot->Edict->v.velocity.z;
pBot->Button |= IN_ATTACK2;
pBot->BotNavInfo.bIsJumping = true;
pBot->BotNavInfo.LeapAttemptedTime = gpGlobals->time;
@ -1725,6 +1730,8 @@ void StartNewBotFrame(AvHAIPlayer* pBot)
{
edict_t* pEdict = pBot->Edict;
if (!pBot->CurrentTask) { pBot->CurrentTask = &pBot->PrimaryBotTask; }
ClearBotInputs(pBot);
pBot->CurrentEyePosition = GetPlayerEyePosition(pEdict);
@ -2197,6 +2204,8 @@ void UpdateAIMarinePlayerNSRole(AvHAIPlayer* pBot)
void AIPlayerNSThink(AvHAIPlayer* pBot)
{
return;
AvHTeam* BotTeam = GetGameRules()->GetTeam(pBot->Player->GetTeam());
if (!BotTeam) { return; }
@ -2676,8 +2685,10 @@ AvHAICombatStrategy GetMarineCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_st
float DistToEnemy = vDist2DSq(pBot->Edict->v.origin, CurrentEnemy->LastSeenLocation);
bool bCanRetreat = AITAC_IsCompletedStructureOfTypeNearLocation(BotTeam, (STRUCTURE_MARINE_COMMCHAIR | STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY), ZERO_VECTOR, 0.0f);
// If we are doing something important, don't get distracted by enemies that aren't an immediate threat
if (pBot->CurrentTask && pBot->CurrentTask->TaskType == TASK_DEFEND || pBot->CommanderTask.TaskType != TASK_NONE)
if (pBot->CurrentTask && (pBot->CurrentTask->TaskType == TASK_DEFEND || pBot->CommanderTask.TaskType != TASK_NONE))
{
if ((!CurrentEnemy->bHasLOS || DistToEnemy > sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) && (!vIsZero(pBot->CurrentTask->TaskLocation) && !UTIL_PlayerHasLOSToLocation(CurrentEnemy->EnemyEdict, pBot->CurrentTask->TaskLocation, UTIL_MetresToGoldSrcUnits(30.0f))))
{
@ -2685,7 +2696,7 @@ AvHAICombatStrategy GetMarineCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_st
}
}
if (pBot->CurrentCombatStrategy == COMBAT_STRATEGY_RETREAT)
if (bCanRetreat && pBot->CurrentCombatStrategy == COMBAT_STRATEGY_RETREAT)
{
int MinDesiredAmmo = imini(UTIL_GetPlayerPrimaryMaxAmmoReserve(pBot->Player), UTIL_GetPlayerPrimaryWeaponMaxClipSize(pBot->Player) * 2);
@ -2698,7 +2709,7 @@ AvHAICombatStrategy GetMarineCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_st
int NumEnemyAllies = AITAC_GetNumPlayersOnTeamWithLOS(EnemyTeam, EnemyEdict->v.origin, UTIL_MetresToGoldSrcUnits(10.0f), EnemyEdict);
int NumFriendlies = AITAC_GetNumPlayersOnTeamWithLOS(BotTeam, pBot->Edict->v.origin, UTIL_MetresToGoldSrcUnits(10.0f), pBot->Edict);
if (CurrentHealthPercent < 0.3f || (CurrentHealthPercent < 0.5f && NumEnemyAllies > 0) || UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) < UTIL_GetPlayerPrimaryWeaponMaxClipSize(pBot->Player))
if (bCanRetreat && (CurrentHealthPercent < 0.3f || (CurrentHealthPercent < 0.5f && NumEnemyAllies > 0) || UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) < UTIL_GetPlayerPrimaryWeaponMaxClipSize(pBot->Player)))
{
return COMBAT_STRATEGY_RETREAT;
}
@ -2797,8 +2808,6 @@ void AIPlayerNSMarineThink(AvHAIPlayer* pBot)
if (MarineCombatThink(pBot)) { return; }
}
if (!pBot->CurrentTask) { pBot->CurrentTask = &pBot->PrimaryBotTask; }
if (gpGlobals->time >= pBot->BotNextTaskEvaluationTime)
{
pBot->BotNextTaskEvaluationTime = gpGlobals->time + frandrange(0.2f, 0.5f);
@ -4199,7 +4208,6 @@ bool AIPlayerMustFinishCurrentTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
void AIPlayerNSAlienThink(AvHAIPlayer* pBot)
{
if (!pBot->CurrentTask) { pBot->CurrentTask = &pBot->PrimaryBotTask; }
if (pBot->CurrentEnemy > -1)
{
@ -4686,8 +4694,6 @@ void AIPlayerCOMarineThink(AvHAIPlayer* pBot)
void AIPlayerCOAlienThink(AvHAIPlayer* pBot)
{
if (!pBot->CurrentTask) { pBot->CurrentTask = &pBot->PrimaryBotTask; }
AvHMessageID NextCombatUpgrade = GetNextAIPlayerCOAlienUpgrade(pBot);
if (NextCombatUpgrade != MESSAGE_NULL)
@ -4762,7 +4768,7 @@ void AIPlayerSetPrimaryCOMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
}
// Nothing to attack, just hunt down remaining enemy players. Shouldn't happen in vanilla combat mode, but a plugin might change behaviour
if (FNullEnt(StructureToAttack))
if (!StructureToAttack || FNullEnt(StructureToAttack))
{
vector<AvHPlayer*> AllEnemyPlayers = AIMGR_GetAllPlayersOnTeam(EnemyTeam);
@ -5108,7 +5114,7 @@ void AIPlayerSetPrimaryCOAlienTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
}
// Nothing to attack, just hunt down remaining enemy players. Shouldn't happen in vanilla combat mode, but a plugin might change behaviour
if (FNullEnt(StructureToAttack))
if (!StructureToAttack || FNullEnt(StructureToAttack))
{
vector<AvHPlayer*> AllEnemyPlayers = AIMGR_GetAllPlayersOnTeam(EnemyTeam);
edict_t* TargetPlayer = nullptr;
@ -5248,6 +5254,58 @@ void AIPlayerSetSecondaryCOAlienTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
AITASK_ClearBotTask(pBot, Task);
}
void AIPlayerEndMatchThink(AvHAIPlayer* pBot)
{
pBot->CurrentEnemy = BotGetNextEnemyTarget(pBot);
if (pBot->CurrentEnemy > -1)
{
if (IsPlayerMarine(pBot->Player))
{
MarineCombatThink(pBot);
}
else
{
AlienCombatThink(pBot);
}
return;
}
AvHTeamNumber BotTeam = pBot->Player->GetTeam();
AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam);
vector<AvHPlayer*> EnemyPlayers = AIMGR_GetAllPlayersOnTeam(EnemyTeam);
AvHPlayer* EnemyToAttack = nullptr;
float MinDist = 0.0f;
for (auto it = EnemyPlayers.begin(); it != EnemyPlayers.end(); it++)
{
edict_t* PlayerEdict = (*it)->edict();
if (IsPlayerActiveInGame(PlayerEdict))
{
float ThisDist = vDist2DSq(pBot->Player->pev->origin, PlayerEdict->v.origin);
if (!EnemyToAttack || ThisDist < MinDist)
{
EnemyToAttack = (*it);
MinDist = ThisDist;
}
}
}
if (EnemyToAttack)
{
MoveTo(pBot, EnemyToAttack->pev->origin, MOVESTYLE_NORMAL, UTIL_MetresToGoldSrcUnits(10.0f));
}
else
{
AIPlayerDMThink(pBot);
}
}
void AIPlayerDMThink(AvHAIPlayer* pBot)
{
@ -5267,8 +5325,6 @@ void AIPlayerDMThink(AvHAIPlayer* pBot)
return;
}
pBot->CurrentTask = &pBot->PrimaryBotTask;
AITASK_BotUpdateAndClearTasks(pBot);
if (pBot->CurrentTask->TaskType == TASK_NONE)
@ -5355,8 +5411,17 @@ void AIPlayerThink(AvHAIPlayer* pBot)
switch (GetGameRules()->GetMapMode())
{
case MAP_MODE_NS:
AIPlayerNSThink(pBot);
break;
{
if (AIMGR_IsMatchPracticallyOver())
{
AIPlayerEndMatchThink(pBot);
}
else
{
AIPlayerNSThink(pBot);
}
}
break;
case MAP_MODE_CO:
AIPlayerCOThink(pBot);
break;

View file

@ -92,6 +92,9 @@ void AIPlayerCOAlienThink(AvHAIPlayer* pBot);
// Think routine for the deathmatch game mode (e.g. when playing CS maps)
void AIPlayerDMThink(AvHAIPlayer* pBot);
// What to do when the game hasn't OFFICIALLY ended, but basically is (i.e. one side has no hive/CC/infantry portal)
void AIPlayerEndMatchThink(AvHAIPlayer* pBot);
void TestNavThink(AvHAIPlayer* pBot);
void DroneThink(AvHAIPlayer* pBot);
void CustomThink(AvHAIPlayer* pBot);

View file

@ -636,6 +636,7 @@ void AIMGR_UpdateAIPlayers()
if (UpdateIndex > -1 && BotIndex >= UpdateIndex && NumBotsThinkThisFrame < BotsPerFrame)
{
AIPlayerThink(bot);
NumBotsThinkThisFrame++;
}
BotIndex++;
@ -1409,4 +1410,44 @@ bool AIMGR_HasMatchEnded()
bool bMatchExceededMaxLength = (GetGameRules()->GetGameTime() > MaxSeconds);
return (bMatchExceededMaxLength && AIMGR_GetNumActiveHumanPlayers() == 0);
}
bool AIMGR_IsMatchPracticallyOver()
{
if (!GetGameRules()->GetGameStarted() || GetGameRules()->GetMapMode() != MAP_MODE_NS) { return false; }
AvHTeamNumber TeamANumber = AIMGR_GetTeamANumber();
AvHTeamNumber TeamBNumber = AIMGR_GetTeamBNumber();
if (AIMGR_GetTeamType(TeamANumber) == AVH_CLASS_TYPE_ALIEN)
{
if (AITAC_GetNumTeamHives(TeamANumber, false) == 0) { return true; }
}
else
{
DeployableSearchFilter ChairFilter;
ChairFilter.DeployableTypes = STRUCTURE_MARINE_COMMCHAIR;
ChairFilter.DeployableTeam = TeamANumber;
ChairFilter.ReachabilityTeam = TeamANumber;
ChairFilter.ReachabilityFlags = AI_REACHABILITY_MARINE;
if (!AITAC_DeployableExistsAtLocation(ZERO_VECTOR, &ChairFilter)) { return true; }
}
if (AIMGR_GetTeamType(TeamBNumber) == AVH_CLASS_TYPE_ALIEN)
{
if (AITAC_GetNumTeamHives(TeamBNumber, false) == 0) { return true; }
}
else
{
DeployableSearchFilter ChairFilter;
ChairFilter.DeployableTypes = STRUCTURE_MARINE_COMMCHAIR;
ChairFilter.DeployableTeam = TeamBNumber;
ChairFilter.ReachabilityTeam = TeamBNumber;
ChairFilter.ReachabilityFlags = AI_REACHABILITY_MARINE;
if (!AITAC_DeployableExistsAtLocation(ZERO_VECTOR, &ChairFilter)) { return true; }
}
return false;
}

View file

@ -124,4 +124,6 @@ void AIMGR_UpdateAISystem();
bool AIMGR_HasMatchEnded();
bool AIMGR_IsMatchPracticallyOver();
#endif

View file

@ -1438,9 +1438,8 @@ BOOL AvHGamerules::ClientCommand( CBasePlayer *pPlayer, const char *pcmd )
for (auto it = AIPlayers.begin(); it != AIPlayers.end(); it++)
{
AvHAIPlayer* thisBot = (*it);
{
AITASK_SetMoveTask(thisBot, &thisBot->PrimaryBotTask, theAvHPlayer->pev->origin, true);
}
AITASK_SetMoveTask(thisBot, &thisBot->PrimaryBotTask, theAvHPlayer->pev->origin, true);
}
theSuccess = true;
@ -1455,6 +1454,101 @@ BOOL AvHGamerules::ClientCommand( CBasePlayer *pPlayer, const char *pcmd )
{
AIDEBUG_DrawOffMeshConnections(10.0f);
theSuccess = true;
}
else if (FStrEq(pcmd, "bot_evolveskulk"))
{
vector<AvHAIPlayer*> AIPlayers = AIMGR_GetAllAIPlayers();
for (auto it = AIPlayers.begin(); it != AIPlayers.end(); it++)
{
AvHAIPlayer* thisBot = (*it);
if (!IsPlayerAlien(thisBot->Edict) || !IsPlayerActiveInGame(thisBot->Edict)) { continue; }
AITASK_SetEvolveTask(thisBot, &thisBot->PrimaryBotTask, thisBot->CurrentFloorPosition, ALIEN_LIFEFORM_ONE, true);
}
theSuccess = true;
}
else if (FStrEq(pcmd, "bot_evolvegorge"))
{
vector<AvHAIPlayer*> AIPlayers = AIMGR_GetAllAIPlayers();
for (auto it = AIPlayers.begin(); it != AIPlayers.end(); it++)
{
AvHAIPlayer* thisBot = (*it);
if (!IsPlayerAlien(thisBot->Edict) || !IsPlayerActiveInGame(thisBot->Edict)) { continue; }
if (thisBot->Player->GetResources() < BALANCE_VAR(kGorgeCost))
{
thisBot->Player->GiveResources(BALANCE_VAR(kGorgeCost));
}
AITASK_SetEvolveTask(thisBot, &thisBot->PrimaryBotTask, thisBot->CurrentFloorPosition, ALIEN_LIFEFORM_TWO, true);
}
theSuccess = true;
}
else if (FStrEq(pcmd, "bot_evolvelerk"))
{
vector<AvHAIPlayer*> AIPlayers = AIMGR_GetAllAIPlayers();
for (auto it = AIPlayers.begin(); it != AIPlayers.end(); it++)
{
AvHAIPlayer* thisBot = (*it);
if (!IsPlayerAlien(thisBot->Edict) || !IsPlayerActiveInGame(thisBot->Edict)) { continue; }
if (thisBot->Player->GetResources() < BALANCE_VAR(kLerkCost))
{
thisBot->Player->GiveResources(BALANCE_VAR(kLerkCost));
}
AITASK_SetEvolveTask(thisBot, &thisBot->PrimaryBotTask, thisBot->CurrentFloorPosition, ALIEN_LIFEFORM_THREE, true);
}
theSuccess = true;
}
else if (FStrEq(pcmd, "bot_evolvefade"))
{
vector<AvHAIPlayer*> AIPlayers = AIMGR_GetAllAIPlayers();
for (auto it = AIPlayers.begin(); it != AIPlayers.end(); it++)
{
AvHAIPlayer* thisBot = (*it);
if (!IsPlayerAlien(thisBot->Edict) || !IsPlayerActiveInGame(thisBot->Edict)) { continue; }
if (thisBot->Player->GetResources() < BALANCE_VAR(kFadeCost))
{
thisBot->Player->GiveResources(BALANCE_VAR(kFadeCost));
}
AITASK_SetEvolveTask(thisBot, &thisBot->PrimaryBotTask, thisBot->CurrentFloorPosition, ALIEN_LIFEFORM_FOUR, true);
}
theSuccess = true;
}
else if (FStrEq(pcmd, "bot_evolveonos"))
{
vector<AvHAIPlayer*> AIPlayers = AIMGR_GetAllAIPlayers();
for (auto it = AIPlayers.begin(); it != AIPlayers.end(); it++)
{
AvHAIPlayer* thisBot = (*it);
if (!IsPlayerAlien(thisBot->Edict) || !IsPlayerActiveInGame(thisBot->Edict)) { continue; }
if (thisBot->Player->GetResources() < BALANCE_VAR(kOnosCost))
{
thisBot->Player->GiveResources(BALANCE_VAR(kOnosCost));
}
AITASK_SetEvolveTask(thisBot, &thisBot->PrimaryBotTask, thisBot->CurrentFloorPosition, ALIEN_LIFEFORM_FIVE, true);
}
theSuccess = true;
}
else if( FStrEq( pcmd, kcRemoveUpgrade) )