Start reimplementing alien AI

This commit is contained in:
RGreenlees 2024-01-09 22:05:06 +00:00 committed by pierow
parent 933ed063f0
commit 083f1ad3ac
17 changed files with 931 additions and 502 deletions

View file

@ -126,11 +126,11 @@ cvar_t avh_version = {kvVersion, "330", FCVAR_SERVER};
// AI Player Settings
cvar_t avh_botsenabled = { kvBotsEnabled,"0", FCVAR_SERVER }; // Bots can be added to the server Y/N
cvar_t avh_botautomode = { kvBotAutoMode,"0", FCVAR_SERVER }; // Defines automated behaviour for adding/removing bots
cvar_t avh_botminplayers = { kvBotMinPlayers,"0", FCVAR_SERVER }; // If bots are enabled and auto mode == 2 then it will maintain this player count by adding/removing as needed
cvar_t avh_botautomode = { kvBotAutoMode,"0", FCVAR_SERVER }; // Defines automated behaviour for adding/removing bots. 0 = manual (must add via console), 1 = automatic (auto-fills teams), 2 = balance only (only keeps teams even)
cvar_t avh_botminplayers = { kvBotMinPlayers,"0", FCVAR_SERVER }; // If bots are enabled and auto mode == 1 then it will maintain this player count by adding/removing as needed
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 }; // Defines automated behaviour for adding/removing bots
cvar_t avh_botcommandermode = { kvBotCommanderMode,"0", FCVAR_SERVER }; // 0 = Bots never command, 1 = Only if no humans on team, 2 = If nobody takes charge
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
//playtest cvars

View file

@ -74,7 +74,7 @@ bool AICOMM_DeployItem(AvHAIPlayer* pBot, const AvHAIDeployableItemType ItemToDe
bool AICOMM_ResearchTech(AvHAIPlayer* pBot, AvHAIBuildableStructure* StructureToResearch, AvHMessageID Research)
{
if (FNullEnt(StructureToResearch->edict)) { return false; }
if (!StructureToResearch || FNullEnt(StructureToResearch->edict)) { return false; }
// Don't do anything if the structure is being recycled, or we DON'T want to recycle but the structure is already busy
if (StructureToResearch->EntityRef->GetIsRecycling() || (Research != BUILD_RECYCLE && StructureToResearch->EntityRef->GetIsResearching())) { return false; }
@ -192,10 +192,15 @@ void AICOMM_AssignNewPlayerOrder(AvHAIPlayer* pBot, edict_t* Assignee, edict_t*
NewOrder.Assignee = Assignee;
NewOrder.OrderTarget = TargetEntity;
NewOrder.OrderPurpose = OrderPurpose;
AICOMM_IssueOrderForAssignedJob(pBot, &NewOrder);
NewOrder.LastReminderTime = 0.0f;
NewOrder.LastPlayerDistance = 0.0f;
pBot->ActiveOrders.push_back(NewOrder);
if (AICOMM_DoesPlayerOrderNeedReminder(pBot, &NewOrder))
{
AICOMM_IssueOrderForAssignedJob(pBot, &NewOrder);
}
}
void AICOMM_IssueOrderForAssignedJob(AvHAIPlayer* pBot, ai_commander_order* Order)
@ -405,11 +410,10 @@ void AICOMM_UpdatePlayerOrders(AvHAIPlayer* pBot)
if (SiegedHive)
{
int NumAssignedPlayers = AICOMM_GetNumPlayersAssignedToOrder(pBot, SiegedHive->HiveEntity->edict(), ORDERPURPOSE_SIEGE_HIVE);
int NumSiegingPlayers = AICOMM_GetNumPlayersAssignedToOrder(pBot, SiegedHive->HiveEntity->edict(), ORDERPURPOSE_SIEGE_HIVE);
if ((NumAssignedPlayers + NumSiegingPlayers) < DesiredPlayers)
if (NumAssignedPlayers < DesiredPlayers)
{
for (int i = 0; i < DesiredPlayers - (NumAssignedPlayers + NumSiegingPlayers); i++)
for (int i = 0; i < (DesiredPlayers - NumAssignedPlayers); i++)
{
edict_t* NewAssignee = AICOMM_GetPlayerWithNoOrderNearestLocation(pBot, SiegedHive->FloorLocation);
@ -425,6 +429,7 @@ void AICOMM_UpdatePlayerOrders(AvHAIPlayer* pBot)
AvHAIHiveDefinition* EmptyHive = nullptr;
float MinDist = 0.0f;
int MinNumAssignedPlayers = 0;
for (auto it = Hives.begin(); it != Hives.end(); it++)
{
@ -432,15 +437,15 @@ void AICOMM_UpdatePlayerOrders(AvHAIPlayer* pBot)
if (ThisHive->Status != HIVE_STATUS_UNBUILT) { continue; }
if (AICOMM_IsHiveFullySecured(pBot, ThisHive, false)) { continue; }
int NumPlayersSecuring = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), ThisHive->FloorLocation, UTIL_MetresToGoldSrcUnits(10.0f), false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER);
int NumAssignedPlayers = AICOMM_GetNumPlayersAssignedToOrder(pBot, ThisHive->HiveEntity->edict(), ORDERPURPOSE_SECURE_HIVE);
if ((NumPlayersSecuring + NumAssignedPlayers) < DesiredPlayers)
if (NumAssignedPlayers < DesiredPlayers)
{
float ThisDist = vDist2DSq(AITAC_GetCommChairLocation(pBot->Player->GetTeam()), ThisHive->Location);
if (!EmptyHive || ThisDist < MinDist)
{
MinNumAssignedPlayers = NumAssignedPlayers;
EmptyHive = ThisHive;
MinDist = ThisDist;
}
@ -449,11 +454,14 @@ void AICOMM_UpdatePlayerOrders(AvHAIPlayer* pBot)
if (EmptyHive)
{
edict_t* NewAssignee = AICOMM_GetPlayerWithNoOrderNearestLocation(pBot, EmptyHive->FloorLocation);
if (!FNullEnt(NewAssignee))
for (int i = 0; i < (DesiredPlayers - MinNumAssignedPlayers); i++)
{
AICOMM_AssignNewPlayerOrder(pBot, NewAssignee, EmptyHive->HiveEntity->edict(), ORDERPURPOSE_SECURE_HIVE);
edict_t* NewAssignee = AICOMM_GetPlayerWithNoOrderNearestLocation(pBot, EmptyHive->FloorLocation);
if (!FNullEnt(NewAssignee))
{
AICOMM_AssignNewPlayerOrder(pBot, NewAssignee, EmptyHive->HiveEntity->edict(), ORDERPURPOSE_SECURE_HIVE);
}
}
}
@ -466,10 +474,9 @@ void AICOMM_UpdatePlayerOrders(AvHAIPlayer* pBot)
if (ResNode)
{
int NumPlayersSecuring = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), ResNode->Location, UTIL_MetresToGoldSrcUnits(5.0f), false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER);
int NumAssignedPlayers = AICOMM_GetNumPlayersAssignedToOrder(pBot, ResNode->ResourceEntity->edict(), ORDERPURPOSE_SECURE_RESNODE);
if ((NumPlayersSecuring + NumAssignedPlayers) < 1)
if (NumAssignedPlayers < 1)
{
edict_t* NewAssignee = AICOMM_GetPlayerWithNoOrderNearestLocation(pBot, ResNode->Location);
@ -763,7 +770,7 @@ bool AICOMM_IsRequestValid(ai_commander_request* Request)
return true;
}
bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot, commander_action* Action)
bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot)
{
AvHTeamNumber TeamNumber = pBot->Player->GetTeam();
@ -784,7 +791,7 @@ bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot, commander_action* Action)
if (NumInfantryPortals < 2)
{
if (AICOMM_BuildInfantryPortal(pBot, CommChair, Action))
if (AICOMM_BuildInfantryPortal(pBot, CommChair))
{
return true;
}
@ -872,7 +879,7 @@ bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot, commander_action* Action)
if (HiveToSecure)
{
if (AICOMM_PerformNextSecureHiveAction(pBot, HiveToSecure, Action))
if (AICOMM_PerformNextSecureHiveAction(pBot, HiveToSecure))
{
return true;
}
@ -924,7 +931,7 @@ bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot, commander_action* Action)
if (HiveToSiege)
{
if (AICOMM_PerformNextSiegeHiveAction(pBot, HiveToSiege, Action))
if (AICOMM_PerformNextSiegeHiveAction(pBot, HiveToSiege))
{
return true;
}
@ -1001,10 +1008,63 @@ bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot, commander_action* Action)
return false;
}
bool AICOMM_CheckForNextResearchAction(AvHAIPlayer* pBot, commander_action* Action)
bool AICOMM_CheckForNextResearchAction(AvHAIPlayer* pBot)
{
AvHTeamNumber CommanderTeam = pBot->Player->GetTeam();
vector<AvHAIHiveDefinition*> Hives = AITAC_GetAllHives();
for (auto it = Hives.begin(); it != Hives.end(); it++)
{
AvHAIHiveDefinition* Hive = (*it);
if (Hive->Status != HIVE_STATUS_UNBUILT) { continue; }
DeployableSearchFilter TFFilter;
TFFilter.DeployableTeam = pBot->Player->GetTeam();
TFFilter.DeployableTypes = STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY;
TFFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
TFFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING | STRUCTURE_STATUS_ELECTRIFIED;
TFFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f);
AvHAIBuildableStructure* NearestTF = AITAC_FindClosestDeployableToLocation(Hive->FloorLocation, &TFFilter);
if (NearestTF)
{
TFFilter.DeployableTypes = STRUCTURE_MARINE_TURRET;
TFFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f);
int NumTurrets = AITAC_GetNumDeployablesNearLocation(NearestTF->Location, &TFFilter);
if (NumTurrets > 0)
{
if (AICOMM_ResearchTech(pBot, NearestTF, RESEARCH_ELECTRICAL))
{
return true;
}
}
}
if (pBot->Player->GetResources() > 60)
{
if (Hive->HiveResNodeRef && Hive->HiveResNodeRef->OwningTeam == pBot->Player->GetTeam())
{
edict_t* Tower = Hive->HiveResNodeRef->ActiveTowerEntity;
if (!FNullEnt(Tower) && UTIL_StructureIsFullyBuilt(Tower) && !UTIL_IsStructureElectrified(Tower))
{
AvHAIBuildableStructure* ResTower = AITAC_GetDeployableRefFromEdict(Tower);
if (ResTower && AICOMM_ResearchTech(pBot, ResTower, RESEARCH_ELECTRICAL))
{
return true;
}
}
}
}
}
DeployableSearchFilter StructureFilter;
StructureFilter.DeployableTeam = CommanderTeam;
StructureFilter.ReachabilityTeam = CommanderTeam;
@ -1252,7 +1312,7 @@ const AvHAIResourceNode* AICOMM_GetNearestResourceNodeCapOpportunity(const AvHTe
return Result;
}
bool AICOMM_PerformNextSiegeHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinition* HiveToSiege, commander_action* Action)
bool AICOMM_PerformNextSiegeHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinition* HiveToSiege)
{
AvHTeamNumber CommanderTeam = pBot->Player->GetTeam();
@ -1385,7 +1445,7 @@ bool AICOMM_PerformNextSiegeHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinit
}
bool AICOMM_PerformNextSecureHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinition* HiveToSecure, commander_action* Action)
bool AICOMM_PerformNextSecureHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinition* HiveToSecure)
{
DeployableSearchFilter StructureFilter;
StructureFilter.DeployableTypes = STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY | STRUCTURE_MARINE_PHASEGATE;
@ -1481,16 +1541,10 @@ bool AICOMM_PerformNextSecureHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefini
return false;
}
if (!UTIL_IsStructureElectrified(ExistingTF->edict))
{
return AICOMM_ResearchTech(pBot, ExistingTF, RESEARCH_ELECTRICAL);
}
return false;
}
bool AICOMM_BuildInfantryPortal(AvHAIPlayer* pBot, edict_t* CommChair, commander_action* Action)
bool AICOMM_BuildInfantryPortal(AvHAIPlayer* pBot, edict_t* CommChair)
{
if (FNullEnt(CommChair) || !UTIL_StructureIsFullyBuilt(CommChair)) { return false; }
@ -1840,8 +1894,8 @@ void AICOMM_CommanderThink(AvHAIPlayer* pBot)
if (AICOMM_CheckForNextRecycleAction(pBot)) { return; }
if (AICOMM_CheckForNextSupportAction(pBot)) { return; }
if (AICOMM_CheckForNextBuildAction(pBot, &pBot->BuildAction)) { return; }
if (AICOMM_CheckForNextResearchAction(pBot, &pBot->ResearchAction)) { return; }
if (AICOMM_CheckForNextBuildAction(pBot)) { return; }
if (AICOMM_CheckForNextResearchAction(pBot)) { return; }
}
bool AICOMM_IsCommanderActionValid(AvHAIPlayer* pBot, commander_action* Action)
@ -1931,7 +1985,7 @@ const AvHAIHiveDefinition* AICOMM_GetEmptyHiveOpportunityNearestLocation(AvHAIPl
if (Hive->Status != HIVE_STATUS_UNBUILT) { continue; }
if (AICOMM_IsHiveFullySecured(CommanderBot, Hive, true)) { continue; }
if (AICOMM_IsHiveFullySecured(CommanderBot, Hive, false)) { continue; }
Vector SecureLocation = Hive->FloorLocation;

View file

@ -35,10 +35,10 @@ bool AICOMM_DoesPlayerOrderNeedReminder(AvHAIPlayer* pBot, ai_commander_order* O
void AICOMM_IssueOrderForAssignedJob(AvHAIPlayer* pBot, ai_commander_order* Order);
void AICOMM_ClearAction(commander_action* Action);
bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot, commander_action* Action);
bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot);
bool AICOMM_CheckForNextSupportAction(AvHAIPlayer* pBot);
bool AICOMM_CheckForNextRecycleAction(AvHAIPlayer* pBot);
bool AICOMM_CheckForNextResearchAction(AvHAIPlayer* pBot, commander_action* Action);
bool AICOMM_CheckForNextResearchAction(AvHAIPlayer* pBot);
void AICOMM_SetDropHealthAction(AvHAIPlayer* pBot, commander_action* Action, edict_t* Recipient);
void AICOMM_SetDropAmmoAction(AvHAIPlayer* pBot, commander_action* Action, edict_t* Recipient);
void AICOMM_SetDeployStructureAction(AvHAIPlayer* pBot, commander_action* Action, AvHAIDeployableStructureType StructureToBuild, const Vector Location, bool bIsUrgent);
@ -48,9 +48,9 @@ void AICOMM_CommanderThink(AvHAIPlayer* pBot);
const AvHAIHiveDefinition* AICOMM_GetEmptyHiveOpportunityNearestLocation(AvHAIPlayer* CommanderBot, const Vector SearchLocation);
bool AICOMM_BuildInfantryPortal(AvHAIPlayer* pBot, edict_t* CommChair, commander_action* Action);
bool AICOMM_PerformNextSiegeHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinition* HiveToSiege, commander_action* Action);
bool AICOMM_PerformNextSecureHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinition* HiveToSecure, commander_action* Action);
bool AICOMM_BuildInfantryPortal(AvHAIPlayer* pBot, edict_t* CommChair);
bool AICOMM_PerformNextSiegeHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinition* HiveToSiege);
bool AICOMM_PerformNextSecureHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinition* HiveToSecure);
ai_commander_request* AICOMM_GetExistingRequestForPlayer(AvHAIPlayer* pBot, edict_t* Requestor);
void AICOMM_CheckNewRequests(AvHAIPlayer* pBot);

View file

@ -204,7 +204,7 @@ typedef enum _AVHAIBOTROLE
typedef struct _OFF_MESH_CONN
{
unsigned int ConnectionRefs[2];
unsigned short ConnectionFlags = 0;
unsigned int ConnectionFlags = 0;
Vector FromLocation = g_vecZero;
Vector ToLocation = g_vecZero;
edict_t* TargetObject = nullptr;
@ -379,6 +379,14 @@ typedef enum
}
BotAttackResult;
typedef enum
{
BUILD_ATTEMPT_NONE = 0,
BUILD_ATTEMPT_PENDING,
BUILD_ATTEMPT_SUCCESS,
BUILD_ATTEMPT_FAILED
} BotBuildAttemptStatus;
// Bot path node. A path will be several of these strung together to lead the bot to its destination
typedef struct _BOT_PATH_NODE
@ -386,7 +394,7 @@ typedef struct _BOT_PATH_NODE
Vector FromLocation = g_vecZero; // Location to move from
Vector Location = g_vecZero; // Location to move to
float requiredZ = 0.0f; // If climbing a up ladder or wall, how high should they aim to get before dismounting.
unsigned short flag = 0; // Is this a ladder movement, wall climb, walk etc
unsigned int flag = 0; // Is this a ladder movement, wall climb, walk etc
unsigned char area = 0; // Is this a crouch area, normal walking area etc
unsigned int poly = 0; // The nav mesh poly this point resides on
} bot_path_node;
@ -447,6 +455,16 @@ typedef struct _AVH_AI_PLAYER_TASK
float TaskLength = 0.0f; // If a task has gone on longer than this time, it will be considered completed
} AvHAIPlayerTask;
typedef struct _AVH_AI_BUILD_ATTEMPT
{
AvHAIDeployableStructureType AttemptedStructureType = STRUCTURE_NONE;
Vector AttemptedLocation = g_vecZero;
int NumAttempts = 0;
BotBuildAttemptStatus BuildStatus = BUILD_ATTEMPT_NONE;
float BuildAttemptTime = 0.0f;
AvHAIBuildableStructure* LinkedStructure = nullptr;
} AvHAIBuildAttempt;
// Contains the bot's current navigation info, such as current path
typedef struct _NAV_STATUS
{
@ -486,7 +504,7 @@ typedef struct _NAV_STATUS
BotMoveStyle MoveStyle = MOVESTYLE_NORMAL; // Current desired move style (e.g. normal, ambush, hide). Will trigger new path calculations if this changes
float LastPathCalcTime = 0.0f; // When the bot last calculated a path, to limit how frequently it can recalculate
bool bPendingRecalculation = false; // This bot should recalculate its path as soon as it can
float NextForceRecalc = 0.0f; // If set, then the bot will force-recalc its current path
bool bZig; // Is the bot zigging, or zagging?
float NextZigTime; // Controls how frequently they zig or zag
@ -496,7 +514,7 @@ typedef struct _NAV_STATUS
nav_profile NavProfile;
bool bNavProfileChanged = false;
unsigned short SpecialMovementFlags = 0; // Any special movement flags required for this path (e.g. needs a welder, needs a jetpack etc.)
unsigned int SpecialMovementFlags = 0; // Any special movement flags required for this path (e.g. needs a welder, needs a jetpack etc.)
} nav_status;
@ -629,11 +647,7 @@ typedef struct AVH_AI_PLAYER
nav_status BotNavInfo; // Bot's movement information, their current path, where in the path they are etc.
commander_action BuildAction;
commander_action ResearchAction;
commander_action SupportAction;
commander_action RecycleAction;
commander_action* CurrentAction;
AvHAIBuildAttempt BuildAttempts;
vector<ai_commander_request> ActiveRequests;
vector<ai_commander_order> ActiveOrders;

View file

@ -223,6 +223,25 @@ bool IsEdictStructure(const edict_t* edict)
return (GetDeployableObjectTypeFromEdict(edict) != STRUCTURE_NONE);
}
bool IsDamagingStructure(const edict_t* StructureEdict)
{
return IsDamagingStructure(GetStructureTypeFromEdict(StructureEdict));
}
bool IsDamagingStructure(AvHAIDeployableStructureType StructureType)
{
switch (StructureType)
{
case STRUCTURE_ALIEN_OFFENCECHAMBER:
case STRUCTURE_MARINE_TURRETFACTORY:
return true;
default:
return false;
}
return false;
}
AvHAIDeployableStructureType GetStructureTypeFromEdict(const edict_t* StructureEdict)
{
if (FNullEnt(StructureEdict)) { return STRUCTURE_NONE; }

View file

@ -28,6 +28,11 @@ bool IsEdictStructure(const edict_t* edict);
AvHAIDeployableStructureType GetStructureTypeFromEdict(const edict_t* StructureEdict);
// Returns true if this structure shoots back (turret or offence chamber)
bool IsDamagingStructure(const edict_t* StructureEdict);
// Returns true if this structure shoots back (turret or offence chamber)
bool IsDamagingStructure(AvHAIDeployableStructureType StructureType);
bool GetNearestMapLocationAtPoint(vec3_t SearchLocation, string& outLocation);
AvHAIDeployableStructureType GetDeployableObjectTypeFromEdict(const edict_t* StructureEdict);

View file

@ -126,7 +126,7 @@ struct OffMeshConnectionDef
bool bBiDir = false;
float Rad = 0.0f;
unsigned char Area = 0;
unsigned short Flag = 0;
unsigned int Flag = 0;
bool bPendingDelete = false;
bool bDirty = false;
};
@ -244,13 +244,13 @@ struct MeshProcess : public dtTileCacheMeshProcess
}
else if (polyAreas[i] == DT_TILECACHE_TEAM1STRUCTURE_AREA)
{
polyAreas[i] = SAMPLE_POLYAREA_BLOCKED;
polyFlags[i] = SAMPLE_POLYFLAGS_BLOCKED | SAMPLE_POLYFLAGS_TEAM1STRUCTURE;
polyAreas[i] = SAMPLE_POLYAREA_STRUCTUREBLOCK;
polyFlags[i] = SAMPLE_POLYFLAGS_TEAM1STRUCTURE;
}
else if (polyAreas[i] == DT_TILECACHE_TEAM2STRUCTURE_AREA)
{
polyAreas[i] = SAMPLE_POLYAREA_BLOCKED;
polyFlags[i] = SAMPLE_POLYFLAGS_BLOCKED | SAMPLE_POLYFLAGS_TEAM2STRUCTURE;
polyAreas[i] = SAMPLE_POLYAREA_STRUCTUREBLOCK;
polyFlags[i] = SAMPLE_POLYFLAGS_TEAM2STRUCTURE;
}
else if (polyAreas[i] == DT_TILECACHE_WELD_AREA)
{
@ -431,7 +431,7 @@ unsigned int UTIL_AddTemporaryObstacle(unsigned int NavMeshIndex, const Vector L
ObstacleNum = (unsigned int)ObsRef;
if (ObstacleNum > 0)
if (ObstacleNum > 0 && NavMeshIndex != BUILDING_NAV_MESH)
{
bNavMeshModified = true;
}
@ -894,6 +894,7 @@ void UTIL_PopulateBaseNavProfiles()
BaseNavProfiles[MARINE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_OBSTRUCTION, 2.0f);
BaseNavProfiles[MARINE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_CROUCH, 2.0f);
BaseNavProfiles[MARINE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_BLOCKED, 2.0f);
BaseNavProfiles[MARINE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_STRUCTUREBLOCK, 20.0f);
BaseNavProfiles[MARINE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_FALLDAMAGE, 10.0f);
BaseNavProfiles[MARINE_BASE_NAV_PROFILE].Filters.setIncludeFlags(SAMPLE_POLYFLAGS_ALL);
BaseNavProfiles[MARINE_BASE_NAV_PROFILE].Filters.removeIncludeFlags(SAMPLE_POLYFLAGS_FLY | SAMPLE_POLYFLAGS_WALLCLIMB | SAMPLE_POLYFLAGS_WELD);
@ -906,6 +907,8 @@ void UTIL_PopulateBaseNavProfiles()
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_GROUND, 1.0f);
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_CROUCH, 1.0f);
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_OBSTRUCTION, 2.0f);
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_BLOCKED, 2.0f);
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_STRUCTUREBLOCK, 20.0f);
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setIncludeFlags(SAMPLE_POLYFLAGS_ALL);
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.removeIncludeFlags(SAMPLE_POLYFLAGS_TEAM1PHASEGATE | SAMPLE_POLYFLAGS_TEAM2PHASEGATE | SAMPLE_POLYFLAGS_DUCKJUMP | SAMPLE_POLYFLAGS_WELD | SAMPLE_POLYFLAGS_FLY);
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setExcludeFlags(0);
@ -919,6 +922,7 @@ void UTIL_PopulateBaseNavProfiles()
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_OBSTRUCTION, 2.0f);
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_BLOCKED, 2.0f);
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_FALLDAMAGE, 10.0f);
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_STRUCTUREBLOCK, 20.0f);
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.setIncludeFlags(SAMPLE_POLYFLAGS_ALL);
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.setExcludeFlags(0);
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.removeIncludeFlags(SAMPLE_POLYFLAGS_WALLCLIMB);
@ -934,6 +938,8 @@ void UTIL_PopulateBaseNavProfiles()
BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_GROUND, 1.0f);
BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_CROUCH, 1.0f);
BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_OBSTRUCTION, 2.0f);
BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_BLOCKED, 2.0f);
BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_STRUCTUREBLOCK, 20.0f);
BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.setIncludeFlags(SAMPLE_POLYFLAGS_ALL);
BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.setExcludeFlags(0);
BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.removeIncludeFlags(SAMPLE_POLYFLAGS_TEAM1PHASEGATE);
@ -947,6 +953,8 @@ void UTIL_PopulateBaseNavProfiles()
BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_CROUCH, 1.0f);
BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_OBSTRUCTION, 2.0f);
BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_CROUCH, 1.5f);
BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_BLOCKED, 2.0f);
BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_STRUCTUREBLOCK, 20.0f);
BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setIncludeFlags(SAMPLE_POLYFLAGS_ALL);
BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setExcludeFlags(0);
BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.removeIncludeFlags(SAMPLE_POLYFLAGS_TEAM1PHASEGATE);
@ -961,6 +969,8 @@ void UTIL_PopulateBaseNavProfiles()
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_OBSTRUCTION, 2.0f);
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_CROUCH, 2.0f);
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_FALLDAMAGE, 10.0f);
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_BLOCKED, 2.0f);
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_STRUCTUREBLOCK, 5.0f); // Onos is a wrecking machine, structures shouldn't be such an obstacle for them!
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.setIncludeFlags(SAMPLE_POLYFLAGS_ALL);
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.setExcludeFlags(0);
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.removeIncludeFlags(SAMPLE_POLYFLAGS_WALLCLIMB);
@ -1360,7 +1370,7 @@ dtStatus FindFlightPathToPoint(const nav_profile &NavProfile, Vector FromLocatio
m_navMesh->getPolyArea(StraightPolyPath[0], &CurrArea);
m_navMesh->getPolyFlags(StraightPolyPath[0], &CurrFlags);
CurrFlags &= ~(SAMPLE_POLYFLAGS_TEAM1STRUCTURE | SAMPLE_POLYFLAGS_TEAM2STRUCTURE | SAMPLE_POLYFLAGS_NOONOS);
CurrFlags &= ~(SAMPLE_POLYFLAGS_NOONOS);
// At this point we have our path. Copy it to the path store
int nIndex = 0;
@ -1380,7 +1390,7 @@ dtStatus FindFlightPathToPoint(const nav_profile &NavProfile, Vector FromLocatio
m_navMesh->getPolyArea(StraightPolyPath[nVert], &ThisArea);
m_navMesh->getPolyFlags(StraightPolyPath[nVert], &ThisFlags);
ThisFlags &= ~(SAMPLE_POLYFLAGS_TEAM1STRUCTURE | SAMPLE_POLYFLAGS_TEAM2STRUCTURE);
ThisFlags &= ~(SAMPLE_POLYFLAGS_NOONOS);
if (ThisArea == SAMPLE_POLYAREA_GROUND || ThisArea == SAMPLE_POLYAREA_CROUCH)
{
@ -1605,7 +1615,7 @@ dtStatus FindPathClosestToPoint(const nav_profile& NavProfile, const Vector From
m_navMesh->getPolyFlags(StraightPolyPath[0], &CurrFlags);
m_navMesh->getPolyArea(StraightPolyPath[0], &CurrArea);
CurrFlags &= ~(SAMPLE_POLYFLAGS_TEAM1STRUCTURE | SAMPLE_POLYFLAGS_TEAM2STRUCTURE | SAMPLE_POLYFLAGS_NOONOS);
CurrFlags &= ~(SAMPLE_POLYFLAGS_NOONOS);
// At this point we have our path. Copy it to the path store
int nIndex = 0;
@ -1627,7 +1637,7 @@ dtStatus FindPathClosestToPoint(const nav_profile& NavProfile, const Vector From
m_navMesh->getPolyArea(StraightPolyPath[nVert], &ThisArea);
m_navMesh->getPolyFlags(StraightPolyPath[nVert], &ThisFlags);
ThisFlags &= ~(SAMPLE_POLYFLAGS_TEAM1STRUCTURE | SAMPLE_POLYFLAGS_TEAM2STRUCTURE | SAMPLE_POLYFLAGS_NOONOS);
ThisFlags &= ~(SAMPLE_POLYFLAGS_NOONOS);
if (ThisArea == SAMPLE_POLYAREA_GROUND || ThisArea == SAMPLE_POLYAREA_CROUCH)
{
@ -1774,7 +1784,7 @@ dtStatus FindPathClosestToPoint(AvHAIPlayer* pBot, const BotMoveStyle MoveStyle,
m_navMesh->getPolyFlags(StraightPolyPath[0], &CurrFlags);
m_navMesh->getPolyArea(StraightPolyPath[0], &CurrArea);
CurrFlags &= ~(SAMPLE_POLYFLAGS_TEAM1STRUCTURE | SAMPLE_POLYFLAGS_TEAM2STRUCTURE | SAMPLE_POLYFLAGS_NOONOS);
CurrFlags &= ~(SAMPLE_POLYFLAGS_NOONOS);
// At this point we have our path. Copy it to the path store
int nIndex = 0;
@ -1861,7 +1871,7 @@ dtStatus FindPathClosestToPoint(AvHAIPlayer* pBot, const BotMoveStyle MoveStyle,
m_navMesh->getPolyFlags(StraightPolyPath[nVert], &CurrFlags);
m_navMesh->getPolyArea(StraightPolyPath[nVert], &CurrArea);
CurrFlags &= ~(SAMPLE_POLYFLAGS_TEAM1STRUCTURE | SAMPLE_POLYFLAGS_TEAM2STRUCTURE | SAMPLE_POLYFLAGS_NOONOS);
CurrFlags &= ~(SAMPLE_POLYFLAGS_NOONOS);
NodeFromLocation = NextPathNode.Location;
@ -1971,6 +1981,8 @@ bool HasBotReachedPathPoint(const AvHAIPlayer* pBot)
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:
@ -2019,7 +2031,7 @@ bool HasBotReachedPathPoint(const AvHAIPlayer* pBot)
void CheckAndHandleDoorObstruction(AvHAIPlayer* pBot)
{
edict_t* BlockingDoorEdict = UTIL_GetDoorBlockingPathPoint(pBot->Edict->v.origin, pBot->BotNavInfo.CurrentPathPoint->Location, SAMPLE_POLYAREA_GROUND, nullptr);
edict_t* BlockingDoorEdict = UTIL_GetDoorBlockingPathPoint(pBot->Edict->v.origin, pBot->BotNavInfo.CurrentPathPoint->Location, pBot->BotNavInfo.CurrentPathPoint->flag, nullptr);
if (FNullEnt(BlockingDoorEdict))
{
@ -2114,7 +2126,7 @@ void CheckAndHandleDoorObstruction(AvHAIPlayer* pBot)
// Door must be shot to open
if (Door->ActivationType == DOOR_SHOOT)
{
BotAttackTarget(pBot, Door->DoorEdict);
BotAttackNonPlayerTarget(pBot, Door->DoorEdict);
return;
}
@ -2386,7 +2398,7 @@ edict_t* UTIL_GetBreakableBlockingPathPoint(AvHAIPlayer* pBot, bot_path_node* Pa
return nullptr;
}
edict_t* UTIL_GetBreakableBlockingPathPoint(AvHAIPlayer* pBot, const Vector FromLocation, const Vector ToLocation, const unsigned short MovementFlag, edict_t* SearchBreakable)
edict_t* UTIL_GetBreakableBlockingPathPoint(AvHAIPlayer* pBot, const Vector FromLocation, const Vector ToLocation, const unsigned int MovementFlag, edict_t* SearchBreakable)
{
Vector FromLoc = FromLocation;
Vector ToLoc = ToLocation;
@ -2495,7 +2507,7 @@ edict_t* UTIL_GetBreakableBlockingPathPoint(AvHAIPlayer* pBot, const Vector From
return nullptr;
}
edict_t* UTIL_GetDoorBlockingPathPoint(const Vector FromLocation, const Vector ToLocation, const unsigned short MovementFlag, edict_t* SearchDoor)
edict_t* UTIL_GetDoorBlockingPathPoint(const Vector FromLocation, const Vector ToLocation, const unsigned int MovementFlag, edict_t* SearchDoor)
{
Vector FromLoc = FromLocation;
@ -2839,6 +2851,10 @@ void NewMove(AvHAIPlayer* pBot)
case SAMPLE_POLYFLAGS_BLOCKED:
BlockedMove(pBot, MoveFrom, MoveTo);
break;
case SAMPLE_POLYFLAGS_TEAM1STRUCTURE:
case SAMPLE_POLYFLAGS_TEAM2STRUCTURE:
StructureBlockedMove(pBot, MoveFrom, MoveTo);
break;
case SAMPLE_POLYFLAGS_WALLCLIMB:
{
if (IsPlayerSkulk(pBot->Edict))
@ -3024,6 +3040,50 @@ void FallMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint)
}
}
void StructureBlockedMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint)
{
Vector vForward = UTIL_GetVectorNormal2D(EndPoint - StartPoint);
pBot->desiredMovementDir = vForward;
DeployableSearchFilter BlockingFilter;
BlockingFilter.DeployableTeam = AIMGR_GetEnemyTeam(pBot->Player->GetTeam());
BlockingFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(3.0f);
vector<AvHAIBuildableStructure*> BlockingStructures = AITAC_FindAllDeployables(pBot->Edict->v.origin, &BlockingFilter);
AvHAIBuildableStructure* CulpritStructure = nullptr;
float MinDist = 0.0f;
for (auto it = BlockingStructures.begin(); it != BlockingStructures.end(); it++)
{
AvHAIBuildableStructure* ThisStructure = (*it);
float ThisDist = vDistanceFromLine2DSq(StartPoint, EndPoint, ThisStructure->Location);
if (!CulpritStructure || ThisDist < MinDist)
{
CulpritStructure = ThisStructure;
}
}
if (CulpritStructure)
{
BotMoveLookAt(pBot, CulpritStructure->Location);
AvHAIWeapon AttackWeapon = (IsPlayerAlien(pBot->Edict)) ? BotAlienChooseBestWeaponForStructure(pBot, CulpritStructure->edict) : BotMarineChooseBestWeaponForStructure(pBot, CulpritStructure->edict);
if (GetPlayerCurrentWeapon(pBot->Player) != AttackWeapon)
{
pBot->DesiredMoveWeapon = AttackWeapon;
}
else
{
BotShootTarget(pBot, AttackWeapon, CulpritStructure->edict);
}
}
}
void BlockedMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint)
{
@ -4876,7 +4936,7 @@ bool AbortCurrentMove(AvHAIPlayer* pBot, const Vector NewDestination)
Vector MoveFrom = pBot->BotNavInfo.CurrentPathPoint->FromLocation;
Vector MoveTo = pBot->BotNavInfo.CurrentPathPoint->Location;
unsigned short flag = pBot->BotNavInfo.CurrentPathPoint->flag;
unsigned int flag = pBot->BotNavInfo.CurrentPathPoint->flag;
Vector ClosestPointOnLine = vClosestPointOnLine2D(MoveFrom, MoveTo, pBot->Edict->v.origin);
@ -4893,7 +4953,7 @@ bool AbortCurrentMove(AvHAIPlayer* pBot, const Vector NewDestination)
if (flag == SAMPLE_POLYFLAGS_WALK)
{
if (UTIL_PointIsDirectlyReachable(pBot->Edict->v.origin, MoveFrom) || UTIL_PointIsDirectlyReachable(pBot->Edict->v.origin, MoveTo))
if (UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, MoveFrom) || UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, MoveTo))
{
return true;
}
@ -5040,31 +5100,37 @@ void SetBaseNavProfile(AvHAIPlayer* pBot)
void UpdateBotMoveProfile(AvHAIPlayer* pBot, BotMoveStyle MoveStyle)
{
if (IsPlayerMarine(pBot->Player))
{
MarineUpdateBotMoveProfile(pBot, MoveStyle);
return;
}
switch (pBot->Edict->v.iuser3)
{
case AVH_USER3_MARINE_PLAYER:
MarineUpdateBotMoveProfile(pBot, MoveStyle);
break;
case AVH_USER3_ALIEN_PLAYER1:
SkulkUpdateBotMoveProfile(pBot, MoveStyle);
return;
break;
case AVH_USER3_ALIEN_PLAYER2:
GorgeUpdateBotMoveProfile(pBot, MoveStyle);
return;
break;
case AVH_USER3_ALIEN_PLAYER3:
LerkUpdateBotMoveProfile(pBot, MoveStyle);
return;
break;
case AVH_USER3_ALIEN_PLAYER4:
FadeUpdateBotMoveProfile(pBot, MoveStyle);
return;
break;
case AVH_USER3_ALIEN_PLAYER5:
OnosUpdateBotMoveProfile(pBot, MoveStyle);
return;
break;
}
if (pBot->Player->GetTeam() == GetGameRules()->GetTeamANumber())
{
pBot->BotNavInfo.NavProfile.Filters.removeExcludeFlags(SAMPLE_POLYFLAGS_TEAM2STRUCTURE);
pBot->BotNavInfo.NavProfile.Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM1STRUCTURE);
}
else
{
pBot->BotNavInfo.NavProfile.Filters.removeExcludeFlags(SAMPLE_POLYFLAGS_TEAM1STRUCTURE);
pBot->BotNavInfo.NavProfile.Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM2STRUCTURE);
}
}
@ -5303,6 +5369,8 @@ bool MoveTo(AvHAIPlayer* pBot, const Vector Destination, const BotMoveStyle Move
bool bIsFlyingProfile = pBot->BotNavInfo.NavProfile.bFlyingProfile;
bool bForceRecalculation = (pBot->BotNavInfo.NextForceRecalc > 0.0f && gpGlobals->time >= pBot->BotNavInfo.NextForceRecalc);
if (BotNavInfo->CurrentPath.size() > 0)
{
if (pBot->BotNavInfo.CurrentPathPoint == pBot->BotNavInfo.CurrentPath.end())
@ -5327,10 +5395,10 @@ bool MoveTo(AvHAIPlayer* pBot, const Vector Destination, const BotMoveStyle Move
}
bool bUltimateDestinationChanged = !vEquals(Destination, BotNavInfo->TargetDestination, GetPlayerRadius(pBot->Player)) && !vEquals(Destination, MoveTaskDestination) && !vEquals(Destination, MoveTaskOrigin) && !vEquals(Destination, MoveSecondaryOrigin);
bool bHasReachedDestination = BotIsAtLocation(pBot, BotNavInfo->TargetDestination);
if (bUltimateDestinationChanged || bNavProfileChanged || bHasReachedDestination)
if (bUltimateDestinationChanged || bNavProfileChanged || bHasReachedDestination || bForceRecalculation)
{
// First abort our current move so we don't try to recalculate half-way up a wall or ladder
if (bIsFlyingProfile || AbortCurrentMove(pBot, Destination))
@ -5363,7 +5431,7 @@ bool MoveTo(AvHAIPlayer* pBot, const Vector Destination, const BotMoveStyle Move
bool bCanRecalculatePath = (gpGlobals->time - pBot->BotNavInfo.LastPathCalcTime > MIN_PATH_RECALC_TIME);
// Only recalculate the path if there isn't a path, or something has changed and enough time has elapsed since the last path calculation
bool bShouldCalculatePath = bCanRecalculatePath && (BotNavInfo->CurrentPath.size() == 0 || !vEquals(Destination, BotNavInfo->PathDestination));
bool bShouldCalculatePath = bCanRecalculatePath && (bForceRecalculation || BotNavInfo->CurrentPath.size() == 0 || !vEquals(Destination, BotNavInfo->PathDestination));
if (bShouldCalculatePath)
{
@ -5374,8 +5442,8 @@ bool MoveTo(AvHAIPlayer* pBot, const Vector Destination, const BotMoveStyle Move
}
pBot->BotNavInfo.LastPathCalcTime = gpGlobals->time;
BotNavInfo->bPendingRecalculation = false;
BotNavInfo->bNavProfileChanged = false;
BotNavInfo->NextForceRecalc = 0.0f;
if (vIsZero(BotNavInfo->TargetDestination))
{
@ -5831,7 +5899,7 @@ void BotFollowPath(AvHAIPlayer* pBot)
Vector MoveTo = BotNavInfo->CurrentPathPoint->Location;
unsigned short CurrentFlag = BotNavInfo->CurrentPathPoint->flag;
unsigned int CurrentFlag = BotNavInfo->CurrentPathPoint->flag;
bool bIsUsingPhaseGate = (CurrentFlag == SAMPLE_POLYFLAGS_TEAM1PHASEGATE || CurrentFlag == SAMPLE_POLYFLAGS_TEAM2PHASEGATE);
@ -7882,7 +7950,7 @@ nav_door* UTIL_GetClosestLiftToPoints(const Vector StartPoint, const Vector EndP
return Result;
}
void UTIL_AddOffMeshConnection(Vector StartLoc, Vector EndLoc, unsigned char area, unsigned short flags, bool bBiDirectional, AvHAIOffMeshConnection* RemoveConnectionDef)
void UTIL_AddOffMeshConnection(Vector StartLoc, Vector EndLoc, unsigned char area, unsigned int flags, bool bBiDirectional, AvHAIOffMeshConnection* RemoveConnectionDef)
{
Vector ConnStart, ConnEnd;

View file

@ -37,12 +37,14 @@ constexpr auto MAX_PATH_POLY = 512; // Max nav mesh polys that can be traversed
// Possible area types. Water, Road, Door and Grass are not used (left-over from Detour library)
enum SamplePolyAreas
{
SAMPLE_POLYAREA_GROUND = 0, // Regular ground movement
SAMPLE_POLYAREA_CROUCH = 1, // Requires crouched movement
SAMPLE_POLYAREA_BLOCKED = 2, // Requires a jump to get over
SAMPLE_POLYAREA_FALLDAMAGE = 3, // Requires taking fall damage (if not immune to it)
SAMPLE_POLYAREA_WALLCLIMB = 4, // Requires the ability to wall-stick, fly or blink
SAMPLE_POLYAREA_OBSTRUCTION = 5 // There is a door or weldable object in the way
SAMPLE_POLYAREA_GROUND = 0, // Regular ground movement
SAMPLE_POLYAREA_CROUCH = 1, // Requires crouched movement
SAMPLE_POLYAREA_BLOCKED = 2, // Requires a jump to get over
SAMPLE_POLYAREA_FALLDAMAGE = 3, // Requires taking fall damage (if not immune to it)
SAMPLE_POLYAREA_WALLCLIMB = 4, // Requires the ability to wall-stick, fly or blink
SAMPLE_POLYAREA_OBSTRUCTION = 5, // There is a door or weldable object in the way
SAMPLE_POLYAREA_STRUCTUREBLOCK = 6, // An enemy structure is blocking the way that must be destroyed
SAMPLE_POLYAREA_PHASEGATE = 7 // Phase gate area, for area cost calculation
};
// Possible movement types. Swim and door are not used
@ -247,6 +249,8 @@ void GroundMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoin
void JumpMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint);
// Called by NewMove, determines movement direction and jump inputs to hop over obstructions (structures)
void BlockedMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint);
// Called by NewMove, determines which structure is in the way and attacks it
void StructureBlockedMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint);
// Called by NewMove, determines the movement direction and inputs required to drop down from start to end points
void FallMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint);
// Called by NewMove, determines the movement direction and inputs required to climb a ladder to reach endpoint
@ -275,9 +279,9 @@ DoorTrigger* UTIL_GetNearestDoorTriggerFromLift(edict_t* LiftEdict, nav_door* Do
bool UTIL_IsPathBlockedByDoor(const Vector StartLoc, const Vector EndLoc, edict_t* SearchDoor);
edict_t* UTIL_GetDoorBlockingPathPoint(AvHAIPlayer* pBot, bot_path_node* PathNode, edict_t* SearchDoor);
edict_t* UTIL_GetDoorBlockingPathPoint(const Vector FromLocation, const Vector ToLocation, const unsigned short MovementFlag, edict_t* SearchDoor);
edict_t* UTIL_GetDoorBlockingPathPoint(const Vector FromLocation, const Vector ToLocation, const unsigned int MovementFlag, edict_t* SearchDoor);
edict_t* UTIL_GetBreakableBlockingPathPoint(AvHAIPlayer* pBot, bot_path_node* PathNode, edict_t* SearchBreakable);
edict_t* UTIL_GetBreakableBlockingPathPoint(AvHAIPlayer* pBot, const Vector FromLocation, const Vector ToLocation, const unsigned short MovementFlag, edict_t* SearchBreakable);
edict_t* UTIL_GetBreakableBlockingPathPoint(AvHAIPlayer* pBot, const Vector FromLocation, const Vector ToLocation, const unsigned int MovementFlag, edict_t* SearchBreakable);
Vector UTIL_GetButtonFloorLocation(const Vector UserLocation, edict_t* ButtonEdict);
@ -316,7 +320,7 @@ void UTIL_RemoveTemporaryObstacle(unsigned int ObstacleRef);
void UTIL_RemoveTemporaryObstacles(unsigned int* ObstacleRefs);
void UTIL_AddOffMeshConnection(Vector StartLoc, Vector EndLoc, unsigned char area, unsigned short flags, bool bBiDirectional, AvHAIOffMeshConnection* RemoveConnectionDef);
void UTIL_AddOffMeshConnection(Vector StartLoc, Vector EndLoc, unsigned char area, unsigned int flags, bool bBiDirectional, AvHAIOffMeshConnection* RemoveConnectionDef);
void UTIL_RemoveOffMeshConnections(AvHAIOffMeshConnection* RemoveConnectionDef);

View file

@ -341,53 +341,6 @@ void BotLeap(AvHAIPlayer* pBot, const Vector TargetLocation)
}
}
void LinkDeployedObjectToCommanderAction(AvHAIPlayer* Commander, AvHAIBuildableStructure* NewStructure)
{
if (!Commander || !NewStructure || FNullEnt(Commander->Edict)) { return; }
commander_action* Action = nullptr;
if (Commander->BuildAction.bIsAwaitingBuildLink && Commander->BuildAction.StructureToBuild == NewStructure->StructureType)
{
if (vDist2DSq(Commander->BuildAction.BuildLocation, NewStructure->Location) < sqrf(UTIL_MetresToGoldSrcUnits(5.0f)))
{
Action = &Commander->BuildAction;
}
}
if (!Action)
{
if (Commander->SupportAction.bIsAwaitingBuildLink && Commander->SupportAction.StructureToBuild == NewStructure->StructureType)
{
if (vDist2DSq(Commander->SupportAction.BuildLocation, NewStructure->Location) < sqrf(UTIL_MetresToGoldSrcUnits(5.0f)))
{
Action = &Commander->SupportAction;
}
}
}
if (!Action) { return; }
NewStructure->LastSuccessfulCommanderLocation = Action->LastAttemptedCommanderLocation;
NewStructure->LastSuccessfulCommanderAngle = Action->LastAttemptedCommanderAngle;
NewStructure->Purpose = Action->ActionPurpose;
float CoolDown = (Action->NumDesiredInstances > 1) ? 0.33f : commander_action_cooldown;
Commander->next_commander_action_time = gpGlobals->time + CoolDown;
Action->NumInstances++;
if (Action->NumDesiredInstances > 1)
{
Action->BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(BaseNavProfiles[MARINE_BASE_NAV_PROFILE], Action->BuildLocation, UTIL_MetresToGoldSrcUnits(1.0f));
}
Action->bIsAwaitingBuildLink = false;
}
bot_msg* GetAvailableBotMsgSlot(AvHAIPlayer* pBot)
{
for (int i = 0; i < 5; i++)
@ -466,28 +419,9 @@ void BotDropWeapon(AvHAIPlayer* pBot)
}
}
void BotAttackTarget(AvHAIPlayer* pBot, edict_t* Target)
void BotAlienAttackNonPlayerTarget(AvHAIPlayer* pBot, edict_t* Target)
{
if (FNullEnt(Target) || (Target->v.deadflag != DEAD_NO)) { return; }
AvHAIWeapon Weapon = WEAPON_INVALID;
if (IsPlayerMarine(pBot->Edict))
{
Weapon = BotMarineChooseBestWeaponForStructure(pBot, Target);
}
else
{
Weapon = BotAlienChooseBestWeaponForStructure(pBot, Target);
}
// Add special logic for grenade launchers since they aren't used like regular marine hitscan weapons
// This will handle things like firing from around corners, making sure they have cover from allies etc.
if (Weapon == WEAPON_MARINE_GL)
{
BombardierAttackTarget(pBot, Target);
return;
}
AvHAIWeapon Weapon = BotAlienChooseBestWeaponForStructure(pBot, Target);
BotAttackResult AttackResult = PerformAttackLOSCheck(pBot, Weapon, Target);
@ -502,21 +436,7 @@ void BotAttackTarget(AvHAIPlayer* pBot, edict_t* Target)
pBot->Button |= IN_DUCK;
}
if (IsPlayerLerk(pBot->Edict))
{
if (AITAC_ShouldBotBeCautious(pBot))
{
MoveTo(pBot, Target->v.origin, MOVESTYLE_HIDE, 100.0f);
}
else
{
MoveTo(pBot, Target->v.origin, MOVESTYLE_NORMAL, 100.0f);
}
return;
}
Vector AttackPoint = (IsEdictPlayer(Target) || IsEdictStructure(Target)) ? Target->v.origin : UTIL_GetButtonFloorLocation(pBot->Edict->v.origin, Target);
Vector AttackPoint = (IsEdictStructure(Target)) ? Target->v.origin : UTIL_GetButtonFloorLocation(pBot->Edict->v.origin, Target);
if (StructureType == STRUCTURE_ALIEN_HIVE)
{
@ -528,28 +448,42 @@ void BotAttackTarget(AvHAIPlayer* pBot, edict_t* Target)
}
}
MoveTo(pBot, AttackPoint, MOVESTYLE_NORMAL, WeaponRange);
if (IsPlayerMarine(pBot->Edict))
if (IsPlayerLerk(pBot->Edict))
{
if (gpGlobals->time - pBot->LastCombatTime > 5.0f)
if (AITAC_ShouldBotBeCautious(pBot))
{
BotReloadWeapons(pBot);
MoveTo(pBot, AttackPoint, MOVESTYLE_HIDE, 100.0f);
}
else
{
MoveTo(pBot, AttackPoint, MOVESTYLE_NORMAL, 100.0f);
}
return;
}
MoveTo(pBot, AttackPoint, MOVESTYLE_NORMAL, WeaponRange);
return;
}
if (AttackResult == ATTACK_BLOCKED)
{
if (!(IsEdictPlayer(Target) && !IsEdictStructure(Target)))
// We're attacking a shootable trigger
if (!IsEdictStructure(Target))
{
Vector AttackPoint = UTIL_GetButtonFloorLocation(pBot->Edict->v.origin, Target);
MoveTo(pBot, AttackPoint, MOVESTYLE_NORMAL, WeaponRange);
return;
}
// If we have regen and are hurt and are attacking a damaging structure, let us heal up a bit
if ((StructureType == STRUCTURE_MARINE_TURRET || StructureType == STRUCTURE_ALIEN_OFFENCECHAMBER) && GetPlayerOverallHealthPercent(pBot->Edict) < 0.75f && AvHGetAlienUpgradeLevel(pBot->Edict->v.iuser4, MASK_UPGRADE_2) > 0)
{
return;
}
if (vIsZero(pBot->BotNavInfo.ActualMoveDestination) || UTIL_TraceEntity(pBot->Edict, pBot->BotNavInfo.ActualMoveDestination + Vector(0.0f, 0.0f, 32.0f), UTIL_GetCentreOfEntity(Target)) != Target)
{
Vector NewAttackLocation = ZERO_VECTOR;
@ -589,6 +523,130 @@ void BotAttackTarget(AvHAIPlayer* pBot, edict_t* Target)
}
}
void BotMarineAttackNonPlayerTarget(AvHAIPlayer* pBot, edict_t* Target)
{
AvHAIWeapon Weapon = BotMarineChooseBestWeaponForStructure(pBot, Target);
// Add special logic for grenade launchers since they aren't used like regular marine hitscan weapons
// This will handle things like firing from around corners, making sure they have cover from allies etc.
if (Weapon == WEAPON_MARINE_GL)
{
BombardierAttackTarget(pBot, Target);
return;
}
BotAttackResult AttackResult = PerformAttackLOSCheck(pBot, Weapon, Target);
float WeaponRange = GetMaxIdealWeaponRange(Weapon);
AvHAIDeployableStructureType StructureType = GetStructureTypeFromEdict(Target);
if (AttackResult == ATTACK_OUTOFRANGE)
{
if (vDist2DSq(pBot->Edict->v.origin, Target->v.origin) < sqrf(max_player_use_reach))
{
pBot->Button |= IN_DUCK;
}
Vector AttackPoint = (IsEdictStructure(Target)) ? Target->v.origin : UTIL_GetButtonFloorLocation(pBot->Edict->v.origin, Target);
if (StructureType == STRUCTURE_ALIEN_HIVE)
{
const AvHAIHiveDefinition* HiveDefinition = AITAC_GetHiveFromEdict(Target);
if (HiveDefinition)
{
AttackPoint = HiveDefinition->FloorLocation;
}
}
MoveTo(pBot, AttackPoint, MOVESTYLE_NORMAL, WeaponRange);
return;
}
if (AttackResult == ATTACK_BLOCKED)
{
// Finish reloading, we are probably behind cover
if (IsPlayerReloading(pBot->Player))
{
return;
}
// We're attacking a shootable trigger
if (!IsEdictStructure(Target))
{
Vector AttackPoint = UTIL_GetButtonFloorLocation(pBot->Edict->v.origin, Target);
MoveTo(pBot, AttackPoint, MOVESTYLE_NORMAL, WeaponRange);
return;
}
if (vIsZero(pBot->BotNavInfo.ActualMoveDestination) || UTIL_TraceEntity(pBot->Edict, pBot->BotNavInfo.ActualMoveDestination + Vector(0.0f, 0.0f, 32.0f), UTIL_GetCentreOfEntity(Target)) != Target)
{
Vector NewAttackLocation = ZERO_VECTOR;
if (vIsZero(pBot->BotNavInfo.ActualMoveDestination))
{
NewAttackLocation = FindClosestNavigablePointToDestination(pBot->BotNavInfo.NavProfile, pBot->CurrentFloorPosition, UTIL_GetEntityGroundLocation(Target), WeaponRange);
}
else
{
NewAttackLocation = UTIL_GetRandomPointOnNavmeshInRadius(pBot->BotNavInfo.NavProfile, pBot->CurrentFloorPosition, 2.0f);
// Did we find a clear spot we could attack from? If so, make that our new move destination
if (NewAttackLocation != ZERO_VECTOR && UTIL_TraceEntity(pBot->Edict, NewAttackLocation + Vector(0.0f, 0.0f, 32.0f), UTIL_GetCentreOfEntity(Target)) == Target)
{
MoveTo(pBot, NewAttackLocation, MOVESTYLE_NORMAL);
}
}
}
else
{
MoveTo(pBot, pBot->BotNavInfo.TargetDestination, MOVESTYLE_NORMAL);
}
return;
}
if (AttackResult == ATTACK_SUCCESS)
{
if (IsPlayerReloading(pBot->Player))
{
if (StructureType == STRUCTURE_MARINE_TURRET || StructureType == STRUCTURE_ALIEN_OFFENCECHAMBER)
{
MoveTo(pBot, AITAC_GetTeamStartingLocation(pBot->Player->GetTeam()), MOVESTYLE_NORMAL);
return;
}
}
// If we were ducking before then keep ducking
if (pBot->Edict->v.oldbuttons & IN_DUCK)
{
pBot->Button |= IN_DUCK;
}
BotShootTarget(pBot, Weapon, Target);
}
}
void BotAttackNonPlayerTarget(AvHAIPlayer* pBot, edict_t* Target)
{
if (FNullEnt(Target) || (Target->v.deadflag != DEAD_NO)) { return; }
AvHAIWeapon Weapon = WEAPON_INVALID;
if (IsPlayerMarine(pBot->Edict))
{
BotMarineAttackNonPlayerTarget(pBot, Target);
}
else
{
BotAlienAttackNonPlayerTarget(pBot, Target);
}
}
void BotShootTarget(AvHAIPlayer* pBot, AvHAIWeapon AttackWeapon, edict_t* Target)
{
if (FNullEnt(Target) || (Target->v.deadflag != DEAD_NO)) { return; }
@ -926,8 +984,23 @@ void BotShootLocation(AvHAIPlayer* pBot, AvHAIWeapon AttackWeapon, const Vector
}
}
void BotEvolveLifeform(AvHAIPlayer* pBot, AvHMessageID TargetLifeform)
void BotEvolveLifeform(AvHAIPlayer* pBot, Vector DesiredEvolveLocation, AvHMessageID TargetLifeform)
{
if (!IsPlayerAlien(pBot->Edict)) { return; }
Vector EvolvePoint = UTIL_ProjectPointToNavmesh(DesiredEvolveLocation, GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE));
if (vIsZero(EvolvePoint))
{
EvolvePoint = DesiredEvolveLocation;
}
if (vDist2DSq(pBot->Edict->v.origin, EvolvePoint) > sqrf(32.0f))
{
MoveTo(pBot, EvolvePoint, MOVESTYLE_NORMAL);
return;
}
pBot->Impulse = TargetLifeform;
}
@ -1487,6 +1560,15 @@ void StartNewBotFrame(AvHAIPlayer* pBot)
UpdateCommanderOrders(pBot);
}
// If we tried placing a building as gorge, and nothing has appeared within 0.5s, the placement failed.
if (pBot->BuildAttempts.BuildStatus == BUILD_ATTEMPT_PENDING)
{
if ((gpGlobals->time - pBot->BuildAttempts.BuildAttemptTime) > 0.5f)
{
pBot->BuildAttempts.BuildStatus = BUILD_ATTEMPT_FAILED;
}
}
}
void CustomThink(AvHAIPlayer* pBot)
@ -1510,7 +1592,14 @@ void DroneThink(AvHAIPlayer* pBot)
BotProgressTask(pBot, &pBot->PrimaryBotTask);
}
//AIDEBUG_DrawBotPath(pBot);
AIDEBUG_DrawBotPath(pBot);
AvHAIWeapon DesiredWeapon = (pBot->DesiredMoveWeapon != WEAPON_NONE) ? pBot->DesiredMoveWeapon : pBot->DesiredCombatWeapon;
if (DesiredWeapon != WEAPON_NONE && GetPlayerCurrentWeapon(pBot->Player) != DesiredWeapon)
{
BotSwitchToWeapon(pBot, DesiredWeapon);
}
}
void SetNewAIPlayerRole(AvHAIPlayer* pBot, AvHAIBotRole NewRole)
@ -2032,6 +2121,12 @@ void AIPlayerDMThink(AvHAIPlayer* pBot)
void AIPlayerThink(AvHAIPlayer* pBot)
{
if (pBot == AIMGR_GetDebugAIPlayer())
{
bool bBreak = true;
}
switch (GetGameRules()->GetMapMode())
{
case MAP_MODE_NS:

View file

@ -22,21 +22,21 @@ float GetLeapCost(AvHAIPlayer* pBot);
void BotReloadWeapons(AvHAIPlayer* pBot);
void LinkDeployedObjectToCommanderAction(AvHAIPlayer* Commander, AvHAIBuildableStructure* NewStructure);
// Make the bot type something in either global or team chat
void BotSay(AvHAIPlayer* pBot, bool bTeamSay, float Delay, char* textToSay);
bot_msg* GetAvailableBotMsgSlot(AvHAIPlayer* pBot);
void BotDropWeapon(AvHAIPlayer* pBot);
void BotAttackTarget(AvHAIPlayer* pBot, edict_t* Target);
void BotAttackNonPlayerTarget(AvHAIPlayer* pBot, edict_t* Target);
void BotMarineAttackNonPlayerTarget(AvHAIPlayer* pBot, edict_t* Target);
void BotAlienAttackNonPlayerTarget(AvHAIPlayer* pBot, edict_t* Target);
void BotShootTarget(AvHAIPlayer* pBot, AvHAIWeapon AttackWeapon, edict_t* Target);
void BotShootLocation(AvHAIPlayer* pBot, AvHAIWeapon AttackWeapon, const Vector TargetLocation);
void BombardierAttackTarget(AvHAIPlayer* pBot, edict_t* Target);
void BotEvolveLifeform(AvHAIPlayer* pBot, AvHMessageID Lifeform);
void BotEvolveLifeform(AvHAIPlayer* pBot, Vector DesiredEvolveLocation, AvHMessageID TargetLifeform);
enemy_status* GetTrackedEnemyRefForTarget(AvHAIPlayer* pBot, edict_t* Target);

View file

@ -32,6 +32,8 @@ extern int m_spriteTexture;
Vector DebugVector1 = ZERO_VECTOR;
Vector DebugVector2 = ZERO_VECTOR;
AvHAIPlayer* DebugAIPlayer = nullptr;
vector<bot_path_node> DebugPath;
string BotNames[MAX_PLAYERS] = { "MrRobot",
@ -70,17 +72,17 @@ string BotNames[MAX_PLAYERS] = { "MrRobot",
AvHAICommanderMode AIMGR_GetCommanderMode()
{
if (avh_botcommandermode.value == 0)
if (avh_botcommandermode.value == 1)
{
return COMMANDERMODE_DISABLED;
return COMMANDERMODE_ENABLED;
}
if (avh_botcommandermode.value == 1)
if (avh_botcommandermode.value == 2)
{
return COMMANDERMODE_IFNOHUMAN;
}
return COMMANDERMODE_ENABLED;
return COMMANDERMODE_DISABLED;
}
@ -124,15 +126,15 @@ void AIMGR_UpdateAIPlayerCounts()
return;
}
if (avh_botautomode.value == 1) // Balance only: bots will only be added and removed to ensure teams remain balanced
if (avh_botautomode.value == 1) // Fill teams: bots will be added and removed to maintain a minimum player count
{
AIMGR_UpdateTeamBalance();
AIMGR_UpdateFillTeams();
return;
}
if (avh_botautomode.value == 2) // Fill teams: bots will be added and removed to maintain a minimum player count
if (avh_botautomode.value == 2) // Balance only: bots will only be added and removed to ensure teams remain balanced
{
AIMGR_UpdateFillTeams();
AIMGR_UpdateTeamBalance();
return;
}
@ -455,6 +457,12 @@ void AIMGR_AddAIPlayerToTeam(int Team)
NewAIPlayer.Edict = BotEnt;
NewAIPlayer.Team = theNewAIPlayer->GetTeam();
NewAIPlayer.CurrentTask = nullptr;
NewAIPlayer.PrimaryBotTask.TaskType = TASK_NONE;
NewAIPlayer.SecondaryBotTask.TaskType = TASK_NONE;
NewAIPlayer.WantsAndNeedsTask.TaskType = TASK_NONE;
NewAIPlayer.CommanderTask.TaskType = TASK_NONE;
const bot_skill BotSkillSettings = CONFIG_GetGlobalBotSkillLevel();
memcpy(&NewAIPlayer.BotSkillSettings, &BotSkillSettings, sizeof(bot_skill));
@ -565,9 +573,7 @@ void AIMGR_UpdateAIPlayers()
UpdateBotChat(bot);
AIPlayerThink(bot);
AIDEBUG_DrawPath(DebugPath, 0.0f);
DroneThink(bot);
BotUpdateDesiredViewRotation(bot);
}
@ -813,43 +819,6 @@ AvHAIPlayer* AIMGR_GetAICommander(AvHTeamNumber Team)
return nullptr;
}
AvHAIPlayer* AIMGR_FindPlayerOnTeamWaitingBuildLink(const AvHTeamNumber Team, const AvHAIDeployableStructureType NewStructure, const Vector BuildLocation)
{
vector<AvHAIPlayer*> TeamPlayers = AIMGR_GetAIPlayersOnTeam(Team);
for (auto it = TeamPlayers.begin(); it != TeamPlayers.end(); it++)
{
AvHAIPlayer* AIPlayer = (*it);
if (AIPlayer->PrimaryBotTask.bIsWaitingForBuildLink && AIPlayer->PrimaryBotTask.StructureType == NewStructure)
{
if (vDist2DSq(BuildLocation, AIPlayer->PrimaryBotTask.TaskLocation) < sqrf(UTIL_MetresToGoldSrcUnits(2.0f)))
{
return AIPlayer;
}
}
if (AIPlayer->SecondaryBotTask.bIsWaitingForBuildLink && AIPlayer->SecondaryBotTask.StructureType == NewStructure)
{
if (vDist2DSq(BuildLocation, AIPlayer->SecondaryBotTask.TaskLocation) < sqrf(UTIL_MetresToGoldSrcUnits(2.0f)))
{
return AIPlayer;
}
}
if (AIPlayer->WantsAndNeedsTask.bIsWaitingForBuildLink && AIPlayer->WantsAndNeedsTask.StructureType == NewStructure)
{
if (vDist2DSq(BuildLocation, AIPlayer->WantsAndNeedsTask.TaskLocation) < sqrf(UTIL_MetresToGoldSrcUnits(2.0f)))
{
return AIPlayer;
}
}
}
return nullptr;
}
AvHTeamNumber AIMGR_GetEnemyTeam(const AvHTeamNumber FriendlyTeam)
{
AvHTeamNumber TeamANumber = GetGameRules()->GetTeamANumber();
@ -858,6 +827,15 @@ AvHTeamNumber AIMGR_GetEnemyTeam(const AvHTeamNumber FriendlyTeam)
return (FriendlyTeam == TeamANumber) ? TeamBNumber : TeamANumber;
}
AvHClassType AIMGR_GetEnemyTeamType(const AvHTeamNumber FriendlyTeam)
{
AvHTeamNumber EnemyTeamNumber = AIMGR_GetEnemyTeam(FriendlyTeam);
AvHTeam* TeamRef = GetGameRules()->GetTeam(EnemyTeamNumber);
return (TeamRef) ? TeamRef->GetTeamType() : AVH_CLASS_TYPE_UNDEFINED;
}
vector<AvHAIPlayer*> AIMGR_GetAllAIPlayers()
{
vector<AvHAIPlayer*> Result;
@ -904,3 +882,20 @@ void AIMGR_BotPrecache()
{
m_spriteTexture = PRECACHE_MODEL("sprites/zbeam6.spr");
}
AvHAIPlayer* AIMGR_GetDebugAIPlayer()
{
return DebugAIPlayer;
}
void AIMGR_SetDebugAIPlayer(edict_t* AIPlayer)
{
for (auto it = ActiveAIPlayers.begin(); it != ActiveAIPlayers.end(); it++)
{
if (it->Edict == AIPlayer)
{
DebugAIPlayer = &(*it);
return;
}
}
}

View file

@ -58,13 +58,17 @@ int AIMGR_GetNumAIPlayersWithRoleOnTeam(AvHTeamNumber Team, AvHAIBotRole Role, A
AvHAIPlayer* AIMGR_GetAICommander(AvHTeamNumber Team);
AvHAIPlayer* AIMGR_FindPlayerOnTeamWaitingBuildLink(const AvHTeamNumber Team, const AvHAIDeployableStructureType NewStructure, const Vector BuildLocation);
AvHTeamNumber AIMGR_GetEnemyTeam(const AvHTeamNumber FriendlyTeam);
AvHClassType AIMGR_GetEnemyTeamType(const AvHTeamNumber FriendlyTeam);
vector<AvHAIPlayer*> AIMGR_GetAllAIPlayers();
vector<AvHAIPlayer*> AIMGR_GetAIPlayersOnTeam(AvHTeamNumber Team);
void AIMGR_ClearBotData();
AvHAIPlayer* AIMGR_GetDebugAIPlayer();
void AIMGR_SetDebugAIPlayer(edict_t* AIPlayer);
#endif

View file

@ -221,9 +221,9 @@ AvHAIBuildableStructure* AITAC_FindClosestDeployableToLocation(const Vector& Loc
{
for (auto& it : TeamAStructureMap)
{
if (it.second.StructureStatusFlags & Filter->ExcludeStatusFlags) { continue; }
if ((it.second.StructureStatusFlags & Filter->IncludeStatusFlags) != Filter->IncludeStatusFlags) { continue; }
if (!(it.second.StructureType & Filter->DeployableTypes)) { continue; }
if (it.second.StructureStatusFlags & Filter->ExcludeStatusFlags) { continue; }
if ((it.second.StructureStatusFlags & Filter->IncludeStatusFlags) != Filter->IncludeStatusFlags) { continue; }
unsigned int StructureReachabilityFlags = (it.second.TeamAReachabilityFlags | it.second.TeamBReachabilityFlags);
@ -248,10 +248,10 @@ AvHAIBuildableStructure* AITAC_FindClosestDeployableToLocation(const Vector& Loc
{
for (auto& it : TeamBStructureMap)
{
if (!(it.second.StructureType & Filter->DeployableTypes)) { continue; }
if (it.second.StructureStatusFlags & Filter->ExcludeStatusFlags) { continue; }
if ((it.second.StructureStatusFlags & Filter->IncludeStatusFlags) != Filter->IncludeStatusFlags) { continue; }
if (!(it.second.StructureType & Filter->DeployableTypes)) { continue; }
unsigned int StructureReachabilityFlags = (it.second.TeamAReachabilityFlags | it.second.TeamBReachabilityFlags);
if (Filter->ReachabilityTeam != TEAM_IND)
@ -1182,6 +1182,18 @@ void AITAC_OnNavMeshModified()
{
it->bReachabilityMarkedDirty = true;
}
vector<AvHAIPlayer*> AllAIPlayers = AIMGR_GetAllAIPlayers();
for (auto it = AllAIPlayers.begin(); it != AllAIPlayers.end(); it++)
{
AvHAIPlayer* ThisPlayer = (*it);
if (IsPlayerActiveInGame(ThisPlayer->Edict) && ThisPlayer->BotNavInfo.CurrentPath.size() > 0)
{
ThisPlayer->BotNavInfo.NextForceRecalc = gpGlobals->time + frandrange(0.0f, 1.0f);
}
}
}
void AITAC_RefreshBuildableStructures()
@ -1660,6 +1672,8 @@ AvHAIBuildableStructure* AITAC_UpdateBuildableStructure(CBaseEntity* Structure)
std::unordered_map<int, AvHAIBuildableStructure>& BuildingMap = (BaseBuildable->GetTeamNumber() == TeamANumber) ? TeamAStructureMap : TeamBStructureMap;
BuildingMap[EntIndex].StructureType = StructureType;
// This is the first time we've seen this structure, so it must be new
if (BuildingMap[EntIndex].LastSeen == 0)
{
@ -1674,8 +1688,6 @@ AvHAIBuildableStructure* AITAC_UpdateBuildableStructure(CBaseEntity* Structure)
AITAC_OnStructureCreated(&BuildingMap[EntIndex]);
}
BuildingMap[EntIndex].StructureType = StructureType;
if (vIsZero(BuildingMap[EntIndex].Location) || !vEquals(BaseBuildable->pev->origin, BuildingMap[EntIndex].Location, 5.0f))
{
AITAC_RefreshReachabilityForStructure(&BuildingMap[EntIndex]);
@ -1754,24 +1766,9 @@ void AITAC_OnStructureCreated(AvHAIBuildableStructure* NewStructure)
if (!Team) { return; }
if (Team->GetTeamType() == AVH_CLASS_TYPE_MARINE)
if (Team->GetTeamType() == AVH_CLASS_TYPE_ALIEN)
{
AvHAIPlayer* ActiveAICommander = AIMGR_GetAICommander(StructureTeam);
if (ActiveAICommander)
{
LinkDeployedObjectToCommanderAction(ActiveAICommander, NewStructure);
}
}
else
{
AvHAIPlayer* BuildingPlayer = AIMGR_FindPlayerOnTeamWaitingBuildLink(StructureTeam, NewStructure->StructureType, NewStructure->Location);
if (BuildingPlayer)
{
AITAC_LinkAlienStructureToTask(BuildingPlayer, NewStructure);
}
AITAC_LinkAlienStructureToPlayer(NewStructure);
}
}
@ -1805,7 +1802,7 @@ void AITAC_OnStructureCompleted(AvHAIBuildableStructure* NewStructure)
NewConnection.TargetObject = OtherPhaseGate->edict;
memset(&NewConnection.ConnectionRefs[0], 0, sizeof(NewConnection.ConnectionRefs));
UTIL_AddOffMeshConnection(NewStructure->Location, OtherPhaseGate->Location, SAMPLE_POLYAREA_GROUND, NewFlag, true, &NewConnection);
UTIL_AddOffMeshConnection(NewStructure->Location, OtherPhaseGate->Location, SAMPLE_POLYAREA_PHASEGATE, NewFlag, true, &NewConnection);
NewStructure->OffMeshConnections.push_back(NewConnection);
@ -1875,9 +1872,24 @@ void AITAC_OnStructureDestroyed(AvHAIBuildableStructure* DestroyedStructure)
}
}
void AITAC_LinkAlienStructureToTask(AvHAIPlayer* pBot, AvHAIBuildableStructure* NewStructure)
void AITAC_LinkAlienStructureToPlayer(AvHAIBuildableStructure* NewStructure)
{
vector<AvHAIPlayer*> AllTeamPlayers = AIMGR_GetAIPlayersOnTeam((AvHTeamNumber)NewStructure->edict->v.team);
for (auto it = AllTeamPlayers.begin(); it != AllTeamPlayers.end(); it++)
{
AvHAIPlayer* Player = (*it);
if (Player->BuildAttempts.BuildStatus == BUILD_ATTEMPT_PENDING && Player->BuildAttempts.AttemptedStructureType == NewStructure->StructureType)
{
if (vDist2DSq(NewStructure->Location, Player->BuildAttempts.AttemptedLocation) < sqrf(UTIL_MetresToGoldSrcUnits(2.0f)))
{
Player->BuildAttempts.BuildStatus = BUILD_ATTEMPT_SUCCESS;
Player->BuildAttempts.LinkedStructure = NewStructure;
}
}
}
}
void AITAC_LinkDeployedItemToAction(AvHAIPlayer* CommanderBot, const AvHAIDroppedItem* NewItem)
@ -2044,18 +2056,18 @@ unsigned char UTIL_GetAreaForObstruction(AvHAIDeployableStructureType StructureT
AvHTeamNumber TeamA = GetGameRules()->GetTeamANumber();
AvHTeamNumber TeamB = GetGameRules()->GetTeamBNumber();
unsigned char StructureArea = (BuildingEdict->v.team == TeamA) ? DT_TILECACHE_TEAM1STRUCTURE_AREA : DT_TILECACHE_TEAM2STRUCTURE_AREA;
unsigned char TeamStructureArea = (BuildingEdict->v.team == TeamA) ? DT_TILECACHE_TEAM1STRUCTURE_AREA : DT_TILECACHE_TEAM2STRUCTURE_AREA;
switch (StructureType)
{
case STRUCTURE_MARINE_RESTOWER:
case STRUCTURE_MARINE_COMMCHAIR:
case STRUCTURE_MARINE_ARMOURY:
case STRUCTURE_MARINE_ADVARMOURY:
case STRUCTURE_MARINE_OBSERVATORY:
case STRUCTURE_ALIEN_RESTOWER:
case STRUCTURE_MARINE_RESTOWER:
case STRUCTURE_ALIEN_HIVE:
return StructureArea;
return TeamStructureArea;
default:
return DT_TILECACHE_BLOCKED_AREA;
}
@ -2072,6 +2084,8 @@ float UTIL_GetStructureRadiusForObstruction(AvHAIDeployableStructureType Structu
case STRUCTURE_MARINE_TURRETFACTORY:
case STRUCTURE_MARINE_COMMCHAIR:
return 60.0f;
case STRUCTURE_MARINE_TURRET:
return 30.0f;
default:
return 40.0f;

View file

@ -44,7 +44,7 @@ void AITAC_OnStructureCompleted(AvHAIBuildableStructure* NewStructure);
void AITAC_OnStructureBeginRecycling(AvHAIBuildableStructure* RecyclingStructure);
void AITAC_OnStructureDestroyed(AvHAIBuildableStructure* DestroyedStructure);
void AITAC_LinkDeployedItemToAction(AvHAIPlayer* CommanderBot, const AvHAIDroppedItem* NewItem);
void AITAC_LinkAlienStructureToTask(AvHAIPlayer* pBot, AvHAIBuildableStructure* NewStructure);
void AITAC_LinkAlienStructureToPlayer(AvHAIBuildableStructure* NewStructure);
float AITAC_GetPhaseDistanceBetweenPoints(const Vector StartPoint, const Vector EndPoint);

View file

@ -137,6 +137,8 @@ void AITASK_ClearBotTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
Task->LastBuildAttemptTime = 0.0f;
Task->BuildAttempts = 0;
Task->StructureType = STRUCTURE_NONE;
memset(&pBot->BuildAttempts, 0, sizeof(AvHAIBuildAttempt));
}
bool AITASK_IsTaskUrgent(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
@ -714,8 +716,6 @@ bool AITASK_IsAlienCapResNodeTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask*
return false;
}
if (!IsPlayerSkulk(pBot->Edict) && !IsPlayerGorge(pBot->Edict)) { return false; }
const AvHAIResourceNode* ResNodeIndex = AITAC_GetNearestResourceNodeToLocation(Task->TaskLocation);
if (!ResNodeIndex)
@ -723,8 +723,53 @@ bool AITASK_IsAlienCapResNodeTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask*
return false;
}
AvHTeamNumber BotTeam = pBot->Player->GetTeam();
// Don't waste resources switching down to gorge if we're a lerk, fade or onos
// but we can still clear the area of enemy structures
if (!IsPlayerSkulk(pBot->Edict) && !IsPlayerGorge(pBot->Edict))
{
DeployableSearchFilter EnemyStructuresFilter;
EnemyStructuresFilter.DeployableTeam = AIMGR_GetEnemyTeam(BotTeam);
EnemyStructuresFilter.ReachabilityTeam = BotTeam;
EnemyStructuresFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
EnemyStructuresFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f);
return AITAC_DeployableExistsAtLocation(ResNodeIndex->Location, &EnemyStructuresFilter);
}
// We can attack structures basically if we aren't stuck with Gorge's spit attack
bool bCanAttackStructures = (!IsPlayerGorge(pBot->Edict) || PlayerHasWeapon(pBot->Player, WEAPON_GORGE_BILEBOMB));
if (ResNodeIndex->bIsOccupied)
{
// If we have a tower on the node, we can still help build it if it's not finished yet,
// or we can clear the area if we're able to
if (ResNodeIndex->OwningTeam == BotTeam)
{
if (IsPlayerGorge(pBot->Edict) && !UTIL_StructureIsFullyBuilt(ResNodeIndex->ActiveTowerEntity)) { return true; }
// If the tower is fully built and we don't have bile bomb then our work here is done
if (!bCanAttackStructures) { return false; }
// If we can clear the area of enemy junk then do so, otherwise we're finished
DeployableSearchFilter EnemyStructuresFilter;
EnemyStructuresFilter.DeployableTeam = AIMGR_GetEnemyTeam(BotTeam);
EnemyStructuresFilter.ReachabilityTeam = BotTeam;
EnemyStructuresFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
EnemyStructuresFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f);
return AITAC_DeployableExistsAtLocation(ResNodeIndex->Location, &EnemyStructuresFilter);
}
else
{
// Enemy owns the res node, but we can cap it if we're able to attack structures, or there's a friend in the area who can.
return (bCanAttackStructures || AITAC_GetNumPlayersOfTeamInArea(BotTeam, ResNodeIndex->Location, UTIL_MetresToGoldSrcUnits(5.0f), false, pBot->Edict, AVH_USER3_ALIEN_PLAYER2) > 0);
}
}
// If another gorge is claiming this spot, then move on
if (!IsPlayerGorge(pBot->Edict) && !ResNodeIndex->bIsOccupied)
if (!IsPlayerGorge(pBot->Edict))
{
edict_t* OtherBuilder = AITAC_GetNearestPlayerOfClassInArea(pBot->Player->GetTeam(), ResNodeIndex->Location, UTIL_MetresToGoldSrcUnits(5.0f), false, pBot->Edict, AVH_USER3_ALIEN_PLAYER2);
@ -732,36 +777,15 @@ bool AITASK_IsAlienCapResNodeTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask*
{
if (GetPlayerResources(OtherBuilder) >= (int)(kResourceTowerCost * 0.7f))
{
return false;
}
}
}
// If we can clear the area of enemy junk then do so, otherwise we will let the other guy place the tower
DeployableSearchFilter EnemyStructuresFilter;
EnemyStructuresFilter.DeployableTeam = AIMGR_GetEnemyTeam(BotTeam);
EnemyStructuresFilter.ReachabilityTeam = BotTeam;
EnemyStructuresFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
EnemyStructuresFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f);
if (ResNodeIndex->bIsOccupied)
{
if (IsPlayerGorge(pBot->Edict))
{
if (ResNodeIndex->OwningTeam == pBot->Player->GetTeam())
{
return !UTIL_StructureIsFullyBuilt(ResNodeIndex->ActiveTowerEntity);
return AITAC_DeployableExistsAtLocation(ResNodeIndex->Location, &EnemyStructuresFilter);
}
else
{
return PlayerHasWeapon(pBot->Player, WEAPON_GORGE_BILEBOMB);
}
}
else
{
return (ResNodeIndex->OwningTeam != pBot->Player->GetTeam());
}
}
else
{
if (Task->BuildAttempts > 3)
{
return false;
}
}
@ -776,7 +800,9 @@ bool AITASK_IsMarineCapResNodeTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask*
if (!ResNodeIndex) { return false; }
// Always obey commander orders even if there's a bunch of other marines already there
AvHTeamNumber BotTeam = pBot->Player->GetTeam();
// Always obey commander orders even if there's a bunch of other marines already there, otherwise don't bother if someone else is securing it
if (!Task->bIssuedByCommander)
{
int DesiredNumCappers = (ResNodeIndex->OwningTeam == AIMGR_GetEnemyTeam(pBot->Player->GetTeam())) ? 2 : 1;
@ -785,15 +811,24 @@ bool AITASK_IsMarineCapResNodeTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask*
if (NumMarinesNearby >= DesiredNumCappers && vDist2DSq(pBot->Edict->v.origin, Task->TaskLocation) > sqrf(UTIL_MetresToGoldSrcUnits(4.0f))) { return false; }
}
if (ResNodeIndex->bIsOccupied)
{
if (ResNodeIndex->OwningTeam == pBot->Player->GetTeam())
{
return (FNullEnt(ResNodeIndex->ActiveTowerEntity) || !UTIL_StructureIsFullyBuilt(ResNodeIndex->ActiveTowerEntity));
}
}
// Obviously still valid task if the node is empty
if (!ResNodeIndex->bIsOccupied) { return true; }
return true;
// Also obviously still valid if it's owned by the enemy
if (ResNodeIndex->OwningTeam != BotTeam) { return true; }
// Likewise, still valid if there isn't a tower or it's not fully built
if (FNullEnt(ResNodeIndex->ActiveTowerEntity) || !UTIL_StructureIsFullyBuilt(ResNodeIndex->ActiveTowerEntity)) { return true; }
// At this point, the node is capped fully. However, don't consider a res node secured if the enemy still has their junk lying around. Clear it all out.
DeployableSearchFilter EnemyStructures;
EnemyStructures.DeployableTypes = SEARCH_ALL_STRUCTURES;
EnemyStructures.DeployableTeam = AIMGR_GetEnemyTeam(BotTeam);
EnemyStructures.ReachabilityTeam = BotTeam;
EnemyStructures.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
EnemyStructures.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f);
return AITAC_DeployableExistsAtLocation(ResNodeIndex->Location, &EnemyStructures);
}
bool AITASK_IsDefendTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
@ -823,69 +858,77 @@ bool AITASK_IsReinforceStructureTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTas
if (!FNullEnt(Task->TaskSecondaryTarget) && !UTIL_StructureIsFullyBuilt(Task->TaskSecondaryTarget)) { return true; }
if (Task->TaskTarget->v.team != pBot->Player->GetTeam()) { return false; }
AvHTeamNumber BotTeam = pBot->Player->GetTeam();
if (Task->TaskTarget->v.team != BotTeam) { return false; }
// The reinforce structure task is true if we have an undecided hive available that we could build a new chamber with
bool bActiveHiveWithoutTechExists = AITAC_TeamHiveWithTechExists(pBot->Player->GetTeam(), MESSAGE_NULL);
if (bActiveHiveWithoutTechExists) { return true; }
DeployableSearchFilter StructureFilter;
StructureFilter.DeployableTypes = STRUCTURE_ALIEN_OFFENCECHAMBER;
StructureFilter.DeployableTypes = STRUCTURE_ALIEN_OFFENCECHAMBER | ALIEN_BUILD_DEFENSE_CHAMBER | ALIEN_BUILD_MOVEMENT_CHAMBER | ALIEN_BUILD_SENSORY_CHAMBER;
StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f);
StructureFilter.DeployableTeam = pBot->Player->GetTeam();
// At least 2 offence chambers
int NumOffenceChambers = AITAC_GetNumDeployablesNearLocation(Task->TaskTarget->v.origin, &StructureFilter);
vector<AvHAIBuildableStructure*> AllNearbyStructures = AITAC_FindAllDeployables(Task->TaskTarget->v.origin, &StructureFilter);
if (NumOffenceChambers < 2) { return true; }
bool bUnfinishedStructureExists = false;
int NumOffenceChambers = 0;
int NumDefenceChambers = 0;
int NumMovementChambers = 0;
int NumSensoryChambers = 0;
// At least 2 defence chambers, if the hive exists for it
if (AITAC_TeamHiveWithTechExists(pBot->Player->GetTeam(), ALIEN_BUILD_DEFENSE_CHAMBER))
for (auto it = AllNearbyStructures.begin(); it != AllNearbyStructures.end(); it++)
{
StructureFilter.DeployableTypes = STRUCTURE_ALIEN_DEFENCECHAMBER;
int NumDefenceChambers = AITAC_GetNumDeployablesNearLocation(Task->TaskTarget->v.origin, &StructureFilter);
AvHAIBuildableStructure* ThisStructure = (*it);
if (NumDefenceChambers < 2) { return true; }
if (!(ThisStructure->StructureStatusFlags & STRUCTURE_STATUS_COMPLETED)) { bUnfinishedStructureExists = true; }
StructureFilter.MaxSearchRadius = 0.0f;
int NumTotalDefenceChambers = AITAC_GetNumDeployablesNearLocation(Task->TaskTarget->v.origin, &StructureFilter);
switch (ThisStructure->StructureType)
{
case STRUCTURE_ALIEN_OFFENCECHAMBER:
NumOffenceChambers++;
break;
case STRUCTURE_ALIEN_DEFENCECHAMBER:
NumDefenceChambers++;
break;
case STRUCTURE_ALIEN_MOVEMENTCHAMBER:
NumMovementChambers++;
break;
case STRUCTURE_ALIEN_SENSORYCHAMBER:
NumSensoryChambers++;
break;
default:
break;
if (NumTotalDefenceChambers < 3) { return true; }
}
}
// At least 1 movement and sensory chamber, if the hive exists for them
if (AITAC_TeamHiveWithTechExists(pBot->Player->GetTeam(), ALIEN_BUILD_MOVEMENT_CHAMBER))
{
StructureFilter.DeployableTypes = STRUCTURE_ALIEN_MOVEMENTCHAMBER;
StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f);
// Task is still valid if we have any missing structures, or we're a gorge at the target site and there is an incomplete structure that we can finish off
bool bHasMoveChamber = AITAC_DeployableExistsAtLocation(Task->TaskTarget->v.origin, &StructureFilter);
if (NumOffenceChambers < 2
|| (AITAC_TeamHiveWithTechExists(BotTeam, ALIEN_BUILD_DEFENSE_CHAMBER) && NumDefenceChambers < 2)
|| (AITAC_TeamHiveWithTechExists(BotTeam, ALIEN_BUILD_MOVEMENT_CHAMBER) && NumMovementChambers < 1)
|| (AITAC_TeamHiveWithTechExists(BotTeam, ALIEN_BUILD_SENSORY_CHAMBER) && NumSensoryChambers < 1)
|| (IsPlayerGorge(pBot->Edict) && bUnfinishedStructureExists && vDist2DSq(pBot->Edict->v.origin, Task->TaskTarget->v.origin) <= sqrf(UTIL_MetresToGoldSrcUnits(10.0f)))
) { return true; }
if (!bHasMoveChamber) { return true; }
// Otherwise, are there any enemy structures lying around we could clear out?
StructureFilter.MaxSearchRadius = 0.0f;
int NumTotalMoveChambers = AITAC_GetNumDeployablesNearLocation(Task->TaskTarget->v.origin, &StructureFilter);
bool bCanAttackStructures = (!IsPlayerGorge(pBot->Edict) || PlayerHasWeapon(pBot->Player, WEAPON_GORGE_BILEBOMB));
if (NumTotalMoveChambers < 3) { return true; }
}
if (!bCanAttackStructures) { return false; }
if (AITAC_TeamHiveWithTechExists(pBot->Player->GetTeam(), ALIEN_BUILD_SENSORY_CHAMBER))
{
StructureFilter.DeployableTypes = STRUCTURE_ALIEN_SENSORYCHAMBER;
StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f);
DeployableSearchFilter EnemyStuff;
EnemyStuff.DeployableTypes = SEARCH_ALL_STRUCTURES;
EnemyStuff.DeployableTeam = AIMGR_GetEnemyTeam(BotTeam);
EnemyStuff.ReachabilityTeam = BotTeam;
EnemyStuff.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
EnemyStuff.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f);
bool bHasSensoryChamber = AITAC_DeployableExistsAtLocation(Task->TaskTarget->v.origin, &StructureFilter);
if (!bHasSensoryChamber) { return true; }
StructureFilter.MaxSearchRadius = 0.0f;
int NumTotalSensoryChambers = AITAC_GetNumDeployablesNearLocation(Task->TaskTarget->v.origin, &StructureFilter);
if (NumTotalSensoryChambers < 3) { return true; }
}
// We have all available chambers set up
return false;
return AITAC_DeployableExistsAtLocation(Task->TaskTarget->v.origin, &EnemyStuff);
}
bool AITASK_IsMarineSecureHiveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
@ -946,7 +989,18 @@ bool AITASK_IsMarineSecureHiveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask*
bool bSecuredResNode = (!ResNode || (ResNode->OwningTeam == BotTeam && !FNullEnt(ResNode->ActiveTowerEntity) && UTIL_StructureIsFullyBuilt(ResNode->ActiveTowerEntity)));
return !((!bPhaseGatesAvailable || bHasPhaseGate) && bHasTurretFactory && NumTurrets >= 5 && bSecuredResNode);
if ((bPhaseGatesAvailable && !bHasPhaseGate) || !bHasTurretFactory || NumTurrets < 5) { return true; }
// Don't consider a hive secured if the enemy still has their junk in there. Clear it all out.
DeployableSearchFilter EnemyStructures;
EnemyStructures.DeployableTypes = SEARCH_ALL_STRUCTURES;
EnemyStructures.DeployableTeam = AIMGR_GetEnemyTeam(BotTeam);
EnemyStructures.ReachabilityTeam = BotTeam;
EnemyStructures.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
EnemyStructures.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f);
return AITAC_DeployableExistsAtLocation(HiveToSecure->FloorLocation, &EnemyStructures);
}
bool AITASK_IsEvolveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
@ -1214,7 +1268,6 @@ void BotProgressReinforceStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
if (!FNullEnt(Task->TaskSecondaryTarget))
{
if (UTIL_StructureIsFullyBuilt(Task->TaskSecondaryTarget))
{
Task->TaskSecondaryTarget = nullptr;
@ -1448,6 +1501,24 @@ void BotProgressReinforceStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
if (pBot->Player->GetResources() < ResRequired)
{
if (!IsPlayerGorge(pBot->Edict) || PlayerHasWeapon(pBot->Player, WEAPON_GORGE_BILEBOMB))
{
DeployableSearchFilter EnemyStuff;
EnemyStuff.DeployableTeam = AIMGR_GetEnemyTeam(pBot->Player->GetTeam());
EnemyStuff.DeployableTypes = SEARCH_ALL_STRUCTURES;
EnemyStuff.ReachabilityTeam = pBot->Player->GetTeam();
EnemyStuff.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
EnemyStuff.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f);
AvHAIBuildableStructure* NearestEnemyStructure = AITAC_FindClosestDeployableToLocation(Task->TaskTarget->v.origin, &EnemyStuff);
if (NearestEnemyStructure)
{
BotAttackNonPlayerTarget(pBot, NearestEnemyStructure->edict);
return;
}
}
BotGuardLocation(pBot, Task->TaskLocation);
return;
}
@ -1460,7 +1531,7 @@ void BotProgressReinforceStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
if (!IsPlayerGorge(pBot->Edict))
{
AITASK_SetEvolveTask(pBot, &pBot->WantsAndNeedsTask, pBot->Edict->v.origin, ALIEN_LIFEFORM_TWO, true);
BotEvolveLifeform(pBot, pBot->Edict->v.origin, ALIEN_LIFEFORM_TWO);
return;
}
@ -1579,14 +1650,6 @@ void AIPlayerBuildStructure(AvHAIPlayer* pBot, edict_t* BuildTarget)
MoveTo(pBot, BuildTarget->v.origin, MOVESTYLE_NORMAL);
if (IsPlayerMarine(pBot->Edict))
{
if (gpGlobals->time - pBot->LastCombatTime > 5.0f)
{
BotReloadWeapons(pBot);
}
}
if (vDist2DSq(pBot->Edict->v.origin, BuildTarget->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(5.0f)))
{
BotLookAt(pBot, UTIL_GetCentreOfEntity(BuildTarget));
@ -2204,107 +2267,140 @@ void AlienProgressCapResNodeTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
if (!ResNodeIndex) { return; }
int NumResourcesRequired = (IsPlayerGorge(pBot->Edict) ? BALANCE_VAR(kResourceTowerCost) : (BALANCE_VAR(kResourceTowerCost) + BALANCE_VAR(kGorgeCost)));
AvHTeamNumber BotTeam = pBot->Player->GetTeam();
float DistFromNode = vDist2DSq(pBot->Edict->v.origin, Task->TaskLocation);
// We can attack structures if we're not a gorge stuck with spit as our only offensive weapon
bool bBotCanAttackStructures = !IsPlayerGorge(pBot->Edict) || PlayerHasWeapon(pBot->Player, WEAPON_GORGE_BILEBOMB);
if (DistFromNode > sqrf(UTIL_MetresToGoldSrcUnits(2.0f)) || !UTIL_QuickTrace(pBot->Edict, pBot->CurrentEyePosition, (Task->TaskLocation + Vector(0.0f, 0.0f, 50.0f))))
// First, clear out any marine phase gates nearby so they can't send backup, if we can actually do meaningful damage to them
if (bBotCanAttackStructures)
{
MoveTo(pBot, Task->TaskLocation, MOVESTYLE_NORMAL);
return;
AvHClassType EnemyClassType = AIMGR_GetEnemyTeamType(BotTeam);
if (EnemyClassType == AVH_CLASS_TYPE_MARINE)
{
DeployableSearchFilter PGFilter;
PGFilter.DeployableTeam = AIMGR_GetEnemyTeam(BotTeam);
PGFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE;
PGFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f);
PGFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
AvHAIBuildableStructure* PG = AITAC_FindClosestDeployableToLocation(ResNodeIndex->Location, &PGFilter);
if (PG)
{
BotAttackNonPlayerTarget(pBot, PG->edict);
return;
}
}
}
if (ResNodeIndex->bIsOccupied)
{
Task->TaskTarget = ResNodeIndex->ActiveTowerEntity;
if (ResNodeIndex->OwningTeam != pBot->Player->GetTeam())
{
if (IsPlayerGorge(pBot->Edict) && !PlayerHasWeapon(pBot->Player, WEAPON_GORGE_BILEBOMB))
{
int NumAllies = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), Task->TaskLocation, UTIL_MetresToGoldSrcUnits(5.0f), false, pBot->Edict, AVH_USER3_ALIEN_PLAYER2);
if (NumAllies > 0)
{
BotGuardLocation(pBot, Task->TaskLocation);
}
else
{
BotEvolveLifeform(pBot, ALIEN_LIFEFORM_ONE);
}
}
else
{
AvHAIWeapon AttackWeapon = BotAlienChooseBestWeaponForStructure(pBot, Task->TaskTarget);
float MaxRange = GetMaxIdealWeaponRange(AttackWeapon);
bool bHullSweep = IsMeleeWeapon(AttackWeapon);
if (UTIL_PlayerHasLOSToEntity(pBot->Edict, Task->TaskTarget, MaxRange, bHullSweep))
{
pBot->DesiredCombatWeapon = AttackWeapon;
if (GetPlayerCurrentWeapon(pBot->Player) == AttackWeapon)
{
BotShootTarget(pBot, pBot->DesiredCombatWeapon, Task->TaskTarget);
return;
}
}
else
{
MoveTo(pBot, Task->TaskTarget->v.origin, MOVESTYLE_NORMAL);
}
}
return;
}
else
// If we have a tower on there already then help build it if we're gorge
if (ResNodeIndex->OwningTeam == BotTeam)
{
if (!UTIL_StructureIsFullyBuilt(ResNodeIndex->ActiveTowerEntity))
{
if (UTIL_PlayerHasLOSToEntity(pBot->Edict, ResNodeIndex->ActiveTowerEntity, max_player_use_reach, true))
if (IsPlayerGorge(pBot->Edict))
{
BotUseObject(pBot, ResNodeIndex->ActiveTowerEntity, true);
if (vDist2DSq(pBot->Edict->v.origin, ResNodeIndex->ActiveTowerEntity->v.origin) > sqrf(60.0f))
{
MoveDirectlyTo(pBot, ResNodeIndex->ActiveTowerEntity->v.origin);
}
AIPlayerBuildStructure(pBot, ResNodeIndex->ActiveTowerEntity);
return;
}
}
}
}
else
{
// If the enemy owns it then destroy it if we can, or go Skulk to do so.
if (bBotCanAttackStructures)
{
BotAttackNonPlayerTarget(pBot, ResNodeIndex->ActiveTowerEntity);
return;
}
else
{
BotEvolveLifeform(pBot, pBot->Edict->v.origin, ALIEN_LIFEFORM_ONE);
return;
}
}
}
else
{
// Node is empty and not capped by either side
MoveTo(pBot, ResNodeIndex->ActiveTowerEntity->v.origin, MOVESTYLE_NORMAL);
int NumResourcesRequired = (IsPlayerGorge(pBot->Edict) ? BALANCE_VAR(kResourceTowerCost) : (BALANCE_VAR(kResourceTowerCost) + BALANCE_VAR(kGorgeCost)));
if (vDist2DSq(pBot->Edict->v.origin, ResNodeIndex->ActiveTowerEntity->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(5.0f)))
// We have enough resources to place the tower (includes cost of evolving to gorge if necessary)
if (pBot->Player->GetResources() >= NumResourcesRequired)
{
float CurrDist = vDist2DSq(pBot->CurrentFloorPosition, ResNodeIndex->Location);
// Get close enough to place the tower if we aren't
if (CurrDist > sqrf(UTIL_MetresToGoldSrcUnits(3.0f)))
{
MoveTo(pBot, ResNodeIndex->Location, MOVESTYLE_NORMAL);
return;
}
// Back up a bit if we're too close
if (CurrDist < sqrf(UTIL_MetresToGoldSrcUnits(1.0f)))
{
BotLookAt(pBot, ResNodeIndex->Location);
Vector Orientation = UTIL_GetVectorNormal2D(pBot->Edict->v.origin - ResNodeIndex->Location);
Vector NewMoveLoc = ResNodeIndex->Location + (Orientation * UTIL_MetresToGoldSrcUnits(2.0f));
MoveToWithoutNav(pBot, NewMoveLoc);
return;
}
if (!IsPlayerGorge(pBot->Edict))
{
BotEvolveLifeform(pBot, pBot->Edict->v.origin, ALIEN_LIFEFORM_TWO);
return;
}
else
{
BotLookAt(pBot, Task->TaskLocation);
if (gpGlobals->time - Task->LastBuildAttemptTime < 1.0f) { return; }
float LookDot = UTIL_GetDotProduct2D(UTIL_GetForwardVector2D(pBot->Edict->v.v_angle), UTIL_GetVectorNormal2D(Task->TaskLocation - pBot->Edict->v.origin));
if (LookDot > 0.9f)
{
BotLookAt(pBot, UTIL_GetCentreOfEntity(ResNodeIndex->ActiveTowerEntity));
pBot->Impulse = ALIEN_BUILD_RESOURCES;
Task->LastBuildAttemptTime = gpGlobals->time + 1.0f;
Task->bIsWaitingForBuildLink = true;
Task->BuildAttempts++;
}
return;
}
}
return;
}
if (!IsPlayerGorge(pBot->Edict))
// We don't have enough resources to cap the node yet, so take out any enemy structures in the area while we wait if we can
if (bBotCanAttackStructures)
{
BotEvolveLifeform(pBot, ALIEN_LIFEFORM_TWO);
return;
DeployableSearchFilter EnemyStructureFilter;
EnemyStructureFilter.DeployableTeam = AIMGR_GetEnemyTeam(BotTeam);
EnemyStructureFilter.DeployableTypes = SEARCH_ALL_STRUCTURES;
EnemyStructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f);
AvHAIBuildableStructure* AttackTarget = AITAC_FindClosestDeployableToLocation(ResNodeIndex->Location, &EnemyStructureFilter);
if (AttackTarget)
{
BotAttackNonPlayerTarget(pBot, AttackTarget->edict);
return;
}
}
BotLookAt(pBot, Task->TaskLocation);
// No structures to take out, just wait for resources
BotGuardLocation(pBot, ResNodeIndex->Location);
if (gpGlobals->time - Task->LastBuildAttemptTime < 1.0f) { return; }
float LookDot = UTIL_GetDotProduct2D(UTIL_GetForwardVector2D(pBot->Edict->v.v_angle), UTIL_GetVectorNormal2D(Task->TaskLocation - pBot->Edict->v.origin));
if (LookDot > 0.9f)
{
pBot->Impulse = ALIEN_BUILD_RESOURCES;
Task->LastBuildAttemptTime = gpGlobals->time + 1.0f;
Task->bIsWaitingForBuildLink = true;
Task->BuildAttempts++;
}
}
void BotProgressTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
@ -2477,17 +2573,27 @@ void MarineProgressSecureHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
StructureFilter.DeployableTeam = BotTeam;
StructureFilter.ReachabilityTeam = BotTeam;
StructureFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING | STRUCTURE_STATUS_COMPLETED;
StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
vector<AvHAIBuildableStructure*> BuildableStructures = AITAC_FindAllDeployables(Hive->FloorLocation, &StructureFilter);
bool bKeyStructureBuilt = false;
AvHAIBuildableStructure* StructureToBuild = nullptr;
float MinDist = 0.0f;
for (auto it = BuildableStructures.begin(); it != BuildableStructures.end(); it++)
{
AvHAIBuildableStructure* ThisStructure = (*it);
if ((ThisStructure->StructureStatusFlags & STRUCTURE_STATUS_COMPLETED) && (ThisStructure->StructureType & (STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY | STRUCTURE_MARINE_PHASEGATE)))
{
bKeyStructureBuilt = true;
}
if (ThisStructure->StructureStatusFlags & STRUCTURE_STATUS_COMPLETED) { continue; }
// Phase gates always take priority, so just go and build it if there is one
if (ThisStructure->StructureType == STRUCTURE_MARINE_PHASEGATE)
{
AIPlayerBuildStructure(pBot, ThisStructure->edict);
@ -2515,8 +2621,13 @@ void MarineProgressSecureHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
{
if (ResNode->OwningTeam != BotTeam)
{
BotAttackTarget(pBot, ResNode->ActiveTowerEntity);
return;
// Don't attack the RT until we have build a TF or PG. Avoids giving the game away too quickly
if (bKeyStructureBuilt)
{
BotAttackNonPlayerTarget(pBot, ResNode->ActiveTowerEntity);
return;
}
}
else
{
@ -2528,6 +2639,25 @@ void MarineProgressSecureHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
}
}
// We won't start attacking enemy structures until we have built a turret factory or phase gate so we don't reveal our evil plans until we're ready
if (bKeyStructureBuilt)
{
DeployableSearchFilter EnemyStructures;
EnemyStructures.DeployableTypes = SEARCH_ALL_STRUCTURES;
EnemyStructures.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f);
EnemyStructures.DeployableTeam = AIMGR_GetEnemyTeam(BotTeam);
EnemyStructures.ReachabilityTeam = BotTeam;
EnemyStructures.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
AvHAIBuildableStructure* EnemyStructure = AITAC_FindClosestDeployableToLocation(Hive->FloorLocation, &EnemyStructures);
if (EnemyStructure)
{
BotAttackNonPlayerTarget(pBot, EnemyStructure->edict);
return;
}
}
BotGuardLocation(pBot, Task->TaskLocation);
}
@ -2536,34 +2666,26 @@ void MarineProgressCapResNodeTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
{
if (!Task) { return; }
float DistFromNode = vDist2DSq(pBot->Edict->v.origin, Task->TaskLocation);
const AvHAIResourceNode* ResNodeIndex = AITAC_GetNearestResourceNodeToLocation(Task->TaskLocation);
if (DistFromNode > sqrf(UTIL_MetresToGoldSrcUnits(5.0f)) || !UTIL_QuickTrace(pBot->Edict, pBot->CurrentEyePosition, (Task->TaskLocation + Vector(0.0f, 0.0f, 50.0f))))
// This shouldn't happen, but if somehow it does then at least do SOMETHING
if (!ResNodeIndex)
{
MoveTo(pBot, Task->TaskLocation, MOVESTYLE_NORMAL);
if (gpGlobals->time - pBot->LastCombatTime > 5.0f)
{
BotReloadWeapons(pBot);
}
return;
}
const AvHAIResourceNode* ResNodeIndex = AITAC_GetNearestResourceNodeToLocation(Task->TaskLocation);
if (!ResNodeIndex) { return; }
// There is a res tower, ours or the enemies
if (ResNodeIndex->bIsOccupied)
{
Task->TaskTarget = ResNodeIndex->ActiveTowerEntity;
// Cancel the waiting timeout since a tower has been placed for us
Task->TaskLength = 0.0f;
if (ResNodeIndex->OwningTeam == pBot->Player->GetTeam())
{
if (!UTIL_StructureIsFullyBuilt(ResNodeIndex->ActiveTowerEntity))
{
// Cancel the waiting timeout since there is something for us to do
Task->TaskLength = 0.0f;
AIPlayerBuildStructure(pBot, ResNodeIndex->ActiveTowerEntity);
return;
@ -2571,29 +2693,63 @@ void MarineProgressCapResNodeTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
}
else
{
AvHAIWeapon AttackWeapon = BotMarineChooseBestWeaponForStructure(pBot, Task->TaskTarget);
// Cancel the waiting timeout since there is something for us to do
Task->TaskLength = 0.0f;
float MaxRange = GetMaxIdealWeaponRange(AttackWeapon);
bool bHullSweep = IsMeleeWeapon(AttackWeapon);
// If we're playing MvM, then check the enemy hasn't got a phase gate nearby which could bring in defenders. If so, take that out first.
AvHClassType EnemyType = AIMGR_GetEnemyTeamType(pBot->Player->GetTeam());
if (UTIL_PlayerHasLOSToEntity(pBot->Edict, Task->TaskTarget, MaxRange, bHullSweep))
if (EnemyType == AVH_CLASS_TYPE_MARINE)
{
pBot->DesiredCombatWeapon = AttackWeapon;
DeployableSearchFilter EnemyStructureFilter;
EnemyStructureFilter.DeployableTeam = AIMGR_GetEnemyTeam(pBot->Player->GetTeam());
EnemyStructureFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE;
EnemyStructureFilter.ReachabilityTeam = pBot->Player->GetTeam();
EnemyStructureFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
EnemyStructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f);
if (GetPlayerCurrentWeapon(pBot->Player) == AttackWeapon)
AvHAIBuildableStructure* EnemyStructure = AITAC_FindClosestDeployableToLocation(Task->TaskLocation, &EnemyStructureFilter);
if (EnemyStructure)
{
//BotShootTarget(pBot, pBot->DesiredCombatWeapon, Task->TaskTarget);
BotAttackNonPlayerTarget(pBot, EnemyStructure->edict);
return;
}
}
else
{
MoveTo(pBot, Task->TaskTarget->v.origin, MOVESTYLE_NORMAL);
}
BotAttackNonPlayerTarget(pBot, ResNodeIndex->ActiveTowerEntity);
return;
}
}
// Clear out any enemy structures around the node
DeployableSearchFilter EnemyStructureFilter;
EnemyStructureFilter.DeployableTeam = AIMGR_GetEnemyTeam(pBot->Player->GetTeam());
EnemyStructureFilter.DeployableTypes = SEARCH_ALL_STRUCTURES;
EnemyStructureFilter.ReachabilityTeam = pBot->Player->GetTeam();
EnemyStructureFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
EnemyStructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f);
AvHAIBuildableStructure* EnemyStructure = AITAC_FindClosestDeployableToLocation(Task->TaskLocation, &EnemyStructureFilter);
if (EnemyStructure)
{
// Cancel the waiting timeout since we have something useful to do
Task->TaskLength = 0.0f;
BotAttackNonPlayerTarget(pBot, EnemyStructure->edict);
return;
}
else
{
// Give the commander 30 seconds to drop a tower for us, or give up and move on
// If we're not at our destination yet, go there
if (vDist2DSq(pBot->Edict->v.origin, Task->TaskLocation) > UTIL_MetresToGoldSrcUnits(5.0f))
{
MoveTo(pBot, Task->TaskLocation, MOVESTYLE_NORMAL);
return;
}
// Empty res node with nothing to do but wait, stick around for 30 seconds and then move on if the commander doesn't drop an RT to build
if (Task->TaskLength == 0.0f)
{
Task->TaskStartedTime = gpGlobals->time;
@ -3104,9 +3260,11 @@ void AITASK_SetCapResNodeTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, const Av
AITASK_ClearBotTask(pBot, Task);
Vector WaitLocation = UTIL_GetRandomPointOnNavmeshInRadius(pBot->BotNavInfo.NavProfile, NodeRef->Location, UTIL_MetresToGoldSrcUnits(1.0f));
Task->TaskType = TASK_CAP_RESNODE;
Task->StructureType = NodeStructureType;
Task->TaskLocation = NodeRef->Location;
Task->TaskLocation = (!vIsZero(WaitLocation)) ? WaitLocation : NodeRef->Location;
if (!FNullEnt(NodeRef->ActiveTowerEntity))
{
@ -3247,6 +3405,8 @@ void AITASK_SetReinforceStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task,
AITASK_ClearBotTask(pBot, Task);
if (FNullEnt(Target) || Target->v.deadflag != DEAD_NO) { return; }
Task->TaskType = TASK_REINFORCE_STRUCTURE;
Task->TaskTarget = Target;
Task->bTaskIsUrgent = bIsUrgent;

View file

@ -682,6 +682,11 @@ AvHAIWeapon BotAlienChooseBestWeaponForStructure(AvHAIPlayer* pBot, edict_t* tar
return WEAPON_GORGE_BILEBOMB;
}
if (PlayerHasWeapon(pBot->Player, WEAPON_FADE_ACIDROCKET) && StructureType == STRUCTURE_ALIEN_HIVE || IsDamagingStructure(StructureType))
{
return WEAPON_FADE_ACIDROCKET;
}
// If we have xenocide, then choose it if we have lots of good targets in blast radius
if (PlayerHasWeapon(pBot->Player, WEAPON_SKULK_XENOCIDE))
{
@ -718,7 +723,7 @@ AvHAIWeapon BotMarineChooseBestWeaponForStructure(AvHAIPlayer* pBot, edict_t* ta
{
AvHAIDeployableStructureType StructureType = GetStructureTypeFromEdict(target);
if (StructureType == STRUCTURE_NONE)
if (StructureType == STRUCTURE_NONE || StructureType == STRUCTURE_ALIEN_HIVE || IsDamagingStructure(StructureType))
{
if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0 || UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0)
{
@ -734,34 +739,14 @@ AvHAIWeapon BotMarineChooseBestWeaponForStructure(AvHAIPlayer* pBot, edict_t* ta
}
}
if (StructureType == STRUCTURE_ALIEN_HIVE || StructureType == STRUCTURE_ALIEN_OFFENCECHAMBER)
{
if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0 || UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0)
{
return UTIL_GetPlayerPrimaryWeapon(pBot->Player);
}
else if (BotGetSecondaryWeaponClipAmmo(pBot) > 0 || BotGetSecondaryWeaponAmmoReserve(pBot) > 0)
{
return GetBotMarineSecondaryWeapon(pBot);
}
else
{
return WEAPON_MARINE_KNIFE;
}
}
else
{
AvHAIWeapon PrimaryWeapon = UTIL_GetPlayerPrimaryWeapon(pBot->Player);
AvHAIWeapon PrimaryWeapon = UTIL_GetPlayerPrimaryWeapon(pBot->Player);
if ((PrimaryWeapon == WEAPON_MARINE_GL || PrimaryWeapon == WEAPON_MARINE_SHOTGUN) && (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0 || UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0))
{
return PrimaryWeapon;
}
return WEAPON_MARINE_KNIFE;
if ((PrimaryWeapon == WEAPON_MARINE_GL || PrimaryWeapon == WEAPON_MARINE_SHOTGUN) && (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0 || UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0))
{
return PrimaryWeapon;
}
return UTIL_GetPlayerPrimaryWeapon(pBot->Player);
return WEAPON_MARINE_KNIFE;
}
AvHAIWeapon GorgeGetBestWeaponForCombatTarget(AvHAIPlayer* pBot, edict_t* Target)

View file

@ -1498,27 +1498,39 @@ BOOL AvHGamerules::ClientCommand( CBasePlayer *pPlayer, const char *pcmd )
theSuccess = true;
}
else if (FStrEq(pcmd, "testcommanderbuild"))
else if (FStrEq(pcmd, "setdebugaiplayer"))
{
AvHAIPlayer* AIComm = AIMGR_GetAICommander(theAvHPlayer->GetTeam());
CBaseEntity* SpectatedPlayer = theAvHPlayer->GetSpectatingEntity();
if (AIComm)
if (SpectatedPlayer)
{
AIMGR_SetDebugAIPlayer(SpectatedPlayer->edict());
}
Vector TraceStart = GetPlayerEyePosition(theAvHPlayer->edict()); // origin + pev->view_ofs
Vector LookDir = UTIL_GetForwardVector(theAvHPlayer->edict()->v.v_angle); // Converts view angles to normalized unit vector
theSuccess = true;
}
else if (FStrEq(pcmd, "testalienreinforce"))
{
vector<AvHAIPlayer*> AlienPlayers = AIMGR_GetAIPlayersOnTeam(TEAM_TWO);
Vector TraceEnd = TraceStart + (LookDir * 1000.0f);
if (AlienPlayers.size() > 0)
{
AvHAIPlayer* NewCapper = AlienPlayers[0];
TraceResult Hit;
UTIL_TraceLine(TraceStart, TraceEnd, ignore_monsters, theAvHPlayer->edict(), &Hit);
if (Hit.flFraction < 1.0f)
if (NewCapper)
{
AICOMM_DeployStructure(AIComm, STRUCTURE_MARINE_ARMOURY, Hit.vecEndPos);
}
DeployableSearchFilter ResNodeFilter;
ResNodeFilter.DeployableTeam = TEAM_TWO;
ResNodeFilter.ReachabilityTeam = TEAM_TWO;
ResNodeFilter.ReachabilityFlags = NewCapper->BotNavInfo.NavProfile.ReachabilityFlag;
AvHAIResourceNode* ResNode = AITAC_FindNearestResourceNodeToLocation(NewCapper->Edict->v.origin, &ResNodeFilter);
if (ResNode)
{
AITASK_SetReinforceStructureTask(NewCapper, &NewCapper->PrimaryBotTask, ResNode->ActiveTowerEntity, true);
}
}
}
theSuccess = true;