Compare commits

...

32 commits

Author SHA1 Message Date
RGreenlees
be1aead879 Potential crash fix
* Hopefully fixed rare crash in the Detour library
* Improved bot retreat thought process
* Fixed potential bot stuck when trying to evolve
* Aliens will now also see and track enemies marked by OC / scent of fear
* Updated some nav meshes
2024-07-03 14:33:49 -04:00
RGreenlees
cea65c1fdc Further bot improvements
* Fixed nav meshes for lost and machina which could get bots stuck
* Improved ladder usage, fixed edge cases where bots could get stuck
* At match start, aliens will attempt to spread chambers out if the first chamber is MC or SC, rather than clustering them all at the starting hive
* Eliminated potential freeze issues with nav mesh refreshes
2024-07-03 14:33:49 -04:00
RGreenlees
b1bcdd2f70 Update editor docs and crouch fix
* Bots now properly crouch in crouch areas rather than relying on their stuck detection to do it
* Updated Nav Editor docs to reflect recent changes
2024-07-03 14:33:49 -04:00
RGreenlees
290ad067eb General improvements
* AI Commander will now attempt to place turret factories close to any existing turrets to prevent inactive turrets being left behind
* Aliens will not try to pursue enemies who have teleported away (unless they teleported close by)
* Fixed a bug where bots would not target the nearest sentry/OC to engage
* Improved how marines attack OCs, particularly how they take cover to reload
2024-07-03 14:33:49 -04:00
RGreenlees
a01d8a6708 Nav mesh and editor improvements
* Off-mesh connections in the nav editor are now colour-coded to make it easier to see what they do
* Improved nav meshes for a few maps to fix stuck issues
2024-07-03 14:33:49 -04:00
RGreenlees
0a15148352 Remove Debug Code 2024-07-03 14:33:49 -04:00
RGreenlees
4102dabe05 Further bot improvements
* Player-requested structures are properly integrated into the base system to prevent duplicates and better anticipate player desires
* Improved bot path finding for moving targets, hopefully they get caught less often on railings and stuff when pursuing a moving target
* When bots teleport (e.g. phase gate, but any kind of teleport), they clear their stored unstuck locations so they don't try and backtrack when attempting to get unstuck
2024-07-03 14:33:49 -04:00
RGreenlees
11512d9d28 Update DetourTileCache.cpp 2024-07-03 14:33:49 -04:00
RGreenlees
733ad6eb04 Fixed base bugs
* Fixed issue where commander would build siege turrets too far from the siege target
* Improved commander response to requests for turrets
* Nav reachabilities and paths are now regenerated after all nav mesh modifications are updated
2024-07-03 14:33:49 -04:00
RGreenlees
2842aba0bb Bot reload improvements and nav mesh updates 2024-07-03 14:33:49 -04:00
RGreenlees
65155fb3f0 Finish base system 2024-07-03 14:33:49 -04:00
RGreenlees
9ee01c0d64 Base system Enhancements
* Nearly finished base system
* Bots attacking structures no longer confused by "super chambers"
2024-07-03 14:33:49 -04:00
RGreenlees
6f263d400b New base building setup
* Improved base strategy and planning
* Bot is better at taking over from a human - less likely to sell up stuff unnecessarily
2024-07-03 14:33:49 -04:00
RGreenlees
0897cf15a0 Started base system
* Commanders now think in terms of establishing and building bases rather than placing individual structures. Should help reduce duplication of structures, selling structures unnecessarily etc.
* Incomplete implementation: not currently used but will be once finished
2024-07-03 14:33:49 -04:00
RGreenlees
73ff332b68 Removed debug code 2024-06-03 23:53:08 -04:00
RGreenlees
5b83a4c886 Fix crash on marine victory in combat
* Fixed a bug where bots could crash when marines successfully destroy the alien hive in CO
2024-06-03 23:53:08 -04:00
RGreenlees
2b27b90f25 Stuck fixes
* Hopefully fixed issue that caused bots to get stuck in the tubes in Ragnarok
2024-06-03 23:53:08 -04:00
RGreenlees
f427a9c052 Stability and randomness improvements
* Fixed potential crashing when obtaining references to dropped items
* Fixed issues with random number generation
2024-06-03 23:53:08 -04:00
RGreenlees
ef7e7a9178 Fixed randomness
* Fixed mistake that caused random values to follow a constant pattern
2024-06-03 23:53:08 -04:00
RGreenlees
36318cd1d4 Ladder fix + relocation fixes
* Improved ladder climbing, especially for skulks
* Fixed issues with bots getting confused when switching commanders during a relocation
2024-06-03 23:53:08 -04:00
RGreenlees
9a4a6b3660 Commander Placements Tweaks
* Commander won't get stuck trying to build base structures if using nav hints that are placed in bad spots (i.e. can't deploy structures where the nav hint is)
* Commander can now deploy prototype labs, arms labs and infantry portals on request
2024-06-03 23:53:08 -04:00
RGreenlees
9452b8a0c3 Team fill logic adjusted
* The auto-fill system for bots now takes mp_limitteams into account, and will continue to add bots to one side if the team size config specifies it to
2024-06-03 23:53:08 -04:00
RGreenlees
8fef7241a0 Updated nav meshes for relocation
* All nav meshes now have hints to help the commander relocate in good spots and build their base inside a hive appropriately
* Updated the nav editor to hide "illusionary" surfaces from other tools where they get in the way
2024-06-03 23:53:08 -04:00
RGreenlees
e17fba76ea Bot Relocation
* Marine bots can now relocate their base to a nearby hive
* If the chance to relocate is above 0 (configurable in nsbots.ini) then the bots will relocate at the start of a match based on the percentage set, or if the current base is overrun and about to be lost
* The bot will recycle the old comm chair
* The bot will abandon the relocation attempt if unsuccessful 90 seconds into a match
2024-06-03 23:53:08 -04:00
RGreenlees
91231ac069 Marine Relocation
* The AI Commander can now relocate to a nearby empty hive at the start of a match, or if the current base is almost lost
2024-06-03 23:53:08 -04:00
RGreenlees
c2e41c2011 Bot movement improvements
* Commander no longer sends reminders once an order is issued
* Fixed an issue where marines would loiter and refuse to move on once securing a hive
* Improved wall climbing: bots handle varied wall angles better
2024-06-03 23:53:08 -04:00
RGreenlees
5beb313546 Alien coordinated base attacks
* Aliens will now gang up to attack the marine base
* Aliens will spread out more when attacking in a group, rather than all going for the same buildings
2024-06-03 23:53:08 -04:00
RGreenlees
c0bef7cb05 Improved evasive behaviour
* Bots only zig-zag when approaching a ranged enemy if that enemy is aiming at them
* Bots will not zig-zag off edges, or up against walls
2024-06-03 23:53:08 -04:00
RGreenlees
6ffc3d3258 Improved hive building
* Bots no longer squabble over who's building the hive
2024-06-03 23:53:08 -04:00
RGreenlees
d17cb601eb Task priority and path merging
* Potentially fixed issue causing invalid paths
* Fixed issue where gorges wouldn't heal anyone while focused on capping res node or building defensive structures
2024-06-03 23:53:08 -04:00
RGreenlees
ff3b4afb1c Bot stuck checks
* Bots will now suicide if falling for more than 15 seconds, handle stuck situations
* Bots will hopefully not get stuck in walk nodes as much
2024-06-03 23:53:08 -04:00
RGreenlees
fcddb5b6c3 Resource capping tweak and comm reminders
* Marines will now get close initially to a resource node they want to cap, before guarding it. This fixes the issue in Hera where bots would wait for the RT in cargo to drop but never actually open the door so the commander could drop it
* Disabled AI commander reissuing orders to players as a reminder. It was annoying for humans, and pointless for bots since they should always obey it right away anyway
2024-06-03 23:53:08 -04:00
43 changed files with 5519 additions and 1698 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -14,6 +14,10 @@ MaxAIMatchTime=90
# 2 = When the round has started (after countdown) # 2 = When the round has started (after countdown)
BotFillTiming=1 BotFillTiming=1
# Chance the AI commander will try to relocate to an empty hive at match start
# Value is a decimal between 0.0 and 1.0, with 0 being never and 1 being always
# Note that setting relocation chance to 0.0 will also disable ANY relocation at any time
RelocationChance=0.2
### Skill Settings ### ### Skill Settings ###

View file

@ -842,6 +842,8 @@ dtStatus dtTileCache::update(const float /*dt*/, dtNavMesh* navmesh,
} }
bool bOffMeshChanged = false;
if (m_nupdate == 0) if (m_nupdate == 0)
{ {
for (int i = 0; i < m_params.maxOffMeshConnections; ++i) for (int i = 0; i < m_params.maxOffMeshConnections; ++i)
@ -854,6 +856,7 @@ dtStatus dtTileCache::update(const float /*dt*/, dtNavMesh* navmesh,
navmesh->baseOffMeshLinks(con); navmesh->baseOffMeshLinks(con);
navmesh->GlobalOffMeshLinks(con); navmesh->GlobalOffMeshLinks(con);
con->state = DT_OFFMESH_CLEAN; con->state = DT_OFFMESH_CLEAN;
bOffMeshChanged = true;
} }
if (con->state == DT_OFFMESH_REMOVING) if (con->state == DT_OFFMESH_REMOVING)
@ -869,7 +872,7 @@ dtStatus dtTileCache::update(const float /*dt*/, dtNavMesh* navmesh,
} }
if (upToDate) if (upToDate)
*upToDate = m_nupdate == 0 && m_nreqs == 0 && m_nOffMeshReqs == 0; *upToDate = m_nupdate == 0 && m_nreqs == 0 && m_nOffMeshReqs == 0 && !bOffMeshChanged;
return status; return status;
} }

File diff suppressed because it is too large Load diff

View file

@ -14,7 +14,7 @@
static const float MIN_COMMANDER_REMIND_TIME = 20.0f; // How frequently the commander can nag a player to do something, if they don't think they're doing it static const float MIN_COMMANDER_REMIND_TIME = 20.0f; // How frequently the commander can nag a player to do something, if they don't think they're doing it
bool AICOMM_DeployStructure(AvHAIPlayer* pBot, const AvHAIDeployableStructureType StructureToDeploy, const Vector Location, StructurePurpose Purpose = STRUCTURE_PURPOSE_GENERAL); AvHAIBuildableStructure* AICOMM_DeployStructure(AvHAIPlayer* pBot, const AvHAIDeployableStructureType StructureToDeploy, const Vector Location, StructurePurpose Purpose = STRUCTURE_PURPOSE_GENERAL, bool bPlacedByHuman = false);
bool AICOMM_DeployItem(AvHAIPlayer* pBot, const AvHAIDeployableItemType ItemToDeploy, const Vector Location); bool AICOMM_DeployItem(AvHAIPlayer* pBot, const AvHAIDeployableItemType ItemToDeploy, const Vector Location);
bool AICOMM_UpgradeStructure(AvHAIPlayer* pBot, AvHAIBuildableStructure* StructureToUpgrade); bool AICOMM_UpgradeStructure(AvHAIPlayer* pBot, AvHAIBuildableStructure* StructureToUpgrade);
bool AICOMM_ResearchTech(AvHAIPlayer* pBot, AvHAIBuildableStructure* StructureToResearch, AvHMessageID Research); bool AICOMM_ResearchTech(AvHAIPlayer* pBot, AvHAIBuildableStructure* StructureToResearch, AvHMessageID Research);
@ -27,34 +27,29 @@ bool AICOMM_IssueSiegeHiveOrder(AvHAIPlayer* pBot, edict_t* Recipient, const AvH
bool AICOMM_IssueSecureResNodeOrder(AvHAIPlayer* pBot, edict_t* Recipient, const AvHAIResourceNode* ResNode); bool AICOMM_IssueSecureResNodeOrder(AvHAIPlayer* pBot, edict_t* Recipient, const AvHAIResourceNode* ResNode);
void AICOMM_AssignNewPlayerOrder(AvHAIPlayer* pBot, edict_t* Assignee, edict_t* TargetEntity, AvHAIOrderPurpose OrderPurpose); void AICOMM_AssignNewPlayerOrder(AvHAIPlayer* pBot, edict_t* Assignee, edict_t* TargetEntity, AvHAIOrderPurpose OrderPurpose);
void AICOMM_AssignNewPlayerOrder(AvHAIPlayer* pBot, edict_t* Assignee, Vector OrderLocation, AvHAIOrderPurpose OrderPurpose);
int AICOMM_GetNumPlayersAssignedToOrder(AvHAIPlayer* pBot, edict_t* TargetEntity, AvHAIOrderPurpose OrderPurpose); int AICOMM_GetNumPlayersAssignedToOrder(AvHAIPlayer* pBot, edict_t* TargetEntity, AvHAIOrderPurpose OrderPurpose);
int AICOMM_GetNumPlayersAssignedToOrderType(AvHAIPlayer* pBot, AvHAIOrderPurpose OrderPurpose);
int AICOMM_GetNumPlayersAssignedToOrderLocation(AvHAIPlayer* pBot, Vector OrderLocation, AvHAIOrderPurpose OrderPurpose);
bool AICOMM_IsOrderStillValid(AvHAIPlayer* pBot, ai_commander_order* Order); bool AICOMM_IsOrderStillValid(AvHAIPlayer* pBot, ai_commander_order* Order);
void AICOMM_UpdatePlayerOrders(AvHAIPlayer* pBot); void AICOMM_UpdatePlayerOrders(AvHAIPlayer* pBot);
edict_t* AICOMM_GetPlayerWithNoOrderNearestLocation(AvHAIPlayer* pBot, Vector SearchLocation); edict_t* AICOMM_GetPlayerWithNoOrderNearestLocation(AvHAIPlayer* pBot, Vector SearchLocation);
edict_t* AICOMM_GetPlayerWithoutSpecificOrderNearestLocation(AvHAIPlayer* pBot, Vector SearchLocation, AvHAIOrderPurpose OrderPurpose);
bool AICOMM_DoesPlayerOrderNeedReminder(AvHAIPlayer* pBot, ai_commander_order* Order); bool AICOMM_DoesPlayerOrderNeedReminder(AvHAIPlayer* pBot, ai_commander_order* Order);
void AICOMM_IssueOrderForAssignedJob(AvHAIPlayer* pBot, ai_commander_order* Order); void AICOMM_IssueOrderForAssignedJob(AvHAIPlayer* pBot, ai_commander_order* Order);
void AICOMM_ClearAction(commander_action* Action);
bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot); bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot);
bool AICOMM_CheckForNextSupportAction(AvHAIPlayer* pBot); bool AICOMM_CheckForNextSupportAction(AvHAIPlayer* pBot);
bool AICOMM_CheckForNextRecycleAction(AvHAIPlayer* pBot); bool AICOMM_CheckForNextRecycleAction(AvHAIPlayer* pBot);
bool AICOMM_CheckForNextResearchAction(AvHAIPlayer* pBot); bool AICOMM_CheckForNextResearchAction(AvHAIPlayer* pBot);
bool AICOMM_CheckForNextSupplyAction(AvHAIPlayer* pBot); bool AICOMM_CheckForNextSupplyAction(AvHAIPlayer* pBot);
void AICOMM_SetDropHealthAction(AvHAIPlayer* pBot, commander_action* Action, edict_t* Recipient); Vector AICOMM_GetNextScanLocation(AvHAIPlayer* pBot);
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);
void AICOMM_SetDeployItemAction(AvHAIPlayer* pBot, commander_action* Action, AvHAIDeployableItemType ItemToBuild, const Vector Location, bool bIsUrgent);
void AICOMM_CommanderThink(AvHAIPlayer* pBot); void AICOMM_CommanderThink(AvHAIPlayer* pBot);
const AvHAIHiveDefinition* AICOMM_GetEmptyHiveOpportunityNearestLocation(AvHAIPlayer* CommanderBot, const Vector SearchLocation); const AvHAIHiveDefinition* AICOMM_GetEmptyHiveOpportunityNearestLocation(AvHAIPlayer* CommanderBot, const Vector SearchLocation);
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); ai_commander_request* AICOMM_GetExistingRequestForPlayer(AvHAIPlayer* pBot, edict_t* Requestor);
void AICOMM_CheckNewRequests(AvHAIPlayer* pBot); void AICOMM_CheckNewRequests(AvHAIPlayer* pBot);
bool AICOMM_IsRequestValid(ai_commander_request* Request); bool AICOMM_IsRequestValid(ai_commander_request* Request);
@ -71,4 +66,30 @@ bool AICOMM_ShouldBeacon(AvHAIPlayer* pBot);
void AICOMM_ReceiveChatRequest(AvHAIPlayer* Commander, edict_t* Requestor, const char* Request); void AICOMM_ReceiveChatRequest(AvHAIPlayer* Commander, edict_t* Requestor, const char* Request);
bool AICOMM_ShouldCommanderRelocate(AvHAIPlayer* pBot);
bool AICOMM_GetRelocationMessage(Vector RelocationPoint, char* MessageBuffer);
AvHAIMarineBase* AICOMM_AddNewBase(AvHAIPlayer* pBot, Vector NewBaseLocation, MarineBaseType NewBaseType);
bool AICOMM_AddStructureToBase(AvHAIPlayer* pBot, AvHAIDeployableStructureType StructureToDeploy, Vector BuildLocation, AvHAIMarineBase* BaseToAdd);
void AICOMM_ManageActiveBases(AvHAIPlayer* pBot);
bool AICOMM_IsMarineBaseValid(AvHAIMarineBase* Base);
void AICOMM_DeployBases(AvHAIPlayer* pBot);
vector<AvHAIBuildableStructure> AICOMM_GetBaseStructures(AvHAIMarineBase* Base);
void AICOMM_UpdateBaseStatus(AvHAIPlayer* pBot, AvHAIMarineBase* Base);
void AICOMM_UpdateSiegeBaseStatus(AvHAIPlayer* pBot, AvHAIMarineBase* Base);
void AICOMM_UpdateOutpostStatus(AvHAIPlayer* pBot, AvHAIMarineBase* Base);
void AICOMM_UpdateGuardpostStatus(AvHAIPlayer* pBot, AvHAIMarineBase* Base);
void AICOMM_UpdateMainBaseStatus(AvHAIPlayer* pBot, AvHAIMarineBase* Base);
bool AICOMM_BuildOutBase(AvHAIPlayer* pBot, AvHAIMarineBase* BaseToBuildOut);
bool AICOMM_BuildOutMainBase(AvHAIPlayer* pBot, AvHAIMarineBase* BaseToBuildOut);
bool AICOMM_BuildOutOutpost(AvHAIPlayer* pBot, AvHAIMarineBase* BaseToBuildOut);
bool AICOMM_BuildOutSiege(AvHAIPlayer* pBot, AvHAIMarineBase* BaseToBuildOut);
bool AICOMM_BuildOutGuardPost(AvHAIPlayer* pBot, AvHAIMarineBase* BaseToBuildOut);
AvHAIMarineBase* AICOMM_GetNearestBaseToLocation(AvHAIPlayer* pBot, Vector SearchLocation);
#endif // AVH_AI_COMMANDER_H #endif // AVH_AI_COMMANDER_H

View file

@ -12,12 +12,17 @@ BotFillTiming CurrentBotFillTiming = FILLTIMING_ALLHUMANS;
float MaxAIMatchTimeMinutes = 90.0f; float MaxAIMatchTimeMinutes = 90.0f;
float RelocationChance = 0.1f;
std::unordered_map<std::string, TeamSizeDefinitions> TeamSizeMap; std::unordered_map<std::string, TeamSizeDefinitions> TeamSizeMap;
bot_skill BotSkillLevels[4]; bot_skill BotSkillLevels[4];
std::vector<AvHMessageID> ChamberSequence; std::vector<AvHMessageID> ChamberSequence;
std::default_random_engine rng;
bool bRNGSeeded = false;
string DefaultBotNames[MAX_PLAYERS] = { "MrRobot", string DefaultBotNames[MAX_PLAYERS] = { "MrRobot",
"Wall-E", "Wall-E",
"BeepBoop", "BeepBoop",
@ -90,6 +95,16 @@ bool CONFIG_IsOnosAllowed()
return avh_botallowonos.value > 0; return avh_botallowonos.value > 0;
} }
bool CONFIG_IsRelocationAllowed()
{
return RelocationChance > 0.0f;
}
float CONFIG_GetRelocationChance()
{
return RelocationChance;
}
float CONFIG_GetMaxStuckTime() float CONFIG_GetMaxStuckTime()
{ {
return avh_botmaxstucktime.value; return avh_botmaxstucktime.value;
@ -172,8 +187,6 @@ void CONFIG_PopulateBotNames()
if (BotNames.size() > 2) if (BotNames.size() > 2)
{ {
auto rng = std::default_random_engine{};
rng.seed(time(0));
std::shuffle(begin(BotNames), end(BotNames), rng); std::shuffle(begin(BotNames), end(BotNames), rng);
} }
@ -187,8 +200,6 @@ void CONFIG_PopulateBotNames()
if (DefaultNames.size() > 2) if (DefaultNames.size() > 2)
{ {
auto rng = std::default_random_engine{};
rng.seed(time(0));
std::shuffle(begin(DefaultNames), end(DefaultNames), rng); std::shuffle(begin(DefaultNames), end(DefaultNames), rng);
} }
@ -262,9 +273,13 @@ void CONFIG_ParseConfigFile()
ChamberSequence.push_back(ALIEN_BUILD_MOVEMENT_CHAMBER); ChamberSequence.push_back(ALIEN_BUILD_MOVEMENT_CHAMBER);
ChamberSequence.push_back(ALIEN_BUILD_SENSORY_CHAMBER); ChamberSequence.push_back(ALIEN_BUILD_SENSORY_CHAMBER);
std::srand(time(0)); if (!bRNGSeeded)
auto rng = std::default_random_engine{}; {
srand(time(0));
rng.seed(time(0)); rng.seed(time(0));
bRNGSeeded = true;
}
std::shuffle(std::begin(ChamberSequence), std::end(ChamberSequence), rng); std::shuffle(std::begin(ChamberSequence), std::end(ChamberSequence), rng);
string BotConfigFile = string(getModDirectory()) + "/nsbots.ini"; string BotConfigFile = string(getModDirectory()) + "/nsbots.ini";
@ -328,6 +343,14 @@ void CONFIG_ParseConfigFile()
continue; continue;
} }
if (!stricmp(keyChar, "RelocationChance"))
{
float RelocationValue = std::stof(value.c_str());
RelocationChance = RelocationValue;
continue;
}
if (!stricmp(keyChar, "MaxAIMatchTime")) if (!stricmp(keyChar, "MaxAIMatchTime"))
{ {
float MaxMinutes = std::stof(value.c_str()); float MaxMinutes = std::stof(value.c_str());
@ -641,7 +664,11 @@ void CONFIG_RegenerateIniFile()
fprintf(NewConfigFile, "# 0 = On map load (after 5 second grace period)\n"); fprintf(NewConfigFile, "# 0 = On map load (after 5 second grace period)\n");
fprintf(NewConfigFile, "# 1 = When all humans have joined a team (i.e. no more humans left in ready room)\n"); fprintf(NewConfigFile, "# 1 = When all humans have joined a team (i.e. no more humans left in ready room)\n");
fprintf(NewConfigFile, "# 2 = When the round has started (after countdown)\n"); fprintf(NewConfigFile, "# 2 = When the round has started (after countdown)\n");
fprintf(NewConfigFile, "BotFillTiming=1\n\n\n"); fprintf(NewConfigFile, "BotFillTiming=1\n\n");
fprintf(NewConfigFile, "# Chance the AI Commander will try to relocate at the beginning of the game\n");
fprintf(NewConfigFile, "# Value is a decimal between 0.0 and 1.0, with 0 being never and 1 being always\n");
fprintf(NewConfigFile, "RelocationChance=0.1\n\n\n");
fprintf(NewConfigFile, "### Skill Settings ###\n\n"); fprintf(NewConfigFile, "### Skill Settings ###\n\n");

View file

@ -43,6 +43,9 @@ bool CONFIG_IsLerkAllowed();
bool CONFIG_IsFadeAllowed(); bool CONFIG_IsFadeAllowed();
bool CONFIG_IsOnosAllowed(); bool CONFIG_IsOnosAllowed();
bool CONFIG_IsRelocationAllowed();
float CONFIG_GetRelocationChance();
// Returns the max time a bot is allowed to be stuck before suiciding (0 means forever) // Returns the max time a bot is allowed to be stuck before suiciding (0 means forever)
float CONFIG_GetMaxStuckTime(); float CONFIG_GetMaxStuckTime();

View file

@ -136,7 +136,7 @@ typedef enum
STRUCTURE_ALIEN_OFFENCECHAMBER = 1u << 19, STRUCTURE_ALIEN_OFFENCECHAMBER = 1u << 19,
SEARCH_ALL_MARINE_STRUCTURES = 0xFFF, SEARCH_ALL_MARINE_STRUCTURES = 0xFFF,
SEARCH_ALL_ALIEN_STRUCTURES = 0xFC000, SEARCH_ALL_ALIEN_STRUCTURES = (STRUCTURE_ALIEN_HIVE | STRUCTURE_ALIEN_RESTOWER | STRUCTURE_ALIEN_DEFENCECHAMBER | STRUCTURE_ALIEN_SENSORYCHAMBER | STRUCTURE_ALIEN_MOVEMENTCHAMBER | STRUCTURE_ALIEN_OFFENCECHAMBER),
SEARCH_ANY_RES_TOWER = (STRUCTURE_MARINE_RESTOWER | STRUCTURE_ALIEN_RESTOWER), SEARCH_ANY_RES_TOWER = (STRUCTURE_MARINE_RESTOWER | STRUCTURE_ALIEN_RESTOWER),
SEARCH_ALL_STRUCTURES = ((unsigned int)-1 & ~(STRUCTURE_MARINE_DEPLOYEDMINE)) SEARCH_ALL_STRUCTURES = ((unsigned int)-1 & ~(STRUCTURE_MARINE_DEPLOYEDMINE))
@ -274,7 +274,7 @@ typedef struct _HIVE_DEFINITION_T
AvHTeamNumber OwningTeam = TEAM_IND; // Which team owns this hive currently (TEAM_IND if empty) AvHTeamNumber OwningTeam = TEAM_IND; // Which team owns this hive currently (TEAM_IND if empty)
unsigned int TeamAReachabilityFlags = AI_REACHABILITY_NONE; // Who on team A can reach this node? unsigned int TeamAReachabilityFlags = AI_REACHABILITY_NONE; // Who on team A can reach this node?
unsigned int TeamBReachabilityFlags = AI_REACHABILITY_NONE; // Who on team B can reach this node? unsigned int TeamBReachabilityFlags = AI_REACHABILITY_NONE; // Who on team B can reach this node?
char HiveName[64]; char HiveName[64] = {'\0'};
} AvHAIHiveDefinition; } AvHAIHiveDefinition;
// A nav profile combines a nav mesh reference (indexed into NavMeshes) and filters to determine how a bot should find paths // A nav profile combines a nav mesh reference (indexed into NavMeshes) and filters to determine how a bot should find paths
@ -341,6 +341,7 @@ typedef struct _AVH_AI_GUARD_INFO
typedef struct _AVH_AI_BUILDABLE_STRUCTURE typedef struct _AVH_AI_BUILDABLE_STRUCTURE
{ {
AvHBaseBuildable* EntityRef = nullptr; AvHBaseBuildable* EntityRef = nullptr;
int EntIndex = -1;
edict_t* edict = nullptr; // Reference to structure edict edict_t* edict = nullptr; // Reference to structure edict
Vector Location = g_vecZero; // origin of the structure edict Vector Location = g_vecZero; // origin of the structure edict
float healthPercent = 0.0f; // Current health of the building float healthPercent = 0.0f; // Current health of the building
@ -356,9 +357,11 @@ typedef struct _AVH_AI_BUILDABLE_STRUCTURE
Vector LastSuccessfulCommanderAngle = g_vecZero; // Tracks the last commander input angle ("click" location) used to successfully place or select building Vector LastSuccessfulCommanderAngle = g_vecZero; // Tracks the last commander input angle ("click" location) used to successfully place or select building
StructurePurpose Purpose = STRUCTURE_PURPOSE_NONE; StructurePurpose Purpose = STRUCTURE_PURPOSE_NONE;
bool bReachabilityMarkedDirty = false; // If true, reachability flags will be recalculated for this structure bool bReachabilityMarkedDirty = false; // If true, reachability flags will be recalculated for this structure
bool bPlacedByHuman = true; // This structure was placed by a human: AI commander will not recycle these unless it absolutely makes sense to
bool IsValid() { return !FNullEnt(edict) && !edict->free && !(edict->v.flags & EF_NODRAW) && edict->v.deadflag == DEAD_NO; } bool IsValid() { return !FNullEnt(edict) && !edict->free && !(edict->v.flags & EF_NODRAW) && edict->v.deadflag == DEAD_NO; }
bool IsCompleted() { return (StructureStatusFlags & STRUCTURE_STATUS_COMPLETED); } bool IsCompleted() { return (StructureStatusFlags & STRUCTURE_STATUS_COMPLETED); }
bool IsIdle() { return !(StructureStatusFlags & STRUCTURE_STATUS_RESEARCHING); }
} AvHAIBuildableStructure; } AvHAIBuildableStructure;
@ -372,6 +375,8 @@ typedef struct _DROPPED_MARINE_ITEM
unsigned int TeamBReachabilityFlags = AI_REACHABILITY_NONE; unsigned int TeamBReachabilityFlags = AI_REACHABILITY_NONE;
bool bReachabilityMarkedDirty = false; // Reachability needs to be recalculated bool bReachabilityMarkedDirty = false; // Reachability needs to be recalculated
int LastSeen = 0; // Which refresh cycle was this last seen on? Used to determine if the item has been removed from play int LastSeen = 0; // Which refresh cycle was this last seen on? Used to determine if the item has been removed from play
bool IsValid() { return !FNullEnt(edict) && !edict->free && !(edict->v.flags & EF_NODRAW) && edict->v.deadflag == DEAD_NO; }
} AvHAIDroppedItem; } AvHAIDroppedItem;
// How far a bot can be from a useable object when trying to interact with it. Used also for melee attacks. We make it slightly less than actual to avoid edge cases // How far a bot can be from a useable object when trying to interact with it. Used also for melee attacks. We make it slightly less than actual to avoid edge cases
@ -415,7 +420,7 @@ typedef enum
TASK_REINFORCE_STRUCTURE, TASK_REINFORCE_STRUCTURE,
TASK_SECURE_HIVE, TASK_SECURE_HIVE,
TASK_PLACE_MINE, TASK_PLACE_MINE,
TASK_ATTACK_BASE TASK_ASSAULT_MARINE_BASE
} }
BotTaskType; BotTaskType;
@ -469,6 +474,31 @@ enum NavDoorType
DOORTYPE_TRAIN // Door activated by touching a trigger_once or trigger_multiple DOORTYPE_TRAIN // Door activated by touching a trigger_once or trigger_multiple
}; };
// The type of base a marine outpost could be. Used to help the AI establish and expand outposts across the map
enum MarineBaseType
{
MARINE_BASE_MAINBASE, // The main marine base, where the CC, infantry portals and stuff like arms labs go
MARINE_BASE_OUTPOST, // A permanent outpost designed to control an area of the map, but not the main marine base
MARINE_BASE_SIEGE, // A siege base designed to take down an enemy base
MARINE_BASE_GUARDPOST // A cut-down version of an outpost with just sentry turrets and an observatory
};
typedef struct _AI_MARINE_BASE
{
AvHTeamNumber BaseTeam = TEAM_IND;
MarineBaseType BaseType = MARINE_BASE_OUTPOST; // The purpose of the base. Determines what structures the commander will place
Vector BaseLocation = ZERO_VECTOR; // Where the base should be located. The base will be grown around this location
Vector SiegeTarget = ZERO_VECTOR; // For siege bases, this is where the siege base wants to blast stuff
vector<int> PlacedStructures; // Which structures are part of this base.
int NumBuilders = 0; // How many potential builders are there, able to construct stuff?
int NumEnemies = 0; // How many enemies are in and around the base?
bool bRecycleBase = false; // Should the commander pack up and remove this base?
bool bIsActive = true; // Should the commander actively build and maintain this base?
bool bBaseInitialised = false; // Has the commander started building this base? Will be true once a structure has been placed
bool bCanBeBuiltOut = false; // Can this base be built out currently?
bool bIsBaseEstablished = false; // Have enough key structures been placed to consider this "established", even if it's not finished yet?
} AvHAIMarineBase;
// Bot path node. A path will be several of these strung together to lead the bot to its destination // Bot path node. A path will be several of these strung together to lead the bot to its destination
typedef struct _BOT_PATH_NODE typedef struct _BOT_PATH_NODE
{ {
@ -607,6 +637,7 @@ typedef struct _NAV_STATUS
Vector UnstuckMoveLocation = g_vecZero; // If the bot is unable to find a path, blindly move here to try and fix the problem Vector UnstuckMoveLocation = g_vecZero; // If the bot is unable to find a path, blindly move here to try and fix the problem
float LandedTime = 0.0f; // When the bot last landed after a fall/jump. float LandedTime = 0.0f; // When the bot last landed after a fall/jump.
float AirStartedTime = 0.0f; // When the bot left the ground if in the air
float LeapAttemptedTime = 0.0f; // When the bot last attempted to leap/blink. Avoid spam that sends it flying around too fast float LeapAttemptedTime = 0.0f; // When the bot last attempted to leap/blink. Avoid spam that sends it flying around too fast
bool bIsJumping = false; // Is the bot in the air from a jump? Will duck so it can duck-jump bool bIsJumping = false; // Is the bot in the air from a jump? Will duck so it can duck-jump
bool IsOnGround = true; // Is the bot currently on the ground, or on a ladder? bool IsOnGround = true; // Is the bot currently on the ground, or on a ladder?
@ -621,7 +652,7 @@ typedef struct _NAV_STATUS
float NextForceRecalc = 0.0f; // If set, then the bot will force-recalc its current path float NextForceRecalc = 0.0f; // If set, then the bot will force-recalc its current path
bool bZig; // Is the bot zigging, or zagging? bool bZig; // Is the bot zigging (moving RIGHT), or zagging (moving LEFT)?
float NextZigTime; // Controls how frequently they zig or zag float NextZigTime; // Controls how frequently they zig or zag
nav_profile NavProfile; nav_profile NavProfile;
@ -634,61 +665,16 @@ typedef struct _NAV_STATUS
AvHAIPlayerMoveTask MovementTask; AvHAIPlayerMoveTask MovementTask;
} nav_status; } nav_status;
// Type of goal the commander wants to achieve
typedef enum _COMMANDERACTIONTYPE
{
ACTION_NONE = 0,
ACTION_UPGRADE,
ACTION_RESEARCH,
ACTION_RECYCLE,
ACTION_GIVEORDER,
ACTION_DEPLOY // Deploy a structure or item into the map
} CommanderActionType;
// Some commander actions are multi-step (e.g. click to select building, release to complete selection, input recycle command etc). Tracks where the commander is in the process
typedef enum _COMMANDERACTIONSTEP
{
ACTION_STEP_NONE = 0,
ACTION_STEP_BEGIN_SELECT, // Click mouse button down to start select
ACTION_STEP_END_SELECT, // Release mouse button to complete select
} CommanderActionStep;
// Used by the AI commander instead of bot_task. Has data specifically to handle commander-specific stuff
typedef struct _COMMANDER_ACTION
{
bool bIsActive = false;
CommanderActionType ActionType = ACTION_NONE; // What action to perform (e.g. build, recycle, drop item etc)
CommanderActionStep ActionStep = ACTION_STEP_NONE; // Used for multi-stage processes such as selecting a building, issuing recycle command etc.
AvHAIDeployableStructureType StructureToBuild = STRUCTURE_NONE; // What structure to build if build action
AvHAIDeployableItemType ItemToPlace = DEPLOYABLE_ITEM_NONE;
int NumInstances = 0;
int NumDesiredInstances = 0;
StructurePurpose ActionPurpose = STRUCTURE_PURPOSE_NONE;
Vector BuildLocation = g_vecZero; // Where to build the structure
Vector DesiredCommanderLocation = g_vecZero; // To perform this action, where does the commander's view need to be? For building, usually directly above location, but could be off to side if obstructed by geometry
Vector LastAttemptedCommanderLocation = g_vecZero; // The position of the commander's view at the last action attempt
Vector LastAttemptedCommanderAngle = g_vecZero; // The click angle of the last action attempt
int AssignedPlayer = 0; // Which player index is assigned to perform the action (e.g. build structure)? Will send orders to that player (move here, build this structure etc.)
edict_t* StructureOrItem = nullptr; // Reference the structure edict. If a structure has been successfully placed but not yet fully built, it will be referenced here
edict_t* ActionTarget = nullptr; // Mostly used for dropping health packs and ammo for players where the drop location might be moving around
bool bHasAttemptedAction = false; // Has the commander tried placing a structure or item at the build location? If so, and it didn't appear, will try to adjust view around until it works
float StructureBuildAttemptTime = 0.0f; // When the commander tried placing a structure. Commander will wait a short while to confirm if the building appeared or if it should try again
int NumActionAttempts = 0; // Commander will give up after a certain number of attempts to place structure/item
AvHMessageID ResearchId = MESSAGE_NULL; // What research to perform if research action
bool bIsAwaitingBuildLink = false; // The AI has tried placing a structure or item and is waiting to confirm it worked or not
bool bIsActionUrgent = false;
} commander_action;
typedef enum typedef enum
{ {
ORDERPURPOSE_NONE, ORDERPURPOSE_NONE,
ORDERPURPOSE_SECURE_HIVE, ORDERPURPOSE_SECURE_HIVE,
ORDERPURPOSE_SIEGE_HIVE, ORDERPURPOSE_SIEGE_HIVE,
ORDERPURPOSE_SECURE_RESNODE ORDERPURPOSE_SECURE_RESNODE,
ORDERPURPOSE_BUILD_MAINBASE,
ORDERPURPOSE_BUILD_SIEGE,
ORDERPURPOSE_BUILD_OUTPOST,
ORDERPURPOSE_BUILD_GUARDPOST
} AvHAIOrderPurpose; } AvHAIOrderPurpose;
typedef struct _AI_COMMANDER_ORDER typedef struct _AI_COMMANDER_ORDER
@ -782,11 +768,14 @@ typedef struct AVH_AI_PLAYER
float LastRequestTime = 0.0f; // When bot last used a voice line to request something. Prevents spam float LastRequestTime = 0.0f; // When bot last used a voice line to request something. Prevents spam
float LastTeleportTime = 0.0f; // Last time the bot teleported somewhere
Vector DesiredLookDirection = g_vecZero; // What view angle is the bot currently turning towards Vector DesiredLookDirection = g_vecZero; // What view angle is the bot currently turning towards
Vector InterpolatedLookDirection = g_vecZero; // Used to smoothly interpolate the bot's view rather than snap instantly like an aimbot Vector InterpolatedLookDirection = g_vecZero; // Used to smoothly interpolate the bot's view rather than snap instantly like an aimbot
edict_t* LookTarget = nullptr; // Used to work out what view angle is needed to look at the desired entity edict_t* LookTarget = nullptr; // Used to work out what view angle is needed to look at the desired entity
Vector LookTargetLocation = g_vecZero; // This is the bot's current desired look target. Could be an enemy (see LookTarget), or point of interest Vector LookTargetLocation = g_vecZero; // This is the bot's current desired look target. Could be an enemy (see LookTarget), or point of interest
Vector MoveLookLocation = g_vecZero; // If the bot has to look somewhere specific for movement (e.g. up for a ladder or wall-climb), this will override LookTargetLocation so the bot doesn't get distracted and mess the move up Vector MoveLookLocation = g_vecZero; // If the bot has to look somewhere specific for movement (e.g. up for a ladder or wall-climb), this will override LookTargetLocation so the bot doesn't get distracted and mess the move up
bool bSnapView = false; // Use for rapid, precise snapping of the bot's view to the target. Useful if the bot requires more precise view angles for movement or other reasons
float LastTargetTrackUpdate = 0.0f; // Add a delay to how frequently a bot can track a target's movements float LastTargetTrackUpdate = 0.0f; // Add a delay to how frequently a bot can track a target's movements
float ViewInterpolationSpeed = 0.0f; // How fast should the bot turn its view? Depends on distance to turn float ViewInterpolationSpeed = 0.0f; // How fast should the bot turn its view? Depends on distance to turn
float ViewInterpStartedTime = 0.0f; // Used for interpolation float ViewInterpStartedTime = 0.0f; // Used for interpolation
@ -812,6 +801,10 @@ typedef struct AVH_AI_PLAYER
int DebugValue = 0; // Used for debugging the bot int DebugValue = 0; // Used for debugging the bot
Vector RelocationSpot = ZERO_VECTOR; // If the bot is commanding and wants to relocate, then this is where they plan to go
vector<AvHAIMarineBase> Bases;
} AvHAIPlayer; } AvHAIPlayer;
typedef struct _AVH_AI_SQUAD typedef struct _AVH_AI_SQUAD
@ -820,6 +813,7 @@ typedef struct _AVH_AI_SQUAD
vector<AvHAIPlayer*> SquadMembers; // Which bots are assigned to this vector<AvHAIPlayer*> SquadMembers; // Which bots are assigned to this
Vector SquadGatherLocation = g_vecZero; // Where should the squad gather before attempting the objective? Vector SquadGatherLocation = g_vecZero; // Where should the squad gather before attempting the objective?
edict_t* SquadTarget = nullptr; // The target of the objective edict_t* SquadTarget = nullptr; // The target of the objective
Vector ObjectiveLocation = g_vecZero;
BotTaskType SquadObjective = TASK_NONE; // What to do with the objective BotTaskType SquadObjective = TASK_NONE; // What to do with the objective
bool bExecuteObjective = false; // Are we at the gather or execute phase? bool bExecuteObjective = false; // Are we at the gather or execute phase?

View file

@ -342,7 +342,17 @@ void AIDEBUG_DrawPath(edict_t* OutputPlayer, vector<bot_path_node>& path, float
UTIL_DrawLine(OutputPlayer, FromLoc, ToLoc, DrawTime, 255, 128, 128); UTIL_DrawLine(OutputPlayer, FromLoc, ToLoc, DrawTime, 255, 128, 128);
break; break;
default: default:
{
if (it->area == SAMPLE_POLYAREA_CROUCH)
{
UTIL_DrawLine(OutputPlayer, FromLoc, ToLoc, DrawTime, 255, 150, 150);
}
else
{
UTIL_DrawLine(OutputPlayer, FromLoc, ToLoc, DrawTime); UTIL_DrawLine(OutputPlayer, FromLoc, ToLoc, DrawTime);
}
}
break; break;
} }
} }
@ -350,9 +360,16 @@ void AIDEBUG_DrawPath(edict_t* OutputPlayer, vector<bot_path_node>& path, float
void UTIL_DrawLine(edict_t* pEntity, Vector start, Vector end) void UTIL_DrawLine(edict_t* pEntity, Vector start, Vector end)
{ {
if (FNullEnt(pEntity) || pEntity->free) { return; }
if (FNullEnt(pEntity) || pEntity->free)
{
MESSAGE_BEGIN(MSG_BROADCAST, SVC_TEMPENTITY);
}
else
{
MESSAGE_BEGIN(MSG_ONE, SVC_TEMPENTITY, NULL, pEntity); MESSAGE_BEGIN(MSG_ONE, SVC_TEMPENTITY, NULL, pEntity);
}
WRITE_BYTE(TE_BEAMPOINTS); WRITE_BYTE(TE_BEAMPOINTS);
WRITE_COORD(start.x); WRITE_COORD(start.x);
WRITE_COORD(start.y); WRITE_COORD(start.y);
@ -378,12 +395,18 @@ void UTIL_DrawLine(edict_t* pEntity, Vector start, Vector end)
void UTIL_DrawLine(edict_t* pEntity, Vector start, Vector end, float drawTimeSeconds) void UTIL_DrawLine(edict_t* pEntity, Vector start, Vector end, float drawTimeSeconds)
{ {
if (FNullEnt(pEntity) || pEntity->free) { return; }
int timeTenthSeconds = (int)floorf(drawTimeSeconds * 10.0f); int timeTenthSeconds = (int)floorf(drawTimeSeconds * 10.0f);
timeTenthSeconds = fmaxf(timeTenthSeconds, 1); timeTenthSeconds = fmaxf(timeTenthSeconds, 1);
if (FNullEnt(pEntity) || pEntity->free)
{
MESSAGE_BEGIN(MSG_BROADCAST, SVC_TEMPENTITY);
}
else
{
MESSAGE_BEGIN(MSG_ONE, SVC_TEMPENTITY, NULL, pEntity); MESSAGE_BEGIN(MSG_ONE, SVC_TEMPENTITY, NULL, pEntity);
}
WRITE_BYTE(TE_BEAMPOINTS); WRITE_BYTE(TE_BEAMPOINTS);
WRITE_COORD(start.x); WRITE_COORD(start.x);
WRITE_COORD(start.y); WRITE_COORD(start.y);
@ -409,12 +432,18 @@ void UTIL_DrawLine(edict_t* pEntity, Vector start, Vector end, float drawTimeSec
void UTIL_DrawLine(edict_t* pEntity, Vector start, Vector end, float drawTimeSeconds, int r, int g, int b) void UTIL_DrawLine(edict_t* pEntity, Vector start, Vector end, float drawTimeSeconds, int r, int g, int b)
{ {
if (FNullEnt(pEntity) || pEntity->free) { return; }
int timeTenthSeconds = (int)ceilf(drawTimeSeconds * 10.0f); int timeTenthSeconds = (int)ceilf(drawTimeSeconds * 10.0f);
timeTenthSeconds = fmaxf(timeTenthSeconds, 1); timeTenthSeconds = fmaxf(timeTenthSeconds, 1);
if (FNullEnt(pEntity) || pEntity->free)
{
MESSAGE_BEGIN(MSG_BROADCAST, SVC_TEMPENTITY);
}
else
{
MESSAGE_BEGIN(MSG_ONE, SVC_TEMPENTITY, NULL, pEntity); MESSAGE_BEGIN(MSG_ONE, SVC_TEMPENTITY, NULL, pEntity);
}
WRITE_BYTE(TE_BEAMPOINTS); WRITE_BYTE(TE_BEAMPOINTS);
WRITE_COORD(start.x); WRITE_COORD(start.x);
WRITE_COORD(start.y); WRITE_COORD(start.y);
@ -440,9 +469,15 @@ void UTIL_DrawLine(edict_t* pEntity, Vector start, Vector end, float drawTimeSec
void UTIL_DrawLine(edict_t* pEntity, Vector start, Vector end, int r, int g, int b) void UTIL_DrawLine(edict_t* pEntity, Vector start, Vector end, int r, int g, int b)
{ {
if (FNullEnt(pEntity) || pEntity->free) { return; } if (FNullEnt(pEntity) || pEntity->free)
{
MESSAGE_BEGIN(MSG_BROADCAST, SVC_TEMPENTITY);
}
else
{
MESSAGE_BEGIN(MSG_ONE, SVC_TEMPENTITY, NULL, pEntity); MESSAGE_BEGIN(MSG_ONE, SVC_TEMPENTITY, NULL, pEntity);
}
WRITE_BYTE(TE_BEAMPOINTS); WRITE_BYTE(TE_BEAMPOINTS);
WRITE_COORD(start.x); WRITE_COORD(start.x);
WRITE_COORD(start.y); WRITE_COORD(start.y);
@ -638,11 +673,64 @@ void UTIL_LocalizeText(const char* InputText, string& OutputText)
} }
char* UTIL_StructTypeToChar(const AvHAIDeployableStructureType StructureType)
{
switch (StructureType)
{
case STRUCTURE_MARINE_RESTOWER:
return "RT";
case STRUCTURE_MARINE_INFANTRYPORTAL:
return "IP";
case STRUCTURE_MARINE_TURRETFACTORY:
return "TF";
case STRUCTURE_MARINE_ADVTURRETFACTORY:
return "Adv TF";
case STRUCTURE_MARINE_ARMOURY:
return "Armoury";
case STRUCTURE_MARINE_ADVARMOURY:
return "Adv Armoury";
case STRUCTURE_MARINE_ARMSLAB:
return "Armslab";
case STRUCTURE_MARINE_PROTOTYPELAB:
return "ProtoLab";
case STRUCTURE_MARINE_OBSERVATORY:
return "Obs";
case STRUCTURE_MARINE_PHASEGATE:
return "PG";
case STRUCTURE_MARINE_TURRET:
return "Sentry";
case STRUCTURE_MARINE_SIEGETURRET:
return "Siege T";
case STRUCTURE_MARINE_COMMCHAIR:
return "CC";
case STRUCTURE_MARINE_DEPLOYEDMINE:
return "Mine";
case STRUCTURE_ALIEN_HIVE:
return "Hive";
case STRUCTURE_ALIEN_RESTOWER:
return "RT";
case STRUCTURE_ALIEN_DEFENCECHAMBER:
return "DC";
case STRUCTURE_ALIEN_SENSORYCHAMBER:
return "SC";
case STRUCTURE_ALIEN_MOVEMENTCHAMBER:
return "MC";
case STRUCTURE_ALIEN_OFFENCECHAMBER:
return "OC";
default:
return "None";
}
return "None";
}
char* UTIL_TaskTypeToChar(const BotTaskType TaskType) char* UTIL_TaskTypeToChar(const BotTaskType TaskType)
{ {
switch (TaskType) switch (TaskType)
{ {
case TASK_ATTACK: case TASK_ATTACK:
return "Attack"; return "Attack";
case TASK_BUILD: case TASK_BUILD:
return "Build"; return "Build";
@ -680,8 +768,8 @@ char* UTIL_TaskTypeToChar(const BotTaskType TaskType)
return "Touch Trigger"; return "Touch Trigger";
case TASK_WELD: case TASK_WELD:
return "Weld Target"; return "Weld Target";
case TASK_ATTACK_BASE: case TASK_ASSAULT_MARINE_BASE:
return "Attack Enemy Base"; return "Assault Marine Base";
default: default:
return "None"; return "None";
} }

View file

@ -60,6 +60,7 @@ void UTIL_ClearLocalizations();
void UTIL_LocalizeText(const char* InputText, string& OutputText); void UTIL_LocalizeText(const char* InputText, string& OutputText);
char* UTIL_TaskTypeToChar(const BotTaskType TaskType); char* UTIL_TaskTypeToChar(const BotTaskType TaskType);
char* UTIL_StructTypeToChar(const AvHAIDeployableStructureType StructureType);
char* UTIL_BotRoleToChar(const AvHAIBotRole Role); char* UTIL_BotRoleToChar(const AvHAIBotRole Role);

File diff suppressed because it is too large Load diff

View file

@ -220,7 +220,7 @@ void NewMove(AvHAIPlayer* pBot);
// Returns true if the bot has completed the current movement along their path // Returns true if the bot has completed the current movement along their path
bool HasBotReachedPathPoint(const AvHAIPlayer* pBot); bool HasBotReachedPathPoint(const AvHAIPlayer* pBot);
bool HasBotCompletedLadderMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag); bool HasBotCompletedLadderMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag);
bool HasBotCompletedWalkMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag); bool HasBotCompletedWalkMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, SamplePolyAreas MoveArea, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag, SamplePolyAreas NextMoveArea);
bool HasBotCompletedFallMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag); bool HasBotCompletedFallMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag);
bool HasBotCompletedClimbMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, float RequiredClimbHeight, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag); bool HasBotCompletedClimbMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, float RequiredClimbHeight, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag);
bool HasBotCompletedJumpMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag); bool HasBotCompletedJumpMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag);
@ -495,6 +495,8 @@ nav_door* UTIL_GetNavDoorByEdict(const edict_t* DoorEdict);
nav_door* UTIL_GetClosestLiftToPoints(const Vector StartPoint, const Vector EndPoint); nav_door* UTIL_GetClosestLiftToPoints(const Vector StartPoint, const Vector EndPoint);
AvHAIOffMeshConnection* UTIL_GetOffMeshConnectionForLift(nav_door* LiftRef); AvHAIOffMeshConnection* UTIL_GetOffMeshConnectionForLift(nav_door* LiftRef);
bool UTIL_IsTileCacheUpToDate();
Vector UTIL_AdjustPointAwayFromNavWall(const Vector Location, const float MaxDistanceFromWall); Vector UTIL_AdjustPointAwayFromNavWall(const Vector Location, const float MaxDistanceFromWall);
void UTIL_PopulateBaseNavProfiles(); void UTIL_PopulateBaseNavProfiles();

File diff suppressed because it is too large Load diff

View file

@ -26,9 +26,9 @@ static const float f_ffwidth = f_ffheight * BOT_ASPECT_RATIO;
void BotJump(AvHAIPlayer* pBot); void BotJump(AvHAIPlayer* pBot);
void BotSuicide(AvHAIPlayer* pBot); void BotSuicide(AvHAIPlayer* pBot);
void BotLookAt(AvHAIPlayer* pBot, Vector NewLocation); void BotLookAt(AvHAIPlayer* pBot, Vector NewLocation, bool bSnap = false);
void BotLookAt(AvHAIPlayer* pBot, edict_t* target); void BotLookAt(AvHAIPlayer* pBot, edict_t* target, bool bSnap = false);
void BotMoveLookAt(AvHAIPlayer* pBot, const Vector target); void BotMoveLookAt(AvHAIPlayer* pBot, const Vector target, bool bSnap = false);
void BotDirectLookAt(AvHAIPlayer* pBot, Vector target); void BotDirectLookAt(AvHAIPlayer* pBot, Vector target);
bool BotUseObject(AvHAIPlayer* pBot, edict_t* Target, bool bContinuous); bool BotUseObject(AvHAIPlayer* pBot, edict_t* Target, bool bContinuous);
@ -159,6 +159,8 @@ void AIPlayerHearEnemy(AvHAIPlayer* pBot, edict_t* HeardEnemy, float SoundVolume
int BotGetNextEnemyTarget(AvHAIPlayer* pBot); int BotGetNextEnemyTarget(AvHAIPlayer* pBot);
void OnBotTeleport(AvHAIPlayer* pBot);
AvHMessageID AlienGetDesiredUpgrade(AvHAIPlayer* pBot, HiveTechStatus DesiredTech); AvHMessageID AlienGetDesiredUpgrade(AvHAIPlayer* pBot, HiveTechStatus DesiredTech);
AvHAICombatStrategy GetBotCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_status* CurrentEnemy); AvHAICombatStrategy GetBotCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_status* CurrentEnemy);
@ -188,4 +190,6 @@ void DEBUG_PrintBotDebugInfo(edict_t* OutputPlayer, AvHAIPlayer* pBot);
void DEBUG_PrintTaskInfo(edict_t* OutputPlayer, AvHAIPlayer* pBot); void DEBUG_PrintTaskInfo(edict_t* OutputPlayer, AvHAIPlayer* pBot);
void DEBUG_PrintCombatInfo(edict_t* OutputPlayer, AvHAIPlayer* pBot); void DEBUG_PrintCombatInfo(edict_t* OutputPlayer, AvHAIPlayer* pBot);
Vector GetZigZagDirection(AvHAIPlayer* pBot, edict_t* Enemy, bot_path_node* CurrentPathNode);
#endif #endif

View file

@ -24,6 +24,7 @@ extern cvar_t avh_botusemapdefaults;
extern cvar_t avh_botcommandermode; extern cvar_t avh_botcommandermode;
extern cvar_t avh_botdebugmode; extern cvar_t avh_botdebugmode;
extern cvar_t avh_botskill; extern cvar_t avh_botskill;
extern cvar_t avh_limitteams;
float LastAIPlayerCountUpdate = 0.0f; float LastAIPlayerCountUpdate = 0.0f;
@ -225,7 +226,10 @@ void AIMGR_UpdateFillTeams()
} }
} }
if (TeamSizeA < NumDesiredTeamA && TeamSizeA <= TeamSizeB) bool bCanAddToTeamA = (GetGameRules()->GetCheatsEnabled() || TeamSizeA < TeamSizeB || TeamSizeA - TeamSizeB < avh_limitteams.value);
bool bCanAddToTeamB = (GetGameRules()->GetCheatsEnabled() || TeamSizeB < TeamSizeA || TeamSizeB - TeamSizeA < avh_limitteams.value);
if (TeamSizeA < NumDesiredTeamA && bCanAddToTeamA)
{ {
// Don't add a bot if we have any stuck in the ready room, wait for teams to resolve themselves // Don't add a bot if we have any stuck in the ready room, wait for teams to resolve themselves
if (AIMGR_GetNumAIPlayersOnTeam(TEAM_IND) > 0) { return; } if (AIMGR_GetNumAIPlayersOnTeam(TEAM_IND) > 0) { return; }
@ -242,7 +246,7 @@ void AIMGR_UpdateFillTeams()
} }
} }
if (TeamSizeB < NumDesiredTeamB && TeamSizeB <= TeamSizeA) if (TeamSizeB < NumDesiredTeamB && bCanAddToTeamB)
{ {
// Don't add a bot if we have any stuck in the ready room, wait for teams to resolve themselves // Don't add a bot if we have any stuck in the ready room, wait for teams to resolve themselves
if (AIMGR_GetNumAIPlayersOnTeam(TEAM_IND) > 0) { return; } if (AIMGR_GetNumAIPlayersOnTeam(TEAM_IND) > 0) { return; }
@ -640,6 +644,10 @@ void AIMGR_UpdateAIPlayers()
BotUpdateViewRotation(bot, FrameDelta); BotUpdateViewRotation(bot, FrameDelta);
// Need to reset this since impulses generally should only be called once at a time, so this
// prevents it from being called on consecutive frames if this bot isn't running its think routine every frame
bot->Impulse = 0;
if (bHasRoundStarted) if (bHasRoundStarted)
{ {
if (IsPlayerCommander(bot->Edict)) if (IsPlayerCommander(bot->Edict))
@ -835,7 +843,7 @@ int AIMGR_GetNumAIPlayersWithRoleOnTeam(AvHTeamNumber Team, AvHAIBotRole Role, A
{ {
if (&(*it) == IgnoreAIPlayer) { continue; } if (&(*it) == IgnoreAIPlayer) { continue; }
if (it->Player->GetTeam() == Team) if (it->Player->GetTeam() == Team && IsPlayerActiveInGame((*it).Edict))
{ {
if (it->BotRole == Role) if (it->BotRole == Role)
{ {
@ -921,15 +929,20 @@ void AIMGR_ResetRound()
bool bTileCacheFullyUpdated = UTIL_UpdateTileCache(); bool bTileCacheFullyUpdated = UTIL_UpdateTileCache();
while (!bTileCacheFullyUpdated) int NumAttempts = 0;
while (!bTileCacheFullyUpdated && NumAttempts < 30)
{ {
bTileCacheFullyUpdated = UTIL_UpdateTileCache(); bTileCacheFullyUpdated = UTIL_UpdateTileCache();
NumAttempts++;
} }
bHasRoundStarted = false; bHasRoundStarted = false;
bMapDataInitialised = true; bMapDataInitialised = true;
CountdownStartedTime = 0.0f; CountdownStartedTime = 0.0f;
AITAC_DetermineRelocationEnabled();
} }
void AIMGR_ReloadNavigationData() void AIMGR_ReloadNavigationData()
@ -1307,6 +1320,64 @@ void AIMGR_SetDebugAIPlayer(edict_t* SpectatingPlayer, edict_t* AIPlayer)
DebugBots[PlayerIndex] = nullptr; DebugBots[PlayerIndex] = nullptr;
} }
void AIDEBUG_DisplayTeamGoals()
{
AvHTeamNumber TeamANumber = AIMGR_GetTeamANumber();
AvHTeamNumber TeamBNumber = AIMGR_GetTeamBNumber();
vector<AvHAIPlayer*> Team1Players = AIMGR_GetAIPlayersOnTeam(TeamANumber);
vector<AvHAIPlayer*> Team2Players = AIMGR_GetAIPlayersOnTeam(TeamBNumber);
char buf[511];
char interbuf[164];
sprintf(buf, "Team A (%s)\n\n", (AIMGR_GetTeamType(TeamANumber) == AVH_CLASS_TYPE_MARINE) ? "Marines" : "Aliens");
for (auto it = Team1Players.begin(); it != Team1Players.end(); it++)
{
AvHAIPlayer* ThisPlayer = (*it);
if (!ThisPlayer->CurrentTask || !IsPlayerActiveInGame(ThisPlayer->Edict)) { continue; }
if (!FNullEnt(ThisPlayer->CurrentTask->TaskTarget))
{
sprintf(interbuf, "%s: %s (%s)\n", STRING(ThisPlayer->Player->pev->netname), UTIL_TaskTypeToChar(ThisPlayer->CurrentTask->TaskType), UTIL_StructTypeToChar(UTIL_IUSER3ToStructureType(ThisPlayer->CurrentTask->TaskTarget->v.iuser3)));
}
else
{
sprintf(interbuf, "%s: %s\n", STRING(ThisPlayer->Player->pev->netname), UTIL_TaskTypeToChar(ThisPlayer->CurrentTask->TaskType));
}
strcat(buf, interbuf);
}
UTIL_DrawHUDText(INDEXENT(1), 0, 0.1, 0.1f, 255, 255, 255, buf);
sprintf(buf, "Team B (%s)\n\n", (AIMGR_GetTeamType(TeamBNumber) == AVH_CLASS_TYPE_MARINE) ? "Marines" : "Aliens");
for (auto it = Team2Players.begin(); it != Team2Players.end(); it++)
{
AvHAIPlayer* ThisPlayer = (*it);
if (!ThisPlayer->CurrentTask || !IsPlayerActiveInGame(ThisPlayer->Edict)) { continue; }
if (!FNullEnt(ThisPlayer->CurrentTask->TaskTarget))
{
sprintf(interbuf, "%s: %s (%s)\n", STRING(ThisPlayer->Player->pev->netname), UTIL_TaskTypeToChar(ThisPlayer->CurrentTask->TaskType), UTIL_StructTypeToChar(UTIL_IUSER3ToStructureType(ThisPlayer->CurrentTask->TaskTarget->v.iuser3)));
}
else
{
sprintf(interbuf, "%s: %s\n", STRING(ThisPlayer->Player->pev->netname), UTIL_TaskTypeToChar(ThisPlayer->CurrentTask->TaskType));
}
strcat(buf, interbuf);
}
UTIL_DrawHUDText(INDEXENT(1), 1, 0.6, 0.1f, 255, 255, 255, buf);
}
#endif #endif
void AIMGR_ReceiveCommanderRequest(AvHTeamNumber Team, edict_t* Requestor, const char* Request) void AIMGR_ReceiveCommanderRequest(AvHTeamNumber Team, edict_t* Requestor, const char* Request)
@ -1369,9 +1440,12 @@ void AIMGR_OnBotEnabled()
bool bTileCacheFullyUpdated = UTIL_UpdateTileCache(); bool bTileCacheFullyUpdated = UTIL_UpdateTileCache();
while (!bTileCacheFullyUpdated) int NumAttempts = 0;
while (!bTileCacheFullyUpdated && NumAttempts < 30)
{ {
bTileCacheFullyUpdated = UTIL_UpdateTileCache(); bTileCacheFullyUpdated = UTIL_UpdateTileCache();
NumAttempts++;
} }
} }
// Figure out the current game status // Figure out the current game status
@ -1591,3 +1665,8 @@ float AIMGR_GetFrameDelta()
{ {
return CurrentFrameDelta; return CurrentFrameDelta;
} }
float AIMGR_GetMatchLength()
{
return (gpGlobals->time - GetGameRules()->GetTimeGameStarted());
}

View file

@ -97,6 +97,8 @@ AvHClassType AIMGR_GetTeamType(const AvHTeamNumber Team);
AvHTeamNumber AIMGR_GetTeamANumber(); AvHTeamNumber AIMGR_GetTeamANumber();
AvHTeamNumber AIMGR_GetTeamBNumber(); AvHTeamNumber AIMGR_GetTeamBNumber();
float AIMGR_GetMatchLength();
AvHTeam* AIMGR_GetTeamRef(const AvHTeamNumber Team); AvHTeam* AIMGR_GetTeamRef(const AvHTeamNumber Team);
// Returns all NS AI players. Does not include third-party bots // Returns all NS AI players. Does not include third-party bots
@ -135,4 +137,6 @@ void AIMGR_ProcessPendingSounds();
void AIMGR_SetFrameDelta(float NewValue); void AIMGR_SetFrameDelta(float NewValue);
float AIMGR_GetFrameDelta(); float AIMGR_GetFrameDelta();
void AIDEBUG_DisplayTeamGoals();
#endif #endif

View file

@ -2,6 +2,7 @@
#include "AvHAIPlayerUtil.h" #include "AvHAIPlayerUtil.h"
#include "AvHAIPlayer.h" #include "AvHAIPlayer.h"
#include "AvHAIHelper.h" #include "AvHAIHelper.h"
#include "AvHAIWeaponHelper.h"
#include "AvHPlayerUpgrade.h" #include "AvHPlayerUpgrade.h"
#include "AvHAIMath.h" #include "AvHAIMath.h"
@ -73,6 +74,7 @@ bool IsPlayerClimbingWall(const edict_t* Player)
bool IsPlayerInReadyRoom(const edict_t* Player) bool IsPlayerInReadyRoom(const edict_t* Player)
{ {
if (FNullEnt(Player)) { return false; }
return Player->v.playerclass == PLAYMODE_READYROOM; return Player->v.playerclass == PLAYMODE_READYROOM;
} }
@ -152,7 +154,7 @@ bool IsPlayerOnLadder(const edict_t* Player)
Vector NearestPointOnLadder = UTIL_GetClosestPointOnEntityToLocation(Player->v.origin, NearestLadder); Vector NearestPointOnLadder = UTIL_GetClosestPointOnEntityToLocation(Player->v.origin, NearestLadder);
Vector NearestPointOnPlayer = UTIL_GetClosestPointOnEntityToLocation(NearestPointOnLadder, Player); Vector NearestPointOnPlayer = UTIL_GetClosestPointOnEntityToLocation(NearestPointOnLadder, Player);
return (vDist2DSq(NearestPointOnLadder, NearestPointOnPlayer) <= sqrf(4.0f)); return (vDist3DSq(NearestPointOnLadder, NearestPointOnPlayer) <= sqrf(2.0f));
} }
return (Player->v.movetype == MOVETYPE_FLY); return (Player->v.movetype == MOVETYPE_FLY);
@ -168,6 +170,11 @@ bool IsPlayerMotionTracked(const edict_t* Player)
return (Player->v.iuser4 & MASK_VIS_DETECTED); return (Player->v.iuser4 & MASK_VIS_DETECTED);
} }
bool IsPlayerSOF(const edict_t* Player)
{
return (Player->v.iuser4 & MASK_SENSORY_NEARBY);
}
float GetPlayerEnergy(const edict_t* Player) float GetPlayerEnergy(const edict_t* Player)
{ {
return (Player->v.fuser3 * 0.001f); return (Player->v.fuser3 * 0.001f);
@ -635,7 +642,10 @@ bool PlayerHasEquipment(edict_t* Player)
bool PlayerHasSpecialWeapon(const AvHPlayer* Player) bool PlayerHasSpecialWeapon(const AvHPlayer* Player)
{ {
if (!IsPlayerMarine(Player)) { return false; } if (!IsPlayerMarine(Player)) { return false; }
return !PlayerHasWeapon(Player, WEAPON_MARINE_MG);
AvHAIWeapon PrimaryWeaponType = UTIL_GetPlayerPrimaryWeapon(Player);
return PrimaryWeaponType != WEAPON_INVALID && PrimaryWeaponType != WEAPON_MARINE_MG;
} }
bool UTIL_PlayerHasLOSToEntity(const edict_t* Player, const edict_t* Target, const float MaxRange, const bool bUseHullSweep) bool UTIL_PlayerHasLOSToEntity(const edict_t* Player, const edict_t* Target, const float MaxRange, const bool bUseHullSweep)
@ -991,6 +1001,38 @@ Vector UTIL_GetNearestSurfaceNormal(Vector SearchLocation)
return ClosestNormal; return ClosestNormal;
} }
bool UTIL_QuickCollisionTrace(Vector StartTrace, Vector EndTrace)
{
trace_t TraceResult;
NS_TraceLine(StartTrace, EndTrace, 0, PM_WORLD_ONLY, -1, true, TraceResult);
return TraceResult.fraction >= 1.0f;
}
Vector UTIL_GetLadderNormal(Vector SearchLocation, edict_t* Ladder)
{
if (FNullEnt(Ladder)) { return ZERO_VECTOR; }
if (vPointOverlaps3D(SearchLocation, Ladder->v.absmin, Ladder->v.absmax))
{
return UTIL_GetNearestSurfaceNormal(SearchLocation);
}
else
{
Vector CentrePoint = Ladder->v.absmin + (Ladder->v.size * 0.5f);
CentrePoint.z = SearchLocation.z;
trace_t TraceResult;
NS_TraceLine(SearchLocation, CentrePoint, 1, PM_WORLD_ONLY, -1, true, TraceResult);
if (TraceResult.fraction < 1.0f)
{
return TraceResult.plane.normal;
}
}
return ZERO_VECTOR;
}
Vector UTIL_GetNearestLadderNormal(Vector SearchLocation) Vector UTIL_GetNearestLadderNormal(Vector SearchLocation)
{ {
TraceResult result; TraceResult result;

View file

@ -67,6 +67,8 @@ bool IsPlayerGestating(const edict_t* Player);
bool IsPlayerParasited(const edict_t* Player); bool IsPlayerParasited(const edict_t* Player);
// Is the player being marked through walls to enemies through being sighted by an ally or affected by motion tracking? // Is the player being marked through walls to enemies through being sighted by an ally or affected by motion tracking?
bool IsPlayerMotionTracked(const edict_t* Player); bool IsPlayerMotionTracked(const edict_t* Player);
// Is the player being marked through walls by a nearby sensory chamber or scent of fear?
bool IsPlayerSOF(const edict_t* Player);
// Is the player currently on a ladder? Always false for Skulks and Lerks as they can't climb ladders // Is the player currently on a ladder? Always false for Skulks and Lerks as they can't climb ladders
bool IsPlayerOnLadder(const edict_t* Player); bool IsPlayerOnLadder(const edict_t* Player);
// Is the player an onos under the effect of charge? // Is the player an onos under the effect of charge?
@ -172,6 +174,10 @@ Vector UTIL_GetNearestLadderTopPoint(edict_t* pEdict);
Vector UTIL_GetNearestLadderTopPoint(const Vector SearchLocation); Vector UTIL_GetNearestLadderTopPoint(const Vector SearchLocation);
Vector UTIL_GetNearestLadderBottomPoint(edict_t* pEdict); Vector UTIL_GetNearestLadderBottomPoint(edict_t* pEdict);
Vector UTIL_GetLadderNormal(Vector SearchLocation, edict_t* Ladder);
bool UTIL_QuickCollisionTrace(Vector StartTrace, Vector EndTrace);
Vector UTIL_GetNearestSurfaceNormal(Vector SearchLocation); Vector UTIL_GetNearestSurfaceNormal(Vector SearchLocation);
#endif #endif

View file

@ -18,6 +18,7 @@
#include "AvHAIConstants.h" #include "AvHAIConstants.h"
#include "AvHAIPlayerManager.h" #include "AvHAIPlayerManager.h"
#include "AvHAIConfig.h" #include "AvHAIConfig.h"
#include "AvHAICommander.h"
#include "AvHGamerules.h" #include "AvHGamerules.h"
#include "AvHServerUtil.h" #include "AvHServerUtil.h"
@ -54,6 +55,11 @@ unsigned int ItemRefreshFrame = 0;
Vector TeamAStartingLocation = ZERO_VECTOR; Vector TeamAStartingLocation = ZERO_VECTOR;
Vector TeamBStartingLocation = ZERO_VECTOR; Vector TeamBStartingLocation = ZERO_VECTOR;
Vector TeamARelocationPoint = ZERO_VECTOR;
Vector TeamBRelocationPoint = ZERO_VECTOR;
bool bEnableRelocationAtStart = false; // For this round, should the AI commander try relocating at the start of the match?
extern nav_mesh NavMeshes[MAX_NAV_MESHES]; // Array of nav meshes. Currently only 3 are used (building, onos, and regular) extern nav_mesh NavMeshes[MAX_NAV_MESHES]; // Array of nav meshes. Currently only 3 are used (building, onos, and regular)
extern nav_profile BaseNavProfiles[MAX_NAV_PROFILES]; // Array of nav profiles extern nav_profile BaseNavProfiles[MAX_NAV_PROFILES]; // Array of nav profiles
@ -68,6 +74,9 @@ float LastSeenLerkTeamBTime = 0.0f;
vector<AvHAISquad> ActiveSquads; vector<AvHAISquad> ActiveSquads;
vector<AvHAIMarineBase> ActiveTeamABases; // If Team A are marines, any active bases they have established around the map
vector<AvHAIMarineBase> ActiveTeamBBases; // If Team B are marines, any active bases they have established around the map
std::vector<AvHAIBuildableStructure> AITAC_FindAllDeployables(const Vector& Location, const DeployableSearchFilter* Filter) std::vector<AvHAIBuildableStructure> AITAC_FindAllDeployables(const Vector& Location, const DeployableSearchFilter* Filter)
{ {
std::vector<AvHAIBuildableStructure> Result; std::vector<AvHAIBuildableStructure> Result;
@ -624,20 +633,24 @@ AvHAIBuildableStructure* AITAC_FindFurthestDeployableFromLocationByRef(const Vec
return Result; return Result;
} }
AvHAIDroppedItem* AITAC_GetDroppedItemRefFromEdict(edict_t* ItemEdict) AvHAIDroppedItem AITAC_GetDroppedItemRefFromEdict(edict_t* ItemEdict)
{ {
if (FNullEnt(ItemEdict)) { return nullptr; } AvHAIDroppedItem Result;
if (FNullEnt(ItemEdict)) { return Result; }
int EntIndex = ENTINDEX(ItemEdict); int EntIndex = ENTINDEX(ItemEdict);
if (EntIndex < 0) { return nullptr; } if (EntIndex < 0) { return Result; }
return &MarineDroppedItemMap[EntIndex]; Result = MarineDroppedItemMap[EntIndex];
return Result;
} }
AvHAIDroppedItem* AITAC_FindClosestItemToLocation(const Vector& Location, const AvHAIDeployableItemType ItemType, AvHTeamNumber SearchingTeam, const unsigned int ReachabilityFlags, float MinRadius, float MaxRadius, bool bConsiderPhaseDistance) AvHAIDroppedItem AITAC_FindClosestItemToLocation(const Vector& Location, const AvHAIDeployableItemType ItemType, AvHTeamNumber SearchingTeam, const unsigned int ReachabilityFlags, float MinRadius, float MaxRadius, bool bConsiderPhaseDistance)
{ {
AvHAIDroppedItem* Result = NULL; AvHAIDroppedItem Result;
float CurrMinDist = 0.0f; float CurrMinDist = 0.0f;
float MinDistSq = sqrf(MinRadius); float MinDistSq = sqrf(MinRadius);
@ -663,9 +676,9 @@ AvHAIDroppedItem* AITAC_FindClosestItemToLocation(const Vector& Location, const
float DistSq = (bConsiderPhaseDistance) ? sqrf(AITAC_GetPhaseDistanceBetweenPoints(it.second.Location, Location)) : vDist2DSq(it.second.Location, Location); float DistSq = (bConsiderPhaseDistance) ? sqrf(AITAC_GetPhaseDistanceBetweenPoints(it.second.Location, Location)) : vDist2DSq(it.second.Location, Location);
if ((!bUseMinDist || DistSq >= MinDistSq) && (!bUseMaxDist || DistSq <= MaxDistSq) && (!Result || DistSq < CurrMinDist)) if ((!bUseMinDist || DistSq >= MinDistSq) && (!bUseMaxDist || DistSq <= MaxDistSq) && (!Result.IsValid() || DistSq < CurrMinDist))
{ {
Result = &it.second; Result = it.second;
CurrMinDist = DistSq; CurrMinDist = DistSq;
} }
@ -866,6 +879,16 @@ AvHAIBuildableStructure AITAC_GetNearestDeployableDirectlyReachable(AvHAIPlayer*
return Result; return Result;
} }
AvHAIBuildableStructure AITAC_GetDeployableStructureByEntIndex(AvHTeamNumber Team, int EntIndex)
{
return (Team == AIMGR_GetTeamANumber()) ? TeamAStructureMap[EntIndex] : TeamBStructureMap[EntIndex];
}
AvHAIBuildableStructure* AITAC_GetDeployableStructureRefByEntIndex(AvHTeamNumber Team, int EntIndex)
{
return (Team == AIMGR_GetTeamANumber()) ? &TeamAStructureMap[EntIndex] : &TeamBStructureMap[EntIndex];
}
AvHAIBuildableStructure* AITAC_GetNearestDeployableDirectlyReachableByRef(AvHAIPlayer* pBot, const Vector Location, const DeployableSearchFilter* Filter) AvHAIBuildableStructure* AITAC_GetNearestDeployableDirectlyReachableByRef(AvHAIPlayer* pBot, const Vector Location, const DeployableSearchFilter* Filter)
{ {
AvHTeamNumber TeamA = GetGameRules()->GetTeamANumber(); AvHTeamNumber TeamA = GetGameRules()->GetTeamANumber();
@ -1067,6 +1090,20 @@ Vector AITAC_GetFloorLocationForHive(const AvHAIHiveDefinition* Hive)
} }
string AITAC_GetLocationName(Vector Location)
{
string Result;
string theLocationName;
if (AvHSHUGetNameOfLocation(GetGameRules()->GetInfoLocations(), Location, theLocationName))
{
UTIL_LocalizeText(theLocationName.c_str(), theLocationName);
Result = theLocationName;
}
return Result;
}
void AITAC_PopulateHiveData() void AITAC_PopulateHiveData()
{ {
Hives.clear(); Hives.clear();
@ -1091,17 +1128,16 @@ void AITAC_PopulateHiveData()
NewHive.FloorLocation = UTIL_GetFloorUnderEntity(NewHive.HiveEdict); // Some hives are suspended in the air, this is the floor location directly beneath it NewHive.FloorLocation = UTIL_GetFloorUnderEntity(NewHive.HiveEdict); // Some hives are suspended in the air, this is the floor location directly beneath it
string HiveName; string HiveName = AITAC_GetLocationName(NewHive.Location);
string theLocationName; if (HiveName.empty())
if (AvHSHUGetNameOfLocation(GetGameRules()->GetInfoLocations(), NewHive.Location, theLocationName))
{ {
UTIL_LocalizeText(theLocationName.c_str(), theLocationName); sprintf(NewHive.HiveName, "Hive");
HiveName = theLocationName;
} }
else
{
sprintf(NewHive.HiveName, HiveName.c_str(), "%s"); sprintf(NewHive.HiveName, HiveName.c_str(), "%s");
}
Hives.push_back(NewHive); Hives.push_back(NewHive);
@ -1110,6 +1146,11 @@ void AITAC_PopulateHiveData()
void AITAC_RefreshHiveData() void AITAC_RefreshHiveData()
{ {
if (ResourceNodes.size() == 0)
{
AITAC_PopulateResourceNodes();
}
if (Hives.size() == 0) if (Hives.size() == 0)
{ {
AITAC_PopulateHiveData(); AITAC_PopulateHiveData();
@ -1184,6 +1225,36 @@ void AITAC_RefreshHiveData()
} }
} }
Vector AITAC_GetTeamRelocationPoint(AvHTeamNumber Team)
{
Vector CurrentRelocationPoint = (Team == GetGameRules()->GetTeamANumber()) ? TeamARelocationPoint : TeamBRelocationPoint;
if (!AITAC_IsRelocationPointStillValid(Team, CurrentRelocationPoint))
{
CurrentRelocationPoint = AITAC_FindNewTeamRelocationPoint(Team);
}
if (Team == GetGameRules()->GetTeamANumber())
{
TeamARelocationPoint = CurrentRelocationPoint;
}
else
{
TeamBRelocationPoint = CurrentRelocationPoint;
}
return (Team == GetGameRules()->GetTeamANumber()) ? TeamARelocationPoint : TeamBRelocationPoint;
}
Vector AITAC_GetTeamOriginalStartLocation(AvHTeamNumber Team)
{
AvHTeam* TeamRef = AIMGR_GetTeamRef(Team);
if (!TeamRef) { return ZERO_VECTOR; }
return TeamRef->GetStartingLocation();
}
Vector AITAC_GetTeamStartingLocation(AvHTeamNumber Team) Vector AITAC_GetTeamStartingLocation(AvHTeamNumber Team)
{ {
if (vIsZero(TeamAStartingLocation) || vIsZero(TeamBStartingLocation)) if (vIsZero(TeamAStartingLocation) || vIsZero(TeamBStartingLocation))
@ -2421,6 +2492,7 @@ AvHAIBuildableStructure* AITAC_UpdateBuildableStructure(CBaseEntity* Structure)
if (StructureRef->LastSeen == 0) if (StructureRef->LastSeen == 0)
{ {
StructureRef->Location = BuildingEdict->v.origin; StructureRef->Location = BuildingEdict->v.origin;
StructureRef->EntIndex = EntIndex;
StructureRef->edict = BuildingEdict; StructureRef->edict = BuildingEdict;
StructureRef->healthPercent = 1.0f; StructureRef->healthPercent = 1.0f;
StructureRef->EntityRef = nullptr; StructureRef->EntityRef = nullptr;
@ -2449,6 +2521,7 @@ AvHAIBuildableStructure* AITAC_UpdateBuildableStructure(CBaseEntity* Structure)
{ {
StructureRef->EntityRef = BaseBuildable; StructureRef->EntityRef = BaseBuildable;
StructureRef->edict = BuildingEdict; StructureRef->edict = BuildingEdict;
StructureRef->EntIndex = EntIndex;
StructureRef->OffMeshConnections.clear(); StructureRef->OffMeshConnections.clear();
StructureRef->Obstacles.clear(); StructureRef->Obstacles.clear();
@ -2585,7 +2658,7 @@ AvHAIBuildableStructure* AITAC_UpdateBuildableStructure(CBaseEntity* Structure)
if (NearestHive) if (NearestHive)
{ {
if (NearestHive->Status == HIVE_STATUS_UNBUILT && vDist2DSq(NearestHive->FloorLocation, StructureRef->Location) < sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) if (NearestHive->Status == HIVE_STATUS_UNBUILT && vDist2DSq(NearestHive->FloorLocation, StructureRef->Location) < sqrf(UTIL_MetresToGoldSrcUnits(15.0f)))
{ {
StructureRef->Purpose = STRUCTURE_PURPOSE_FORTIFY; StructureRef->Purpose = STRUCTURE_PURPOSE_FORTIFY;
} }
@ -2865,9 +2938,12 @@ void AITAC_ClearMapAIData(bool bInitialMapLoad)
AITAC_ClearStructureNavData(); AITAC_ClearStructureNavData();
while (!bTileCacheUpToDate) int NumAttempts = 0;
while (!bTileCacheUpToDate && NumAttempts < 30)
{ {
UTIL_UpdateTileCache(); UTIL_UpdateTileCache();
NumAttempts++;
} }
} }
else else
@ -3086,7 +3162,6 @@ bool UTIL_ShouldStructureCollide(AvHAIDeployableStructureType StructureType)
{ {
case STRUCTURE_MARINE_INFANTRYPORTAL: case STRUCTURE_MARINE_INFANTRYPORTAL:
case STRUCTURE_MARINE_PHASEGATE: case STRUCTURE_MARINE_PHASEGATE:
case STRUCTURE_MARINE_TURRET:
case STRUCTURE_MARINE_DEPLOYEDMINE: case STRUCTURE_MARINE_DEPLOYEDMINE:
return false; return false;
default: default:
@ -4379,7 +4454,6 @@ edict_t* AITAC_GetCommChair(AvHTeamNumber Team)
ChairFilter.DeployableTypes = STRUCTURE_MARINE_COMMCHAIR; ChairFilter.DeployableTypes = STRUCTURE_MARINE_COMMCHAIR;
ChairFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; ChairFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
ChairFilter.DeployableTeam = Team; ChairFilter.DeployableTeam = Team;
ChairFilter.ReachabilityTeam = TEAM_IND;
vector<AvHAIBuildableStructure> CommChairs = AITAC_FindAllDeployables(ZERO_VECTOR, &ChairFilter); vector<AvHAIBuildableStructure> CommChairs = AITAC_FindAllDeployables(ZERO_VECTOR, &ChairFilter);
@ -4391,8 +4465,10 @@ edict_t* AITAC_GetCommChair(AvHTeamNumber Team)
{ {
AvHCommandStation* ChairRef = dynamic_cast<AvHCommandStation*>((*it).EntityRef); AvHCommandStation* ChairRef = dynamic_cast<AvHCommandStation*>((*it).EntityRef);
if (!ChairRef) { continue; }
// Idle animation will be 3 if the chair is in use (closed animation). See AvHCommandStation::GetIdleAnimation // Idle animation will be 3 if the chair is in use (closed animation). See AvHCommandStation::GetIdleAnimation
if (ChairRef && ChairRef->GetIdleAnimation() == 3) if (ChairRef->GetIdleAnimation() == 3)
{ {
MainCommChair = ChairRef->edict(); MainCommChair = ChairRef->edict();
} }
@ -4759,11 +4835,16 @@ bool AITAC_ShouldBotBuildHive(AvHAIPlayer* pBot, AvHAIHiveDefinition** EligibleH
HiveCost += BALANCE_VAR(kGorgeCost); HiveCost += BALANCE_VAR(kGorgeCost);
} }
if (pBot->Player->GetResources() < HiveCost) { return false; } if (pBot->Player->GetResources() < HiveCost - 10) { return false; }
AvHTeamNumber BotTeam = pBot->Player->GetTeam(); AvHTeamNumber BotTeam = pBot->Player->GetTeam();
AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam); AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam);
AvHAIPlayer* ExistingBuilder = GetFirstBotWithBuildTask(BotTeam, STRUCTURE_ALIEN_HIVE, pBot->Edict);
// Another bot already plans to do it
if (ExistingBuilder && IsPlayerActiveInGame(ExistingBuilder->Edict)) { return false; }
// Prioritise getting at least one fade or Onos on the team before putting up a second hive, or we're likely to lose it pretty quickly // Prioritise getting at least one fade or Onos on the team before putting up a second hive, or we're likely to lose it pretty quickly
int NumHeavyHitters = AITAC_GetNumPlayersOnTeamOfClass(BotTeam, AVH_USER3_ALIEN_PLAYER4, nullptr) + AITAC_GetNumPlayersOnTeamOfClass(BotTeam, AVH_USER3_ALIEN_PLAYER5, nullptr); int NumHeavyHitters = AITAC_GetNumPlayersOnTeamOfClass(BotTeam, AVH_USER3_ALIEN_PLAYER4, nullptr) + AITAC_GetNumPlayersOnTeamOfClass(BotTeam, AVH_USER3_ALIEN_PLAYER5, nullptr);
@ -4779,6 +4860,8 @@ bool AITAC_ShouldBotBuildHive(AvHAIPlayer* pBot, AvHAIHiveDefinition** EligibleH
{ {
AvHAIPlayer* OtherBot = (*it); AvHAIPlayer* OtherBot = (*it);
if (OtherBot == pBot) { continue; }
// If the other bot has enough resources to drop a hive, and they're a less expensive life form than us, let them do it. // If the other bot has enough resources to drop a hive, and they're a less expensive life form than us, let them do it.
if (OtherBot->Player->GetResources() >= BALANCE_VAR(kHiveCost) * 0.8f && OtherBot->Player->GetUser3() < pBot->Player->GetUser3()) { return false; } if (OtherBot->Player->GetResources() >= BALANCE_VAR(kHiveCost) * 0.8f && OtherBot->Player->GetUser3() < pBot->Player->GetUser3()) { return false; }
} }
@ -5313,6 +5396,8 @@ edict_t* AITAC_AlienFindNearestHealingSource(AvHTeamNumber Team, Vector SearchLo
edict_t* Result = nullptr; edict_t* Result = nullptr;
float MinDist = 0.0f; float MinDist = 0.0f;
AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(Team);
vector<AvHAIHiveDefinition*> AllTeamHives = AITAC_GetAllTeamHives(Team, true); vector<AvHAIHiveDefinition*> AllTeamHives = AITAC_GetAllTeamHives(Team, true);
for (auto it = AllTeamHives.begin(); it != AllTeamHives.end(); it++) for (auto it = AllTeamHives.begin(); it != AllTeamHives.end(); it++)
@ -5321,6 +5406,8 @@ edict_t* AITAC_AlienFindNearestHealingSource(AvHTeamNumber Team, Vector SearchLo
// Factor healing radius into the distance checks, we don't have to be right at the hive to heal // Factor healing radius into the distance checks, we don't have to be right at the hive to heal
ThisDist -= BALANCE_VAR(kHiveHealRadius) * 0.75f; ThisDist -= BALANCE_VAR(kHiveHealRadius) * 0.75f;
if (AITAC_AnyPlayerOnTeamHasLOSToLocation(EnemyTeam, (*it)->Location, UTIL_MetresToGoldSrcUnits(30.0f), nullptr)) { continue; }
// We're already in healing distance of a hive, that's our healing source // We're already in healing distance of a hive, that's our healing source
if (ThisDist <= 0.0f) { return (*it)->HiveEdict; } if (ThisDist <= 0.0f) { return (*it)->HiveEdict; }
@ -5346,6 +5433,8 @@ edict_t* AITAC_AlienFindNearestHealingSource(AvHTeamNumber Team, Vector SearchLo
// Factor healing radius into the distance checks, we don't have to be sat on top of the DC to heal // Factor healing radius into the distance checks, we don't have to be sat on top of the DC to heal
ThisDist -= BALANCE_VAR(kHiveHealRadius) * 0.75f; ThisDist -= BALANCE_VAR(kHiveHealRadius) * 0.75f;
if (AITAC_AnyPlayerOnTeamHasLOSToLocation(EnemyTeam, ThisDC.Location, UTIL_MetresToGoldSrcUnits(30.0f), nullptr)) { continue; }
// We're already in healing distance of a DC, that's our healing source // We're already in healing distance of a DC, that's our healing source
if (ThisDist <= 0.0f) { return ThisDC.edict; } if (ThisDist <= 0.0f) { return ThisDC.edict; }
@ -5362,6 +5451,11 @@ edict_t* AITAC_AlienFindNearestHealingSource(AvHTeamNumber Team, Vector SearchLo
{ {
float PlayerSearchDist = (!FNullEnt(Result)) ? MinDist : 0.0f; // As before, we only want players closer than our current "winner" float PlayerSearchDist = (!FNullEnt(Result)) ? MinDist : 0.0f; // As before, we only want players closer than our current "winner"
FriendlyGorge = AITAC_GetNearestPlayerOfClassInArea(Team, SearchLocation, PlayerSearchDist, false, SearchingPlayer, AVH_USER3_ALIEN_PLAYER2); FriendlyGorge = AITAC_GetNearestPlayerOfClassInArea(Team, SearchLocation, PlayerSearchDist, false, SearchingPlayer, AVH_USER3_ALIEN_PLAYER2);
if (!FNullEnt(FriendlyGorge))
{
if (AITAC_AnyPlayerOnTeamHasLOSToLocation(EnemyTeam, FriendlyGorge->v.origin, UTIL_MetresToGoldSrcUnits(30.0f), nullptr)) { FriendlyGorge = nullptr; }
}
} }
return (!FNullEnt(FriendlyGorge) ? FriendlyGorge : Result); return (!FNullEnt(FriendlyGorge) ? FriendlyGorge : Result);
@ -5507,15 +5601,16 @@ bool AITAC_IsBotPursuingSquadObjective(AvHAIPlayer* pBot, AvHAISquad* Squad)
if (!IsPlayerActiveInGame(pBot->Edict) || pBot->Player->GetTeam() != Squad->SquadTeam) { return false; } if (!IsPlayerActiveInGame(pBot->Edict) || pBot->Player->GetTeam() != Squad->SquadTeam) { return false; }
// Bot no longer has this squad's objective as its primary task // Bot no longer has this squad's objective as its primary task
if (pBot->PrimaryBotTask.TaskType != Squad->SquadObjective || pBot->PrimaryBotTask.TaskTarget != Squad->SquadTarget) { return false; } if (pBot->PrimaryBotTask.TaskType != Squad->SquadObjective || (!FNullEnt(Squad->SquadTarget) && pBot->PrimaryBotTask.TaskTarget != Squad->SquadTarget) || (FNullEnt(Squad->SquadTarget) && !vEquals(pBot->PrimaryBotTask.TaskLocation, Squad->ObjectiveLocation))) { return false; }
// Bot is focused on the job at hand // Bot is focused on the job at hand
if (!pBot->CurrentTask || pBot->CurrentTask == &pBot->PrimaryBotTask) { return true; } if (!pBot->CurrentTask || pBot->CurrentTask == &pBot->PrimaryBotTask) { return true; }
// Bot isn't currently pursuing squad objective, so check if it's doing something in the vicinity // Bot isn't currently pursuing squad objective, so check if it's doing something in the vicinity
Vector TaskLocation = (!FNullEnt(pBot->CurrentTask->TaskTarget)) ? pBot->CurrentTask->TaskTarget->v.origin : pBot->CurrentTask->TaskLocation; Vector BotTaskLocation = (!FNullEnt(pBot->CurrentTask->TaskTarget)) ? pBot->CurrentTask->TaskTarget->v.origin : pBot->CurrentTask->TaskLocation;
Vector SquadTaskLocation = (!FNullEnt(Squad->SquadTarget)) ? Squad->SquadTarget->v.origin : Squad->ObjectiveLocation;
return vDist2DSq(TaskLocation, Squad->SquadTarget->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(10.0f)); return vDist2DSq(BotTaskLocation, SquadTaskLocation) < sqrf(UTIL_MetresToGoldSrcUnits(10.0f));
} }
void AITAC_ManageSquads() void AITAC_ManageSquads()
@ -5525,7 +5620,7 @@ void AITAC_ManageSquads()
for (auto pIt = it->SquadMembers.begin(); pIt != it->SquadMembers.end();) for (auto pIt = it->SquadMembers.begin(); pIt != it->SquadMembers.end();)
{ {
AvHAIPlayer* ThisPlayer = (*pIt); AvHAIPlayer* ThisPlayer = (*pIt);
if (!AITAC_IsBotPursuingSquadObjective(ThisPlayer, &(*it))) if (!ThisPlayer || FNullEnt(ThisPlayer->Edict) || !AITAC_IsBotPursuingSquadObjective(ThisPlayer, &(*it)))
{ {
pIt = it->SquadMembers.erase(pIt); pIt = it->SquadMembers.erase(pIt);
} }
@ -5558,7 +5653,9 @@ void AITAC_UpdateSquads()
{ {
vector<bot_path_node> TravelPath; vector<bot_path_node> TravelPath;
dtStatus PathFindResult = FindPathClosestToPoint(GetBaseNavProfile(ONOS_BASE_NAV_PROFILE), AITAC_GetTeamStartingLocation(it->SquadTeam), UTIL_GetEntityGroundLocation(it->SquadTarget), TravelPath, UTIL_MetresToGoldSrcUnits(20.0f)); Vector TargetLocation = (!FNullEnt(it->SquadTarget)) ? UTIL_GetEntityGroundLocation(it->SquadTarget) : it->ObjectiveLocation;
dtStatus PathFindResult = FindPathClosestToPoint(GetBaseNavProfile(ONOS_BASE_NAV_PROFILE), AITAC_GetTeamStartingLocation(it->SquadTeam), TargetLocation, TravelPath, UTIL_MetresToGoldSrcUnits(20.0f));
if (dtStatusSucceed(PathFindResult)) if (dtStatusSucceed(PathFindResult))
{ {
@ -5566,9 +5663,12 @@ void AITAC_UpdateSquads()
{ {
if (pIt->area != SAMPLE_POLYAREA_GROUND || pIt->flag != SAMPLE_POLYFLAGS_WALK) { continue; } if (pIt->area != SAMPLE_POLYAREA_GROUND || pIt->flag != SAMPLE_POLYFLAGS_WALK) { continue; }
if (!FNullEnt(it->SquadTarget))
{
if (UTIL_QuickTrace(nullptr, pIt->Location, it->SquadTarget->v.origin)) { continue; } if (UTIL_QuickTrace(nullptr, pIt->Location, it->SquadTarget->v.origin)) { continue; }
}
if (vDist2DSq(pIt->Location, it->SquadTarget->v.origin) > sqrf(UTIL_MetresToGoldSrcUnits(20.0f))) if (vDist2DSq(pIt->Location, TargetLocation) > sqrf(UTIL_MetresToGoldSrcUnits(20.0f)))
{ {
DeployableSearchFilter EnemyStuff; DeployableSearchFilter EnemyStuff;
EnemyStuff.DeployableTeam = AIMGR_GetEnemyTeam(it->SquadTeam); EnemyStuff.DeployableTeam = AIMGR_GetEnemyTeam(it->SquadTeam);
@ -5656,6 +5756,48 @@ AvHAISquad* AITAC_GetSquadForObjective(AvHAIPlayer* pBot, edict_t* TaskTarget, B
return nullptr; return nullptr;
} }
AvHAISquad* AITAC_GetSquadForObjective(AvHAIPlayer* pBot, Vector TaskLocation, BotTaskType ObjectiveType)
{
AvHAISquad* JoinSquad = nullptr;
for (auto it = ActiveSquads.begin(); it != ActiveSquads.end(); it++)
{
if (it->SquadTeam == pBot->Player->GetTeam() && vEquals(it->ObjectiveLocation, TaskLocation) && it->SquadObjective == ObjectiveType)
{
auto element = std::find(it->SquadMembers.begin(), it->SquadMembers.end(), pBot);
if (element != it->SquadMembers.end())
{
return &(*it);
}
else
{
if (!JoinSquad && !it->bExecuteObjective)
{
JoinSquad = &(*it);
}
}
}
}
if (JoinSquad)
{
JoinSquad->SquadMembers.push_back(pBot);
return JoinSquad;
}
AvHAISquad NewSquad;
NewSquad.SquadTeam = pBot->Player->GetTeam();
NewSquad.ObjectiveLocation = TaskLocation;
NewSquad.SquadObjective = ObjectiveType;
NewSquad.bExecuteObjective = false;
NewSquad.SquadGatherLocation = ZERO_VECTOR;
ActiveSquads.push_back(NewSquad);
return nullptr;
}
void AITAC_ClearSquads() void AITAC_ClearSquads()
{ {
ActiveSquads.clear(); ActiveSquads.clear();
@ -5696,3 +5838,467 @@ Vector AITAC_GetGatherLocationForSquad(AvHAISquad* Squad)
return ZERO_VECTOR; return ZERO_VECTOR;
} }
Vector AITAC_FindNewTeamRelocationPoint(AvHTeamNumber Team)
{
AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(Team);
// Only relocate if:
// There is a hive to relocate to with a marine ready to build
// The current base is overrun and lost
// Or we decide we want to, and the current base isn't too built up
Vector CurrentTeamStartLocation = AITAC_GetTeamStartingLocation(Team);
const AvHAIHiveDefinition* RelocationHive = nullptr;
float MinDist = 0.0f;
vector<AvHAIHiveDefinition*> AllHives = AITAC_GetAllHives();
for (auto it = AllHives.begin(); it != AllHives.end(); it++)
{
const AvHAIHiveDefinition* ThisHive = (*it);
// Obviously don't relocate to an active enemy hive...
if (ThisHive->Status != HIVE_STATUS_UNBUILT) { continue; }
// Don't relocate if we're already located close to this hive
if (vDist2DSq(CurrentTeamStartLocation, ThisHive->FloorLocation) < sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) { continue; }
// Don't relocate if the enemy has a foothold here
DeployableSearchFilter EnemyStuff;
EnemyStuff.DeployableTeam = EnemyTeam;
EnemyStuff.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
EnemyStuff.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
EnemyStuff.DeployableTypes = (STRUCTURE_MARINE_PHASEGATE | STRUCTURE_MARINE_COMMCHAIR | STRUCTURE_MARINE_INFANTRYPORTAL | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY | STRUCTURE_ALIEN_OFFENCECHAMBER);
EnemyStuff.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f);
if (AITAC_DeployableExistsAtLocation(ThisHive->FloorLocation, &EnemyStuff)) { continue; }
const AvHAIHiveDefinition* NearestEnemyHive = AITAC_GetActiveHiveNearestLocation(EnemyTeam, ThisHive->FloorLocation);
float ThisDist = 0.0f;
// Either pick an empty hive furthest from the nearest enemy hive (if they have one)
// Or the closest one to us if the enemy don't (e.g. it's MvM)
if (NearestEnemyHive)
{
ThisDist = vDist2DSq(NearestEnemyHive->FloorLocation, ThisHive->FloorLocation);
if (!RelocationHive || ThisDist > MinDist)
{
RelocationHive = ThisHive;
MinDist = ThisDist;
}
}
else
{
ThisDist = vDist2DSq(CurrentTeamStartLocation, ThisHive->FloorLocation);
if (!RelocationHive || ThisDist < MinDist)
{
RelocationHive = ThisHive;
MinDist = ThisDist;
}
}
}
// No hives to relocate to
if (!RelocationHive) { return ZERO_VECTOR; }
return RelocationHive->FloorLocation;
}
bool AITAC_IsRelocationPointStillValid(AvHTeamNumber RelocationTeam, Vector RelocationPoint)
{
if (vIsZero(RelocationPoint)) { return false; }
const AvHAIHiveDefinition* ThisHive = AITAC_GetHiveNearestLocation(RelocationPoint);
// Obviously don't relocate to an active enemy hive...
if (ThisHive->Status != HIVE_STATUS_UNBUILT) { return false; }
AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(RelocationTeam);
// Don't relocate if the enemy has a foothold here
DeployableSearchFilter EnemyStuff;
EnemyStuff.DeployableTeam = EnemyTeam;
EnemyStuff.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
EnemyStuff.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
EnemyStuff.DeployableTypes = (STRUCTURE_MARINE_PHASEGATE | STRUCTURE_MARINE_COMMCHAIR | STRUCTURE_MARINE_INFANTRYPORTAL | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY | STRUCTURE_ALIEN_OFFENCECHAMBER);
EnemyStuff.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f);
if (AITAC_DeployableExistsAtLocation(ThisHive->FloorLocation, &EnemyStuff)) { return false; }
return true;
}
bool AITAC_IsRelocationCompleted(AvHTeamNumber RelocationTeam, Vector RelocationPoint)
{
if (vIsZero(RelocationPoint)) { return true; }
// Don't relocate if the enemy has a foothold here
DeployableSearchFilter BaseStuffFilter;
BaseStuffFilter.DeployableTeam = RelocationTeam;
BaseStuffFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
BaseStuffFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
BaseStuffFilter.DeployableTypes = (STRUCTURE_MARINE_COMMCHAIR | STRUCTURE_MARINE_INFANTRYPORTAL);
BaseStuffFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f);
edict_t* RelocationChair = nullptr;
edict_t* CurrentCommChair = AITAC_GetCommChair(RelocationTeam);
int NumInfPortals = 0;
vector<AvHAIBuildableStructure> RelocationStructures = AITAC_FindAllDeployables(RelocationPoint, &BaseStuffFilter);
for (auto it = RelocationStructures.begin(); it != RelocationStructures.end(); it++)
{
if (it->StructureType == STRUCTURE_MARINE_COMMCHAIR)
{
RelocationChair = it->edict;
}
if (it->StructureType == STRUCTURE_MARINE_INFANTRYPORTAL)
{
NumInfPortals++;
}
}
if (FNullEnt(RelocationChair) || NumInfPortals < 2) { return false; }
DeployableSearchFilter OldStuffFilter;
OldStuffFilter.DeployableTeam = RelocationTeam;
OldStuffFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
OldStuffFilter.DeployableTypes = SEARCH_ALL_STRUCTURES;
OldStuffFilter.MinSearchRadius = UTIL_MetresToGoldSrcUnits(20.0f);
OldStuffFilter.PurposeFlags = STRUCTURE_PURPOSE_BASE;
vector<AvHAIBuildableStructure> AllOldStructures = AITAC_FindAllDeployables(RelocationPoint, &OldStuffFilter);
for (auto it = AllOldStructures.begin(); it != AllOldStructures.end(); it++)
{
if (it->edict != CurrentCommChair) { return false; }
}
return true;
}
bool AITAC_IsRelocationAtStartEnabled()
{
return bEnableRelocationAtStart;
}
void AITAC_DetermineRelocationEnabled()
{
bEnableRelocationAtStart = false;
if (CONFIG_IsRelocationAllowed())
{
float RandomRoll = frandrange(0.0f, 1.0f);
bEnableRelocationAtStart = (RandomRoll <= CONFIG_GetRelocationChance());
}
}
bool AITAC_IsMarineBaseValid(AvHAIMarineBase* Base)
{
if (Base->PlacedStructures.size() > 0) { return true; }
if ((Base->bRecycleBase || Base->bBaseInitialised) && Base->PlacedStructures.size() == 0) { return false; }
return true;
}
void AITAC_ManageActiveMarineBases()
{
for (auto it = ActiveTeamABases.begin(); it != ActiveTeamABases.end();)
{
for (auto structIt = it->PlacedStructures.begin(); structIt != it->PlacedStructures.end();)
{
AvHAIBuildableStructure StructureRef = TeamAStructureMap[*structIt];
if (!StructureRef.IsValid() || (StructureRef.StructureStatusFlags & STRUCTURE_STATUS_RECYCLING))
{
structIt = it->PlacedStructures.erase(structIt);
}
else
{
structIt++;
}
}
if (!AITAC_IsMarineBaseValid(&(*it)))
{
it = ActiveTeamABases.erase(it);
}
else
{
it++;
}
}
for (auto it = ActiveTeamBBases.begin(); it != ActiveTeamBBases.end();)
{
for (auto structIt = it->PlacedStructures.begin(); structIt != it->PlacedStructures.end();)
{
AvHAIBuildableStructure StructureRef = TeamBStructureMap[*structIt];
if (!StructureRef.IsValid() || (StructureRef.StructureStatusFlags & STRUCTURE_STATUS_RECYCLING))
{
structIt = it->PlacedStructures.erase(structIt);
}
else
{
structIt++;
}
}
if (!AITAC_IsMarineBaseValid(&(*it)))
{
it = ActiveTeamBBases.erase(it);
}
else
{
it++;
}
}
}
void AITAC_AddNewBase(AvHTeamNumber Team, Vector NewBaseLocation, MarineBaseType NewBaseType)
{
vector<AvHAIMarineBase>& BaseList = (Team == AIMGR_GetTeamANumber()) ? ActiveTeamABases : ActiveTeamBBases;
AvHAIMarineBase NewBase;
NewBase.BaseLocation = NewBaseLocation;
NewBase.BaseType = NewBaseType;
NewBase.BaseTeam = Team;
BaseList.push_back(NewBase);
}
bool AITAC_CanBuildOutBase(const AvHAIMarineBase* Base)
{
if (!Base || Base->bRecycleBase || !Base->bIsActive) { return false; }
switch (Base->BaseType)
{
case MARINE_BASE_MAINBASE:
return AITAC_CanBuildOutMainBase(Base);
case MARINE_BASE_OUTPOST:
return AITAC_CanBuildOutOutpost(Base);
case MARINE_BASE_SIEGE:
return AITAC_CanBuildOutSiege(Base);
case MARINE_BASE_GUARDPOST:
return AITAC_CanBuildOutGuardPost(Base);
}
return false;
}
bool AITAC_CanBuildOutMainBase(const AvHAIMarineBase* Base)
{
bool bHasCommChair = false;
int NumInfPortals = 0;
bool bHasArmoury = false;
bool bHasAdvArmoury = false;
bool bArmouryCompleted = false;
bool bHasArmsLab = false;
bool bArmsLabCompleted = false;
bool bHasProtoLab = false;
bool bHasObs = false;
bool bHasPhase = false;
bool bHasTF = false;
int NumTurrets = 0;
std::unordered_map<int, AvHAIBuildableStructure>& BuildingMap = (Base->BaseTeam == AIMGR_GetTeamANumber()) ? TeamAStructureMap : TeamBStructureMap;
for (auto it = Base->PlacedStructures.begin(); it != Base->PlacedStructures.end(); it++)
{
AvHAIBuildableStructure StructureRef = BuildingMap[*it];
switch (StructureRef.StructureType)
{
case STRUCTURE_MARINE_COMMCHAIR:
bHasCommChair = true;
break;
case STRUCTURE_MARINE_INFANTRYPORTAL:
NumInfPortals++;
break;
case STRUCTURE_MARINE_ARMOURY:
bArmouryCompleted = (StructureRef.StructureStatusFlags & STRUCTURE_STATUS_COMPLETED);
bHasArmoury = true;
break;
case STRUCTURE_MARINE_ADVARMOURY:
bArmouryCompleted = (StructureRef.StructureStatusFlags & STRUCTURE_STATUS_COMPLETED);
bHasArmoury = true;
bHasAdvArmoury = true;
break;
case STRUCTURE_MARINE_ARMSLAB:
bHasArmsLab = true;
bArmsLabCompleted = (StructureRef.StructureStatusFlags & STRUCTURE_STATUS_COMPLETED);
break;
case STRUCTURE_MARINE_PROTOTYPELAB:
bHasProtoLab = true;
break;
case STRUCTURE_MARINE_OBSERVATORY:
bHasObs = true;
break;
case STRUCTURE_MARINE_PHASEGATE:
bHasPhase = true;
break;
case STRUCTURE_MARINE_TURRETFACTORY:
case STRUCTURE_MARINE_ADVTURRETFACTORY:
bHasTF = true;
break;
case STRUCTURE_MARINE_TURRET:
NumTurrets++;
break;
default:
break;
}
}
return (!bHasCommChair
|| NumInfPortals < 2
|| !bHasArmoury
|| !bHasAdvArmoury
|| (!bHasArmsLab && bArmouryCompleted)
|| (!bHasProtoLab && bHasAdvArmoury && bArmsLabCompleted)
|| (!bHasObs && bArmouryCompleted)
|| (!bHasPhase && AITAC_ResearchIsComplete(Base->BaseTeam, TECH_RESEARCH_PHASETECH))
|| !bHasTF
|| NumTurrets < 5);
}
bool AITAC_CanBuildOutOutpost(const AvHAIMarineBase* Base)
{
bool bHasArmoury = false;
bool bHasObs = false;
bool bHasPhase = false;
bool bHasTF = false;
int NumTurrets = 0;
std::unordered_map<int, AvHAIBuildableStructure>& BuildingMap = (Base->BaseTeam == AIMGR_GetTeamANumber()) ? TeamAStructureMap : TeamBStructureMap;
for (auto it = Base->PlacedStructures.begin(); it != Base->PlacedStructures.end(); it++)
{
AvHAIBuildableStructure StructureRef = BuildingMap[*it];
switch (StructureRef.StructureType)
{
case STRUCTURE_MARINE_ARMOURY:
case STRUCTURE_MARINE_ADVARMOURY:
bHasArmoury = true;
break;
case STRUCTURE_MARINE_OBSERVATORY:
bHasObs = true;
break;
case STRUCTURE_MARINE_PHASEGATE:
bHasPhase = true;
break;
case STRUCTURE_MARINE_TURRETFACTORY:
case STRUCTURE_MARINE_ADVTURRETFACTORY:
bHasTF = true;
break;
case STRUCTURE_MARINE_TURRET:
NumTurrets++;
break;
default:
break;
}
}
return (!bHasArmoury
|| !bHasObs
|| (!bHasPhase && AITAC_ResearchIsComplete(Base->BaseTeam, TECH_RESEARCH_PHASETECH))
|| !bHasTF
|| NumTurrets < 5);
}
bool AITAC_CanBuildOutSiege(const AvHAIMarineBase* Base)
{
bool bHasArmoury = false;
bool bHasObs = false;
bool bHasPhase = false;
bool bHasTF = false;
int NumTurrets = 0;
std::unordered_map<int, AvHAIBuildableStructure>& BuildingMap = (Base->BaseTeam == AIMGR_GetTeamANumber()) ? TeamAStructureMap : TeamBStructureMap;
for (auto it = Base->PlacedStructures.begin(); it != Base->PlacedStructures.end(); it++)
{
AvHAIBuildableStructure StructureRef = BuildingMap[*it];
switch (StructureRef.StructureType)
{
case STRUCTURE_MARINE_ARMOURY:
case STRUCTURE_MARINE_ADVARMOURY:
bHasArmoury = true;
break;
case STRUCTURE_MARINE_OBSERVATORY:
bHasObs = true;
break;
case STRUCTURE_MARINE_PHASEGATE:
bHasPhase = true;
break;
case STRUCTURE_MARINE_ADVTURRETFACTORY:
bHasTF = true;
break;
case STRUCTURE_MARINE_SIEGETURRET:
NumTurrets++;
break;
default:
break;
}
}
return (!bHasArmoury
|| !bHasObs
|| (!bHasPhase && AITAC_ResearchIsComplete(Base->BaseTeam, TECH_RESEARCH_PHASETECH))
|| !bHasTF
|| NumTurrets < 3);
}
bool AITAC_CanBuildOutGuardPost(const AvHAIMarineBase* Base)
{
bool bHasObs = false;
bool bHasTF = false;
int NumTurrets = 0;
std::unordered_map<int, AvHAIBuildableStructure>& BuildingMap = (Base->BaseTeam == AIMGR_GetTeamANumber()) ? TeamAStructureMap : TeamBStructureMap;
for (auto it = Base->PlacedStructures.begin(); it != Base->PlacedStructures.end(); it++)
{
AvHAIBuildableStructure StructureRef = BuildingMap[*it];
switch (StructureRef.StructureType)
{
case STRUCTURE_MARINE_OBSERVATORY:
bHasObs = true;
break;
case STRUCTURE_MARINE_TURRETFACTORY:
case STRUCTURE_MARINE_ADVTURRETFACTORY:
bHasTF = true;
break;
case STRUCTURE_MARINE_TURRET:
NumTurrets++;
break;
default:
break;
}
}
return (!bHasObs
|| !bHasTF
|| NumTurrets < 5);
}
vector<AvHAIMarineBase>& AITAC_GetTeamBases(AvHTeamNumber Team)
{
return (Team == AIMGR_GetTeamANumber()) ? ActiveTeamABases : ActiveTeamBBases;
}

View file

@ -31,6 +31,8 @@ AvHAIBuildableStructure AITAC_GetDeployableFromEdict(const edict_t* Structure);
AvHAIBuildableStructure* AITAC_GetDeployableRefFromEdict(const edict_t* Structure); AvHAIBuildableStructure* AITAC_GetDeployableRefFromEdict(const edict_t* Structure);
AvHAIBuildableStructure AITAC_GetNearestDeployableDirectlyReachable(AvHAIPlayer* pBot, const Vector Location, const DeployableSearchFilter* Filter); AvHAIBuildableStructure AITAC_GetNearestDeployableDirectlyReachable(AvHAIPlayer* pBot, const Vector Location, const DeployableSearchFilter* Filter);
AvHAIBuildableStructure* AITAC_GetNearestDeployableDirectlyReachableByRef(AvHAIPlayer* pBot, const Vector Location, const DeployableSearchFilter* Filter); AvHAIBuildableStructure* AITAC_GetNearestDeployableDirectlyReachableByRef(AvHAIPlayer* pBot, const Vector Location, const DeployableSearchFilter* Filter);
AvHAIBuildableStructure AITAC_GetDeployableStructureByEntIndex(AvHTeamNumber Team, int EntIndex);
AvHAIBuildableStructure* AITAC_GetDeployableStructureRefByEntIndex(AvHTeamNumber Team, int EntIndex);
int AITAC_GetNumDeployablesNearLocation(const Vector& Location, const DeployableSearchFilter* Filter); int AITAC_GetNumDeployablesNearLocation(const Vector& Location, const DeployableSearchFilter* Filter);
void AITAC_PopulateHiveData(); void AITAC_PopulateHiveData();
void AITAC_RefreshHiveData(); void AITAC_RefreshHiveData();
@ -62,15 +64,20 @@ const AvHAIHiveDefinition* AITAC_GetNonEmptyHiveNearestLocation(const Vector Sea
Vector AITAC_GetCommChairLocation(AvHTeamNumber Team); Vector AITAC_GetCommChairLocation(AvHTeamNumber Team);
edict_t* AITAC_GetCommChair(AvHTeamNumber Team); edict_t* AITAC_GetCommChair(AvHTeamNumber Team);
Vector AITAC_GetTeamOriginalStartLocation(AvHTeamNumber Team);
Vector AITAC_GetTeamStartingLocation(AvHTeamNumber Team); Vector AITAC_GetTeamStartingLocation(AvHTeamNumber Team);
Vector AITAC_GetTeamRelocationPoint(AvHTeamNumber Team);
// Returns the name of the supplied location on the map. This will be the same as what appears in the bottom left of the player's screen
string AITAC_GetLocationName(Vector Location);
AvHAIResourceNode* AITAC_GetRandomResourceNode(AvHTeamNumber SearchingTeam, const unsigned int ReachabilityFlags); AvHAIResourceNode* AITAC_GetRandomResourceNode(AvHTeamNumber SearchingTeam, const unsigned int ReachabilityFlags);
AvHAIDroppedItem* AITAC_FindClosestItemToLocation(const Vector& Location, const AvHAIDeployableItemType ItemType, AvHTeamNumber SearchingTeam, const unsigned int ReachabilityFlags, float MinRadius, float MaxRadius, bool bConsiderPhaseDistance); AvHAIDroppedItem AITAC_FindClosestItemToLocation(const Vector& Location, const AvHAIDeployableItemType ItemType, AvHTeamNumber SearchingTeam, const unsigned int ReachabilityFlags, float MinRadius, float MaxRadius, bool bConsiderPhaseDistance);
bool AITAC_ItemExistsInLocation(const Vector& Location, const AvHAIDeployableItemType ItemType, AvHTeamNumber SearchingTeam, const unsigned int ReachabilityFlags, float MinRadius, float MaxRadius, bool bConsiderPhaseDistance); bool AITAC_ItemExistsInLocation(const Vector& Location, const AvHAIDeployableItemType ItemType, AvHTeamNumber SearchingTeam, const unsigned int ReachabilityFlags, float MinRadius, float MaxRadius, bool bConsiderPhaseDistance);
int AITAC_GetNumItemsInLocation(const Vector& Location, const AvHAIDeployableItemType ItemType, AvHTeamNumber SearchingTeam, const unsigned int ReachabilityFlags, float MinRadius, float MaxRadius, bool bConsiderPhaseDistance); int AITAC_GetNumItemsInLocation(const Vector& Location, const AvHAIDeployableItemType ItemType, AvHTeamNumber SearchingTeam, const unsigned int ReachabilityFlags, float MinRadius, float MaxRadius, bool bConsiderPhaseDistance);
AvHAIDroppedItem* AITAC_GetDroppedItemRefFromEdict(edict_t* ItemEdict); AvHAIDroppedItem AITAC_GetDroppedItemRefFromEdict(edict_t* ItemEdict);
Vector AITAC_GetRandomBuildHintInLocation(const unsigned int StructureType, const Vector SearchLocation, const float SearchRadius); Vector AITAC_GetRandomBuildHintInLocation(const unsigned int StructureType, const Vector SearchLocation, const float SearchRadius);
@ -208,6 +215,26 @@ void AITAC_UpdateSquads();
void AITAC_ManageSquads(); void AITAC_ManageSquads();
void AITAC_ClearSquads(); void AITAC_ClearSquads();
AvHAISquad* AITAC_GetSquadForObjective(AvHAIPlayer* pBot, edict_t* TaskTarget, BotTaskType ObjectiveType); AvHAISquad* AITAC_GetSquadForObjective(AvHAIPlayer* pBot, edict_t* TaskTarget, BotTaskType ObjectiveType);
AvHAISquad* AITAC_GetSquadForObjective(AvHAIPlayer* pBot, Vector TaskLocation, BotTaskType ObjectiveType);
Vector AITAC_GetGatherLocationForSquad(AvHAISquad* Squad); Vector AITAC_GetGatherLocationForSquad(AvHAISquad* Squad);
Vector AITAC_FindNewTeamRelocationPoint(AvHTeamNumber Team);
bool AITAC_IsRelocationPointStillValid(AvHTeamNumber RelocationTeam, Vector RelocationPoint);
bool AITAC_IsRelocationCompleted(AvHTeamNumber RelocationTeam, Vector RelocationPoint);
bool AITAC_IsRelocationAtStartEnabled();
void AITAC_DetermineRelocationEnabled();
bool AITAC_IsMarineBaseValid(AvHAIMarineBase* Base);
void AITAC_ManageActiveMarineBases();
void AITAC_AddNewBase(AvHTeamNumber Team, Vector NewBaseLocation, MarineBaseType NewBaseType);
bool AITAC_CanBuildOutBase(const AvHAIMarineBase* Base);
bool AITAC_CanBuildOutMainBase(const AvHAIMarineBase* Base);
bool AITAC_CanBuildOutOutpost(const AvHAIMarineBase* Base);
bool AITAC_CanBuildOutSiege(const AvHAIMarineBase* Base);
bool AITAC_CanBuildOutGuardPost(const AvHAIMarineBase* Base);
vector<AvHAIMarineBase>& AITAC_GetTeamBases(AvHTeamNumber Team);
#endif #endif

View file

@ -395,8 +395,8 @@ bool AITASK_IsTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
return AITASK_IsAlienSecureHiveTaskStillValid(pBot, Task); return AITASK_IsAlienSecureHiveTaskStillValid(pBot, Task);
} }
} }
case TASK_ATTACK_BASE: case TASK_ASSAULT_MARINE_BASE:
return true; return AITASK_IsAssaultMarineBaseTaskStillValid(pBot, Task);
case TASK_DEFEND: case TASK_DEFEND:
return AITASK_IsDefendTaskStillValid(pBot, Task); return AITASK_IsDefendTaskStillValid(pBot, Task);
case TASK_WELD: case TASK_WELD:
@ -441,11 +441,11 @@ bool AITASK_IsWeldTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
{ {
if (FNullEnt(Task->TaskSecondaryTarget)) if (FNullEnt(Task->TaskSecondaryTarget))
{ {
AvHAIDroppedItem* NearestWelder = AITAC_FindClosestItemToLocation(pBot->Edict->v.origin, DEPLOYABLE_ITEM_WELDER, pBot->Player->GetTeam(), pBot->BotNavInfo.NavProfile.ReachabilityFlag, 0.0f, 0.0f, true); AvHAIDroppedItem NearestWelder = AITAC_FindClosestItemToLocation(pBot->Edict->v.origin, DEPLOYABLE_ITEM_WELDER, pBot->Player->GetTeam(), pBot->BotNavInfo.NavProfile.ReachabilityFlag, 0.0f, 0.0f, true);
if (NearestWelder) if (NearestWelder.IsValid())
{ {
Task->TaskSecondaryTarget = NearestWelder->edict; Task->TaskSecondaryTarget = NearestWelder.edict;
} }
else else
{ {
@ -875,12 +875,32 @@ bool AITASK_IsReinforceStructureTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTas
if (bActiveHiveWithoutTechExists) { return true; } if (bActiveHiveWithoutTechExists) { return true; }
DeployableSearchFilter StructureFilter; int NumMissing = 0;
StructureFilter.DeployableTypes = STRUCTURE_ALIEN_OFFENCECHAMBER | ALIEN_BUILD_DEFENSE_CHAMBER | ALIEN_BUILD_MOVEMENT_CHAMBER | ALIEN_BUILD_SENSORY_CHAMBER; AvHAIDeployableStructureType MissingStructure = AITAC_GetNextMissingUpgradeChamberForTeam(BotTeam, NumMissing);
StructureFilter.MaxSearchRadius = (IsEdictHive(Task->TaskTarget)) ? UTIL_MetresToGoldSrcUnits(10.0f) : UTIL_MetresToGoldSrcUnits(5.0f);
StructureFilter.DeployableTeam = pBot->Player->GetTeam();
vector<AvHAIBuildableStructure> AllNearbyStructures = AITAC_FindAllDeployables(Task->TaskTarget->v.origin, &StructureFilter); if (MissingStructure != STRUCTURE_NONE) { return true; }
Vector ReinforceLocation = Task->TaskTarget->v.origin;
float SearchRadius = UTIL_MetresToGoldSrcUnits(5.0f);
if (IsEdictHive(Task->TaskTarget))
{
AvHAIHiveDefinition* HiveToReinforce = AITAC_GetHiveFromEdict(Task->TaskTarget);
if (HiveToReinforce)
{
ReinforceLocation = HiveToReinforce->FloorLocation;
}
SearchRadius = UTIL_MetresToGoldSrcUnits(10.0f);
}
DeployableSearchFilter StructureFilter;
StructureFilter.DeployableTypes = SEARCH_ALL_ALIEN_STRUCTURES;
StructureFilter.MaxSearchRadius = SearchRadius * 1.25f;
StructureFilter.DeployableTeam = BotTeam;
vector<AvHAIBuildableStructure> AllNearbyStructures = AITAC_FindAllDeployables(ReinforceLocation, &StructureFilter);
bool bUnfinishedStructureExists = false; bool bUnfinishedStructureExists = false;
int NumOffenceChambers = 0; int NumOffenceChambers = 0;
@ -916,11 +936,11 @@ bool AITASK_IsReinforceStructureTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTas
// 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 // 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
if (NumOffenceChambers < 2 if (NumOffenceChambers < 3
|| (AITAC_TeamHiveWithTechExists(BotTeam, ALIEN_BUILD_DEFENSE_CHAMBER) && NumDefenceChambers < 2) || (AITAC_TeamHiveWithTechExists(BotTeam, ALIEN_BUILD_DEFENSE_CHAMBER) && NumDefenceChambers < 2)
|| (AITAC_TeamHiveWithTechExists(BotTeam, ALIEN_BUILD_MOVEMENT_CHAMBER) && NumMovementChambers < 1) || (AITAC_TeamHiveWithTechExists(BotTeam, ALIEN_BUILD_MOVEMENT_CHAMBER) && NumMovementChambers < 1)
|| (AITAC_TeamHiveWithTechExists(BotTeam, ALIEN_BUILD_SENSORY_CHAMBER) && NumSensoryChambers < 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))) || (IsPlayerGorge(pBot->Edict) && bUnfinishedStructureExists && vDist2DSq(pBot->Edict->v.origin, ReinforceLocation) <= sqrf(UTIL_MetresToGoldSrcUnits(10.0f)))
) { return true; } ) { return true; }
// Otherwise, are there any enemy structures lying around we could clear out? // Otherwise, are there any enemy structures lying around we could clear out?
@ -934,9 +954,9 @@ bool AITASK_IsReinforceStructureTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTas
EnemyStuff.DeployableTeam = AIMGR_GetEnemyTeam(BotTeam); EnemyStuff.DeployableTeam = AIMGR_GetEnemyTeam(BotTeam);
EnemyStuff.ReachabilityTeam = BotTeam; EnemyStuff.ReachabilityTeam = BotTeam;
EnemyStuff.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; EnemyStuff.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
EnemyStuff.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); EnemyStuff.MaxSearchRadius = SearchRadius;
return AITAC_DeployableExistsAtLocation(Task->TaskTarget->v.origin, &EnemyStuff); return AITAC_DeployableExistsAtLocation(ReinforceLocation, &EnemyStuff);
} }
bool AITASK_IsAlienSecureHiveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) bool AITASK_IsAlienSecureHiveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
@ -961,6 +981,24 @@ bool AITASK_IsAlienSecureHiveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask*
return AITAC_DeployableExistsAtLocation(HiveToSecure->FloorLocation, &EnemyStuff); return AITAC_DeployableExistsAtLocation(HiveToSecure->FloorLocation, &EnemyStuff);
} }
bool AITASK_IsAssaultMarineBaseTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
{
AvHTeamNumber BotTeam = pBot->Player->GetTeam();
AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam);
if (AIMGR_GetTeamType(EnemyTeam) != AVH_CLASS_TYPE_MARINE) { return false; }
DeployableSearchFilter StructureFilter;
StructureFilter.DeployableTeam = EnemyTeam;
StructureFilter.DeployableTypes = (STRUCTURE_MARINE_OBSERVATORY | STRUCTURE_MARINE_ARMSLAB | STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY | STRUCTURE_MARINE_INFANTRYPORTAL | STRUCTURE_MARINE_COMMCHAIR | STRUCTURE_MARINE_PROTOTYPELAB);
StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
StructureFilter.ReachabilityTeam = BotTeam;
StructureFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f);
return AITAC_DeployableExistsAtLocation(Task->TaskLocation, &StructureFilter);
}
bool AITASK_IsMarineSecureHiveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) bool AITASK_IsMarineSecureHiveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
{ {
if (!Task || FNullEnt(Task->TaskTarget) || IsPlayerAlien(pBot->Edict)) { return false; } if (!Task || FNullEnt(Task->TaskTarget) || IsPlayerAlien(pBot->Edict)) { return false; }
@ -977,6 +1015,12 @@ bool AITASK_IsMarineSecureHiveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask*
AvHTeamNumber BotTeam = pBot->Player->GetTeam(); AvHTeamNumber BotTeam = pBot->Player->GetTeam();
// We've relocated to this hive, no need to secure now
if (vDist2DSq(AITAC_GetTeamStartingLocation(BotTeam), HiveToSecure->FloorLocation) < sqrf(UTIL_MetresToGoldSrcUnits(10.0f)))
{
return false;
}
bool bPhaseGatesAvailable = AITAC_PhaseGatesAvailable(BotTeam); bool bPhaseGatesAvailable = AITAC_PhaseGatesAvailable(BotTeam);
bool bHasPhaseGate = false; bool bHasPhaseGate = false;
@ -1246,9 +1290,9 @@ void BotProgressMineStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
} }
} }
float DistToPlaceLocation = vDist2DSq(pBot->Edict->v.origin, Task->TaskLocation); float DistToPlaceLocation = vDist3DSq(pBot->Edict->v.origin, Task->TaskLocation);
if (DistToPlaceLocation < sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) if (DistToPlaceLocation < sqrf(50.0f))
{ {
pBot->DesiredCombatWeapon = WEAPON_MARINE_MINES; pBot->DesiredCombatWeapon = WEAPON_MARINE_MINES;
} }
@ -1325,15 +1369,23 @@ void BotProgressReinforceStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
{ {
if (FNullEnt(Task->TaskTarget)) { return; } if (FNullEnt(Task->TaskTarget)) { return; }
if (Task->ActiveBuildInfo.BuildStatus == BUILD_ATTEMPT_PENDING) { return; } if (Task->ActiveBuildInfo.BuildStatus == BUILD_ATTEMPT_PENDING)
{
pBot->Impulse = 0;
return;
}
// We had a go, whether it succeeded or not we should try a new location // We had a go, whether it succeeded or not we should try a new location
if (Task->ActiveBuildInfo.BuildStatus == BUILD_ATTEMPT_FAILED || Task->ActiveBuildInfo.BuildStatus == BUILD_ATTEMPT_SUCCESS) if (Task->ActiveBuildInfo.BuildStatus == BUILD_ATTEMPT_FAILED || Task->ActiveBuildInfo.BuildStatus == BUILD_ATTEMPT_SUCCESS)
{ {
pBot->Impulse = 0;
Task->TaskStartedTime = gpGlobals->time;
Task->TaskLocation = ZERO_VECTOR; Task->TaskLocation = ZERO_VECTOR;
Task->ActiveBuildInfo.BuildStatus = BUILD_ATTEMPT_NONE; Task->ActiveBuildInfo.BuildStatus = BUILD_ATTEMPT_NONE;
} }
if (gpGlobals->time - Task->TaskStartedTime < 0.5f) { return; }
AvHTeamNumber BotTeam = pBot->Player->GetTeam(); AvHTeamNumber BotTeam = pBot->Player->GetTeam();
Vector ReinforceLocation = UTIL_ProjectPointToNavmesh(UTIL_GetEntityGroundLocation(Task->TaskTarget), pBot->BotNavInfo.NavProfile); Vector ReinforceLocation = UTIL_ProjectPointToNavmesh(UTIL_GetEntityGroundLocation(Task->TaskTarget), pBot->BotNavInfo.NavProfile);
@ -1355,7 +1407,7 @@ void BotProgressReinforceStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
DeployableSearchFilter StructureFilter; DeployableSearchFilter StructureFilter;
StructureFilter.DeployableTeam = BotTeam; StructureFilter.DeployableTeam = BotTeam;
StructureFilter.MaxSearchRadius = SearchRadius; StructureFilter.MaxSearchRadius = SearchRadius * 1.25f;
StructureFilter.DeployableTypes = STRUCTURE_ALIEN_OFFENCECHAMBER; StructureFilter.DeployableTypes = STRUCTURE_ALIEN_OFFENCECHAMBER;
int NumOCs = AITAC_GetNumDeployablesNearLocation(ReinforceLocation, &StructureFilter); int NumOCs = AITAC_GetNumDeployablesNearLocation(ReinforceLocation, &StructureFilter);
@ -1410,6 +1462,14 @@ void BotProgressReinforceStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
} }
} }
if (NextStructure == STRUCTURE_NONE)
{
int NumMissing = 0;
AvHAIDeployableStructureType MissingStructure = AITAC_GetNextMissingUpgradeChamberForTeam(BotTeam, NumMissing);
NextStructure = MissingStructure;
}
if (NextStructure != STRUCTURE_NONE) if (NextStructure != STRUCTURE_NONE)
{ {
if (vIsZero(Task->TaskLocation)) if (vIsZero(Task->TaskLocation))
@ -1756,7 +1816,7 @@ void BotProgressAttackTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
AvHTurret* TurretRef = dynamic_cast<AvHTurret*>(CBaseEntity::Instance(Task->TaskTarget)); AvHTurret* TurretRef = dynamic_cast<AvHTurret*>(CBaseEntity::Instance(Task->TaskTarget));
if (TurretRef && TurretRef->GetIsValidTarget(pBot->Player)) if (TurretRef && TurretRef->GetIsValidTarget(pBot->Player) && !vIsZero(GetVisiblePointOnPlayerFromObserver(Task->TaskTarget, pBot->Edict)))
{ {
if (vIsZero(pBot->LastSafeLocation)) if (vIsZero(pBot->LastSafeLocation))
{ {
@ -1764,13 +1824,33 @@ void BotProgressAttackTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
} }
MoveTo(pBot, pBot->LastSafeLocation, MOVESTYLE_NORMAL); MoveTo(pBot, pBot->LastSafeLocation, MOVESTYLE_NORMAL);
return; BotLookAt(pBot, Task->TaskTarget);
} }
return;
} }
} }
BotAttackResult AttackResult = PerformAttackLOSCheck(pBot, Weapon, Task->TaskTarget); BotAttackResult AttackResult = PerformAttackLOSCheck(pBot, Weapon, Task->TaskTarget);
if (AttackResult == ATTACK_BLOCKED)
{
TraceResult hit;
Vector StartTrace = pBot->CurrentEyePosition;
Vector AttackDir = UTIL_GetVectorNormal(UTIL_GetCentreOfEntity(Task->TaskTarget) - StartTrace);
Vector EndTrace = pBot->CurrentEyePosition + (AttackDir * UTIL_MetresToGoldSrcUnits(50.0f));
UTIL_TraceLine(StartTrace, EndTrace, dont_ignore_monsters, dont_ignore_glass, pBot->Edict->v.pContainingEntity, &hit);
if (!FNullEnt(hit.pHit) && hit.pHit->v.team == AIMGR_GetEnemyTeam(pBot->Player->GetTeam()) && vDist2DSq(hit.pHit->v.origin, Task->TaskTarget->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(2.0f)))
{
AttackResult = ATTACK_SUCCESS;
}
}
if (AttackResult == ATTACK_SUCCESS) if (AttackResult == ATTACK_SUCCESS)
{ {
// If we were ducking before then keep ducking // If we were ducking before then keep ducking
@ -1805,17 +1885,14 @@ void BotProgressAttackTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
Vector RightDir = UTIL_GetCrossProduct(EnemyOrientation, UP_VECTOR); Vector RightDir = UTIL_GetCrossProduct(EnemyOrientation, UP_VECTOR);
pBot->desiredMovementDir = (pBot->BotNavInfo.bZig) ? UTIL_GetVectorNormal2D(RightDir) : UTIL_GetVectorNormal2D(-RightDir); Vector EvasiveDir = GetZigZagDirection(pBot, Task->TaskTarget, nullptr);
// Let's get ziggy with it if (!vIsZero(EvasiveDir))
if (gpGlobals->time > pBot->BotNavInfo.NextZigTime)
{ {
pBot->BotNavInfo.bZig = !pBot->BotNavInfo.bZig; pBot->desiredMovementDir = EvasiveDir;
pBot->BotNavInfo.NextZigTime = gpGlobals->time + frandrange(0.5f, 1.0f);
}
BotMovementInputs(pBot); BotMovementInputs(pBot);
} }
}
return; return;
} }
@ -1910,9 +1987,34 @@ void BotProgressTakeCommandTask(AvHAIPlayer* pBot)
// Don't take command if we already have a commander // Don't take command if we already have a commander
if (pBot->Player->GetCommander()) { return; } if (pBot->Player->GetCommander()) { return; }
edict_t* CommChair = AITAC_GetCommChair(pBot->Player->GetTeam()); edict_t* CommChair = nullptr;
if (!CommChair) { return; } AvHTeamNumber BotTeam = pBot->Player->GetTeam();
Vector RelocationPoint = pBot->RelocationSpot;
if (!vIsZero(RelocationPoint) && AITAC_IsRelocationCompleted(BotTeam, RelocationPoint))
{
DeployableSearchFilter RelocationChairFilter;
RelocationChairFilter.DeployableTeam = BotTeam;
RelocationChairFilter.DeployableTypes = STRUCTURE_MARINE_COMMCHAIR;
RelocationChairFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
RelocationChairFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
RelocationChairFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f);
AvHAIBuildableStructure RelocationChair = AITAC_FindClosestDeployableToLocation(RelocationPoint, &RelocationChairFilter);
if (RelocationChair.IsValid())
{
CommChair = RelocationChair.edict;
}
}
else
{
CommChair = AITAC_GetCommChair(BotTeam);
}
if (FNullEnt(CommChair)) { return; }
float DistFromChair = vDist2DSq(pBot->Edict->v.origin, CommChair->v.origin); float DistFromChair = vDist2DSq(pBot->Edict->v.origin, CommChair->v.origin);
@ -2006,7 +2108,7 @@ void BotProgressEvolveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
{ {
if (FNullEnt(Task->TaskTarget)) if (FNullEnt(Task->TaskTarget))
{ {
Task->TaskLocation = FindClosestNavigablePointToDestination(BaseNavProfiles[ONOS_BASE_NAV_PROFILE], AITAC_GetTeamStartingLocation(pBot->Player->GetTeam()), UTIL_GetEntityGroundLocation(Task->TaskTarget), UTIL_MetresToGoldSrcUnits(10.0f)); Task->TaskLocation = FindClosestNavigablePointToDestination(BaseNavProfiles[ONOS_BASE_NAV_PROFILE], AITAC_GetTeamStartingLocation(pBot->Player->GetTeam()), pBot->CurrentFloorPosition, UTIL_MetresToGoldSrcUnits(10.0f));
if (vIsZero(Task->TaskLocation)) if (vIsZero(Task->TaskLocation))
{ {
@ -2024,11 +2126,11 @@ void BotProgressEvolveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
} }
else else
{ {
Task->TaskLocation = FindClosestNavigablePointToDestination(BaseNavProfiles[ONOS_BASE_NAV_PROFILE], pBot->CurrentFloorPosition, UTIL_GetEntityGroundLocation(Task->TaskTarget), UTIL_MetresToGoldSrcUnits(10.0f)); Task->TaskLocation = FindClosestNavigablePointToDestination(BaseNavProfiles[ONOS_BASE_NAV_PROFILE], AITAC_GetTeamStartingLocation(pBot->Player->GetTeam()), UTIL_GetEntityGroundLocation(Task->TaskTarget), UTIL_MetresToGoldSrcUnits(10.0f));
if (vIsZero(Task->TaskLocation)) if (vIsZero(Task->TaskLocation))
{ {
Task->TaskLocation = UTIL_GetRandomPointOnNavmeshInRadius(BaseNavProfiles[STRUCTURE_BASE_NAV_PROFILE], Task->TaskLocation, UTIL_MetresToGoldSrcUnits(5.0f)); Task->TaskLocation = UTIL_GetRandomPointOnNavmeshInRadius(BaseNavProfiles[STRUCTURE_BASE_NAV_PROFILE], UTIL_GetEntityGroundLocation(Task->TaskTarget), UTIL_MetresToGoldSrcUnits(10.0f));
} }
} }
} }
@ -2106,6 +2208,7 @@ void AlienProgressBuildTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
// We tried and failed to place the structure // We tried and failed to place the structure
if (Task->ActiveBuildInfo.BuildStatus == BUILD_ATTEMPT_PENDING) if (Task->ActiveBuildInfo.BuildStatus == BUILD_ATTEMPT_PENDING)
{ {
pBot->Impulse = 0;
return; return;
} }
@ -2119,6 +2222,8 @@ void AlienProgressBuildTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
{ {
edict_t* LinkedEdict = Task->ActiveBuildInfo.LinkedStructure->edict; edict_t* LinkedEdict = Task->ActiveBuildInfo.LinkedStructure->edict;
Task->TaskTarget = LinkedEdict;
if (UTIL_StructureIsFullyBuilt(LinkedEdict)) { return; } if (UTIL_StructureIsFullyBuilt(LinkedEdict)) { return; }
if (IsPlayerInUseRange(pBot->Edict, LinkedEdict)) if (IsPlayerInUseRange(pBot->Edict, LinkedEdict))
@ -2186,7 +2291,11 @@ void BotAlienPlaceChamber(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, AvHAIDeploya
{ {
if (vIsZero(Task->TaskLocation) || DesiredStructure == STRUCTURE_NONE) { return; } if (vIsZero(Task->TaskLocation) || DesiredStructure == STRUCTURE_NONE) { return; }
if (Task->ActiveBuildInfo.BuildStatus == BUILD_ATTEMPT_PENDING) { return; } if (Task->ActiveBuildInfo.BuildStatus == BUILD_ATTEMPT_PENDING)
{
pBot->Impulse = 0;
return;
}
float DistFromBuildLocation = vDist2DSq(pBot->Edict->v.origin, Task->TaskLocation); float DistFromBuildLocation = vDist2DSq(pBot->Edict->v.origin, Task->TaskLocation);
@ -2577,6 +2686,9 @@ void BotProgressTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
} }
} }
break; break;
case TASK_ASSAULT_MARINE_BASE:
BotProgressAssaultMarineBaseTask(pBot, Task);
break;
default: default:
break; break;
@ -2594,11 +2706,11 @@ void BotProgressWeldTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
} }
else else
{ {
AvHAIDroppedItem* Welder = AITAC_FindClosestItemToLocation(pBot->Edict->v.origin, DEPLOYABLE_ITEM_WELDER, pBot->Player->GetTeam(), pBot->BotNavInfo.NavProfile.ReachabilityFlag, 0.0f, 0.0f, true); AvHAIDroppedItem Welder = AITAC_FindClosestItemToLocation(pBot->Edict->v.origin, DEPLOYABLE_ITEM_WELDER, pBot->Player->GetTeam(), pBot->BotNavInfo.NavProfile.ReachabilityFlag, 0.0f, 0.0f, true);
if (Welder) if (Welder.IsValid())
{ {
Task->TaskSecondaryTarget = Welder->edict; Task->TaskSecondaryTarget = Welder.edict;
} }
} }
@ -2698,6 +2810,9 @@ void AlienProgressSecureHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
if (PhaseGate.IsValid()) if (PhaseGate.IsValid())
{ {
// If the phase gate is next to an electrified structure, and we are a skulk or lerk, then attack
// The electrified structure instead. I might change this to avoid it altogether, but there's nothing
// wrong with trying to chip away at the TF if you're a skulk
if (bAvoidElectrified) if (bAvoidElectrified)
{ {
EnemyStuffFilter.DeployableTypes = SEARCH_ALL_STRUCTURES; EnemyStuffFilter.DeployableTypes = SEARCH_ALL_STRUCTURES;
@ -2798,6 +2913,8 @@ void MarineProgressSecureHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
bool bKeyStructureBuilt = false; bool bKeyStructureBuilt = false;
AvHAIBuildableStructure KeyOutpostStructure;
AvHAIBuildableStructure StructureToBuild; AvHAIBuildableStructure StructureToBuild;
float MinDist = 0.0f; float MinDist = 0.0f;
@ -2807,6 +2924,7 @@ void MarineProgressSecureHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
if ((ThisStructure.StructureStatusFlags & STRUCTURE_STATUS_COMPLETED) && (ThisStructure.StructureType & (STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY | STRUCTURE_MARINE_PHASEGATE))) if ((ThisStructure.StructureStatusFlags & STRUCTURE_STATUS_COMPLETED) && (ThisStructure.StructureType & (STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY | STRUCTURE_MARINE_PHASEGATE)))
{ {
KeyOutpostStructure = ThisStructure;
bKeyStructureBuilt = true; bKeyStructureBuilt = true;
} }
@ -2869,10 +2987,10 @@ void MarineProgressSecureHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
{ {
DeployableSearchFilter EnemyStructures; DeployableSearchFilter EnemyStructures;
EnemyStructures.DeployableTypes = SEARCH_ALL_STRUCTURES; EnemyStructures.DeployableTypes = SEARCH_ALL_STRUCTURES;
EnemyStructures.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f);
EnemyStructures.DeployableTeam = EnemyTeam; EnemyStructures.DeployableTeam = EnemyTeam;
EnemyStructures.ReachabilityTeam = BotTeam; EnemyStructures.ReachabilityTeam = BotTeam;
EnemyStructures.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; EnemyStructures.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
EnemyStructures.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f);
AvHAIBuildableStructure EnemyStructure = AITAC_FindClosestDeployableToLocation(Hive->FloorLocation, &EnemyStructures); AvHAIBuildableStructure EnemyStructure = AITAC_FindClosestDeployableToLocation(Hive->FloorLocation, &EnemyStructures);
@ -2883,8 +3001,9 @@ void MarineProgressSecureHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
} }
} }
Vector OutpostLocation = (KeyOutpostStructure.IsValid()) ? KeyOutpostStructure.Location : Task->TaskLocation;
BotGuardLocation(pBot, Task->TaskLocation); BotGuardLocation(pBot, OutpostLocation);
} }
@ -2978,12 +3097,129 @@ void MarineProgressCapResNodeTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
// 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 // 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) if (Task->TaskLength == 0.0f)
{ {
const AvHAIResourceNode* ResNode = AITAC_GetNearestResourceNodeToLocation(Task->TaskLocation);
// If we're not at our destination yet, go there
if (vDist2DSq(pBot->Edict->v.origin, ResNode->Location) > sqrf(UTIL_MetresToGoldSrcUnits(1.0f)))
{
MoveTo(pBot, ResNode->Location, MOVESTYLE_NORMAL);
return;
}
Task->TaskStartedTime = gpGlobals->time; Task->TaskStartedTime = gpGlobals->time;
Task->TaskLength = 30.0f; Task->TaskLength = 30.0f;
} }
BotGuardLocation(pBot, Task->TaskLocation); BotGuardLocation(pBot, Task->TaskLocation);
} }
}
void BotProgressAssaultMarineBaseTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
{
AvHTeamNumber BotTeam = pBot->Player->GetTeam();
AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam);
if (AIMGR_GetTeamType(BotTeam) == AVH_CLASS_TYPE_ALIEN)
{
AvHAISquad* ActiveSquad = AITAC_GetSquadForObjective(pBot, Task->TaskLocation, Task->TaskType);
if (ActiveSquad && !ActiveSquad->bExecuteObjective && !vIsZero(ActiveSquad->SquadGatherLocation))
{
BotGuardLocation(pBot, ActiveSquad->SquadGatherLocation);
return;
}
}
DeployableSearchFilter EnemyStructureFilter;
EnemyStructureFilter.DeployableTeam = EnemyTeam;
EnemyStructureFilter.ReachabilityTeam = BotTeam;
EnemyStructureFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
EnemyStructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f);
EnemyStructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
// First go for any TFs, to eliminate defences
EnemyStructureFilter.DeployableTypes = (STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY);
vector<AvHAIBuildableStructure> Structures = AITAC_FindAllDeployables(Task->TaskLocation, &EnemyStructureFilter);
for (auto it = Structures.begin(); it != Structures.end(); it++)
{
float DistToStructure = vDist2D(pBot->Edict->v.origin, it->Location) - 5.0f;
if (AITAC_GetNumPlayersOfTeamInArea(BotTeam, it->Location, DistToStructure, false, pBot->Edict, AVH_USER3_NONE) < 2)
{
BotAttackNonPlayerTarget(pBot, it->edict);
return;
}
}
// First go for any observatory, to prevent beacon
EnemyStructureFilter.DeployableTypes = STRUCTURE_MARINE_OBSERVATORY;
Structures = AITAC_FindAllDeployables(Task->TaskLocation, &EnemyStructureFilter);
for (auto it = Structures.begin(); it != Structures.end(); it++)
{
float DistToStructure = vDist2D(pBot->Edict->v.origin, it->Location) - 5.0f;
if (AITAC_GetNumPlayersOfTeamInArea(BotTeam, it->Location, DistToStructure, false, pBot->Edict, AVH_USER3_NONE) < 2)
{
BotAttackNonPlayerTarget(pBot, it->edict);
return;
}
}
// Next go for any arms lab, to weaken the marines
EnemyStructureFilter.DeployableTypes = STRUCTURE_MARINE_ARMSLAB;
Structures = AITAC_FindAllDeployables(Task->TaskLocation, &EnemyStructureFilter);
for (auto it = Structures.begin(); it != Structures.end(); it++)
{
float DistToStructure = vDist2D(pBot->Edict->v.origin, it->Location) - 5.0f;
if (AITAC_GetNumPlayersOfTeamInArea(BotTeam, it->Location, DistToStructure, false, pBot->Edict, AVH_USER3_NONE) < 2)
{
BotAttackNonPlayerTarget(pBot, it->edict);
return;
}
}
// Next go for any infantry portals, to prevent reinforcements
EnemyStructureFilter.DeployableTypes = STRUCTURE_MARINE_INFANTRYPORTAL;
Structures = AITAC_FindAllDeployables(Task->TaskLocation, &EnemyStructureFilter);
for (auto it = Structures.begin(); it != Structures.end(); it++)
{
float DistToStructure = vDist2D(pBot->Edict->v.origin, it->Location) - 5.0f;
if (AITAC_GetNumPlayersOfTeamInArea(BotTeam, it->Location, DistToStructure, false, pBot->Edict, AVH_USER3_NONE) < 2)
{
BotAttackNonPlayerTarget(pBot, it->edict);
return;
}
}
// Finally, any other structures
EnemyStructureFilter.DeployableTypes = SEARCH_ALL_STRUCTURES;
Structures = AITAC_FindAllDeployables(Task->TaskLocation, &EnemyStructureFilter);
for (auto it = Structures.begin(); it != Structures.end(); it++)
{
float DistToStructure = vDist2D(pBot->Edict->v.origin, it->Location) - 5.0f;
if (AITAC_GetNumPlayersOfTeamInArea(BotTeam, it->Location, DistToStructure, false, pBot->Edict, AVH_USER3_NONE) < 2)
{
BotAttackNonPlayerTarget(pBot, it->edict);
return;
}
}
// nothing to attack, just hang around
BotGuardLocation(pBot, Task->TaskLocation);
} }
@ -3353,9 +3589,9 @@ void AITASK_SetPickupTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, edict_t* Tar
return; return;
} }
AvHAIDroppedItem* ItemToPickup = AITAC_GetDroppedItemRefFromEdict(Target); AvHAIDroppedItem ItemToPickup = AITAC_GetDroppedItemRefFromEdict(Target);
if (!ItemToPickup) if (!ItemToPickup.IsValid())
{ {
AITASK_ClearBotTask(pBot, Task); AITASK_ClearBotTask(pBot, Task);
return; return;
@ -3369,7 +3605,7 @@ void AITASK_SetPickupTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, edict_t* Tar
return; return;
} }
switch (ItemToPickup->ItemType) switch (ItemToPickup.ItemType)
{ {
case DEPLOYABLE_ITEM_AMMO: case DEPLOYABLE_ITEM_AMMO:
Task->TaskType = TASK_GET_AMMO; Task->TaskType = TASK_GET_AMMO;
@ -3449,16 +3685,16 @@ void AITASK_SetWeldTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, edict_t* Targe
if (!PlayerHasWeapon(pBot->Player, WEAPON_MARINE_WELDER)) if (!PlayerHasWeapon(pBot->Player, WEAPON_MARINE_WELDER))
{ {
AvHAIDroppedItem* NearestWelder = AITAC_FindClosestItemToLocation(pBot->Edict->v.origin, DEPLOYABLE_ITEM_WELDER, pBot->Player->GetTeam(), pBot->BotNavInfo.NavProfile.ReachabilityFlag, 0.0f, 0.0f, true); AvHAIDroppedItem NearestWelder = AITAC_FindClosestItemToLocation(pBot->Edict->v.origin, DEPLOYABLE_ITEM_WELDER, pBot->Player->GetTeam(), pBot->BotNavInfo.NavProfile.ReachabilityFlag, 0.0f, 0.0f, true);
if (!NearestWelder) if (!NearestWelder.IsValid())
{ {
AITASK_ClearBotTask(pBot, Task); AITASK_ClearBotTask(pBot, Task);
return; return;
} }
else else
{ {
Task->TaskSecondaryTarget = NearestWelder->edict; Task->TaskSecondaryTarget = NearestWelder.edict;
} }
} }
@ -3570,7 +3806,13 @@ void AITASK_SetMoveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, const Vector L
void AITASK_SetBuildTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, const AvHAIDeployableStructureType StructureType, const Vector Location, const bool bIsUrgent) void AITASK_SetBuildTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, const AvHAIDeployableStructureType StructureType, const Vector Location, const bool bIsUrgent)
{ {
if (Task->TaskType == TASK_BUILD && Task->StructureType == StructureType && vDist2DSq(Task->TaskLocation, Location) < sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) { return; } if (Task->TaskType == TASK_BUILD && Task->StructureType == StructureType && vDist2DSq(Task->TaskLocation, Location) < sqrf(UTIL_MetresToGoldSrcUnits(1.0f)))
{
Task->bTaskIsUrgent = bIsUrgent;
return;
}
if (Task->ActiveBuildInfo.BuildStatus == BUILD_ATTEMPT_PENDING) { return; }
AITASK_ClearBotTask(pBot, Task); AITASK_ClearBotTask(pBot, Task);
@ -3789,6 +4031,8 @@ void AITASK_SetReinforceStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task,
return; return;
} }
if (Task->ActiveBuildInfo.BuildStatus == BUILD_ATTEMPT_PENDING) { return; }
AITASK_ClearBotTask(pBot, Task); AITASK_ClearBotTask(pBot, Task);
if (FNullEnt(Target) || Target->v.deadflag != DEAD_NO) { return; } if (FNullEnt(Target) || Target->v.deadflag != DEAD_NO) { return; }
@ -3848,6 +4092,19 @@ void AITASK_SetMineStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, edict
Task->bTaskIsUrgent = bIsUrgent; Task->bTaskIsUrgent = bIsUrgent;
Task->TaskLocation = UTIL_GetNextMinePosition2(Target); Task->TaskLocation = UTIL_GetNextMinePosition2(Target);
Task->StructureType = STRUCTURE_MARINE_DEPLOYEDMINE; Task->StructureType = STRUCTURE_MARINE_DEPLOYEDMINE;
}
void AITASK_SetAssaultMarineBaseTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, Vector BaseLocation, bool bIsUrgent)
{
if (Task->TaskType == TASK_ASSAULT_MARINE_BASE && vEquals(BaseLocation, Task->TaskLocation))
{
Task->bTaskIsUrgent = bIsUrgent;
return;
}
AITASK_ClearBotTask(pBot, Task);
Task->TaskType = TASK_ASSAULT_MARINE_BASE;
Task->TaskLocation = BaseLocation;
Task->bTaskIsUrgent = bIsUrgent;
} }

View file

@ -47,6 +47,8 @@ bool AITASK_IsReinforceStructureTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTas
bool AITASK_IsMarineSecureHiveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); bool AITASK_IsMarineSecureHiveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
bool AITASK_IsAlienSecureHiveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); bool AITASK_IsAlienSecureHiveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
bool AITASK_IsAssaultMarineBaseTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
bool AITASK_IsAlienGetHealthTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); bool AITASK_IsAlienGetHealthTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
bool AITASK_IsAlienHealTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); bool AITASK_IsAlienHealTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
@ -67,6 +69,7 @@ void AITASK_SetReinforceStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task,
void AITASK_SetReinforceStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, edict_t* Target, const AvHAIDeployableStructureType FirstStructureType, bool bIsUrgent); void AITASK_SetReinforceStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, edict_t* Target, const AvHAIDeployableStructureType FirstStructureType, bool bIsUrgent);
void AITASK_SetSecureHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, edict_t* Target, const Vector WaitLocation, bool bIsUrgent); void AITASK_SetSecureHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, edict_t* Target, const Vector WaitLocation, bool bIsUrgent);
void AITASK_SetMineStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, edict_t* Target, bool bIsUrgent); void AITASK_SetMineStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, edict_t* Target, bool bIsUrgent);
void AITASK_SetAssaultMarineBaseTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, Vector BaseLocation, bool bIsUrgent);
void AITASK_SetWeldTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, edict_t* Target, const bool bIsUrgent); void AITASK_SetWeldTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, edict_t* Target, const bool bIsUrgent);
void AITASK_SetPickupTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, edict_t* Target, const bool bIsUrgent); void AITASK_SetPickupTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, edict_t* Target, const bool bIsUrgent);
void AITASK_SetGetHealthTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, edict_t* HealingSource, const bool bIsUrgent); void AITASK_SetGetHealthTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, edict_t* HealingSource, const bool bIsUrgent);
@ -90,6 +93,8 @@ void MarineProgressBuildTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
void MarineProgressCapResNodeTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); void MarineProgressCapResNodeTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
void BotProgressWeldTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); void BotProgressWeldTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
void BotProgressAssaultMarineBaseTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
void AIPlayerBuildStructure(AvHAIPlayer* pBot, edict_t* BuildTarget); void AIPlayerBuildStructure(AvHAIPlayer* pBot, edict_t* BuildTarget);
void MarineProgressSecureHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); void MarineProgressSecureHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);