diff --git a/main/source/dlls/game.cpp b/main/source/dlls/game.cpp index 044ca12e..af1e4aad 100644 --- a/main/source/dlls/game.cpp +++ b/main/source/dlls/game.cpp @@ -126,11 +126,11 @@ cvar_t avh_version = {kvVersion, "330", FCVAR_SERVER}; // AI Player Settings cvar_t avh_botsenabled = { kvBotsEnabled,"0", FCVAR_SERVER }; // Bots can be added to the server Y/N -cvar_t avh_botautomode = { kvBotAutoMode,"0", FCVAR_SERVER }; // Defines automated behaviour for adding/removing bots -cvar_t avh_botminplayers = { kvBotMinPlayers,"0", FCVAR_SERVER }; // If bots are enabled and auto mode == 2 then it will maintain this player count by adding/removing as needed +cvar_t avh_botautomode = { kvBotAutoMode,"0", FCVAR_SERVER }; // Defines automated behaviour for adding/removing bots. 0 = manual (must add via console), 1 = automatic (auto-fills teams), 2 = balance only (only keeps teams even) +cvar_t avh_botminplayers = { kvBotMinPlayers,"0", FCVAR_SERVER }; // If bots are enabled and auto mode == 1 then it will maintain this player count by adding/removing as needed cvar_t avh_botskill = { kvBotSkill,"0", FCVAR_SERVER }; // Sets the skill for the bots (0 = easiest, 3 = hardest) -cvar_t avh_botusemapdefaults = { kvBotUseMapDefaults,"0", FCVAR_SERVER }; // Defines automated behaviour for adding/removing bots -cvar_t avh_botcommandermode = { kvBotCommanderMode,"0", FCVAR_SERVER }; // 0 = Bots never command, 1 = Only if no humans on team, 2 = If nobody takes charge +cvar_t avh_botusemapdefaults = { kvBotUseMapDefaults,"0", FCVAR_SERVER }; // If bot auto mode == 1 then the min players will be taken from the config +cvar_t avh_botcommandermode = { kvBotCommanderMode,"0", FCVAR_SERVER }; // 0 = Bots never command, 1 = If nobody takes charge, 2 = Only if no humans on team //playtest cvars diff --git a/main/source/mod/AIPlayers/AvHAICommander.cpp b/main/source/mod/AIPlayers/AvHAICommander.cpp index 4179144a..56e7b241 100644 --- a/main/source/mod/AIPlayers/AvHAICommander.cpp +++ b/main/source/mod/AIPlayers/AvHAICommander.cpp @@ -74,7 +74,7 @@ bool AICOMM_DeployItem(AvHAIPlayer* pBot, const AvHAIDeployableItemType ItemToDe bool AICOMM_ResearchTech(AvHAIPlayer* pBot, AvHAIBuildableStructure* StructureToResearch, AvHMessageID Research) { - if (FNullEnt(StructureToResearch->edict)) { return false; } + if (!StructureToResearch || FNullEnt(StructureToResearch->edict)) { return false; } // Don't do anything if the structure is being recycled, or we DON'T want to recycle but the structure is already busy if (StructureToResearch->EntityRef->GetIsRecycling() || (Research != BUILD_RECYCLE && StructureToResearch->EntityRef->GetIsResearching())) { return false; } @@ -192,10 +192,15 @@ void AICOMM_AssignNewPlayerOrder(AvHAIPlayer* pBot, edict_t* Assignee, edict_t* NewOrder.Assignee = Assignee; NewOrder.OrderTarget = TargetEntity; NewOrder.OrderPurpose = OrderPurpose; - - AICOMM_IssueOrderForAssignedJob(pBot, &NewOrder); + NewOrder.LastReminderTime = 0.0f; + NewOrder.LastPlayerDistance = 0.0f; pBot->ActiveOrders.push_back(NewOrder); + + if (AICOMM_DoesPlayerOrderNeedReminder(pBot, &NewOrder)) + { + AICOMM_IssueOrderForAssignedJob(pBot, &NewOrder); + } } void AICOMM_IssueOrderForAssignedJob(AvHAIPlayer* pBot, ai_commander_order* Order) @@ -405,11 +410,10 @@ void AICOMM_UpdatePlayerOrders(AvHAIPlayer* pBot) if (SiegedHive) { int NumAssignedPlayers = AICOMM_GetNumPlayersAssignedToOrder(pBot, SiegedHive->HiveEntity->edict(), ORDERPURPOSE_SIEGE_HIVE); - int NumSiegingPlayers = AICOMM_GetNumPlayersAssignedToOrder(pBot, SiegedHive->HiveEntity->edict(), ORDERPURPOSE_SIEGE_HIVE); - if ((NumAssignedPlayers + NumSiegingPlayers) < DesiredPlayers) + if (NumAssignedPlayers < DesiredPlayers) { - for (int i = 0; i < DesiredPlayers - (NumAssignedPlayers + NumSiegingPlayers); i++) + for (int i = 0; i < (DesiredPlayers - NumAssignedPlayers); i++) { edict_t* NewAssignee = AICOMM_GetPlayerWithNoOrderNearestLocation(pBot, SiegedHive->FloorLocation); @@ -425,6 +429,7 @@ void AICOMM_UpdatePlayerOrders(AvHAIPlayer* pBot) AvHAIHiveDefinition* EmptyHive = nullptr; float MinDist = 0.0f; + int MinNumAssignedPlayers = 0; for (auto it = Hives.begin(); it != Hives.end(); it++) { @@ -432,15 +437,15 @@ void AICOMM_UpdatePlayerOrders(AvHAIPlayer* pBot) if (ThisHive->Status != HIVE_STATUS_UNBUILT) { continue; } if (AICOMM_IsHiveFullySecured(pBot, ThisHive, false)) { continue; } - int NumPlayersSecuring = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), ThisHive->FloorLocation, UTIL_MetresToGoldSrcUnits(10.0f), false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER); int NumAssignedPlayers = AICOMM_GetNumPlayersAssignedToOrder(pBot, ThisHive->HiveEntity->edict(), ORDERPURPOSE_SECURE_HIVE); - if ((NumPlayersSecuring + NumAssignedPlayers) < DesiredPlayers) + if (NumAssignedPlayers < DesiredPlayers) { float ThisDist = vDist2DSq(AITAC_GetCommChairLocation(pBot->Player->GetTeam()), ThisHive->Location); if (!EmptyHive || ThisDist < MinDist) { + MinNumAssignedPlayers = NumAssignedPlayers; EmptyHive = ThisHive; MinDist = ThisDist; } @@ -449,11 +454,14 @@ void AICOMM_UpdatePlayerOrders(AvHAIPlayer* pBot) if (EmptyHive) { - edict_t* NewAssignee = AICOMM_GetPlayerWithNoOrderNearestLocation(pBot, EmptyHive->FloorLocation); - - if (!FNullEnt(NewAssignee)) + for (int i = 0; i < (DesiredPlayers - MinNumAssignedPlayers); i++) { - AICOMM_AssignNewPlayerOrder(pBot, NewAssignee, EmptyHive->HiveEntity->edict(), ORDERPURPOSE_SECURE_HIVE); + edict_t* NewAssignee = AICOMM_GetPlayerWithNoOrderNearestLocation(pBot, EmptyHive->FloorLocation); + + if (!FNullEnt(NewAssignee)) + { + AICOMM_AssignNewPlayerOrder(pBot, NewAssignee, EmptyHive->HiveEntity->edict(), ORDERPURPOSE_SECURE_HIVE); + } } } @@ -466,10 +474,9 @@ void AICOMM_UpdatePlayerOrders(AvHAIPlayer* pBot) if (ResNode) { - int NumPlayersSecuring = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), ResNode->Location, UTIL_MetresToGoldSrcUnits(5.0f), false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER); int NumAssignedPlayers = AICOMM_GetNumPlayersAssignedToOrder(pBot, ResNode->ResourceEntity->edict(), ORDERPURPOSE_SECURE_RESNODE); - if ((NumPlayersSecuring + NumAssignedPlayers) < 1) + if (NumAssignedPlayers < 1) { edict_t* NewAssignee = AICOMM_GetPlayerWithNoOrderNearestLocation(pBot, ResNode->Location); @@ -763,7 +770,7 @@ bool AICOMM_IsRequestValid(ai_commander_request* Request) return true; } -bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot, commander_action* Action) +bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot) { AvHTeamNumber TeamNumber = pBot->Player->GetTeam(); @@ -784,7 +791,7 @@ bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot, commander_action* Action) if (NumInfantryPortals < 2) { - if (AICOMM_BuildInfantryPortal(pBot, CommChair, Action)) + if (AICOMM_BuildInfantryPortal(pBot, CommChair)) { return true; } @@ -872,7 +879,7 @@ bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot, commander_action* Action) if (HiveToSecure) { - if (AICOMM_PerformNextSecureHiveAction(pBot, HiveToSecure, Action)) + if (AICOMM_PerformNextSecureHiveAction(pBot, HiveToSecure)) { return true; } @@ -924,7 +931,7 @@ bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot, commander_action* Action) if (HiveToSiege) { - if (AICOMM_PerformNextSiegeHiveAction(pBot, HiveToSiege, Action)) + if (AICOMM_PerformNextSiegeHiveAction(pBot, HiveToSiege)) { return true; } @@ -1001,10 +1008,63 @@ bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot, commander_action* Action) return false; } -bool AICOMM_CheckForNextResearchAction(AvHAIPlayer* pBot, commander_action* Action) +bool AICOMM_CheckForNextResearchAction(AvHAIPlayer* pBot) { AvHTeamNumber CommanderTeam = pBot->Player->GetTeam(); + vector Hives = AITAC_GetAllHives(); + + for (auto it = Hives.begin(); it != Hives.end(); it++) + { + AvHAIHiveDefinition* Hive = (*it); + + if (Hive->Status != HIVE_STATUS_UNBUILT) { continue; } + + DeployableSearchFilter TFFilter; + TFFilter.DeployableTeam = pBot->Player->GetTeam(); + TFFilter.DeployableTypes = STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY; + TFFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + TFFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING | STRUCTURE_STATUS_ELECTRIFIED; + TFFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); + + AvHAIBuildableStructure* NearestTF = AITAC_FindClosestDeployableToLocation(Hive->FloorLocation, &TFFilter); + + if (NearestTF) + { + TFFilter.DeployableTypes = STRUCTURE_MARINE_TURRET; + TFFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); + + int NumTurrets = AITAC_GetNumDeployablesNearLocation(NearestTF->Location, &TFFilter); + + if (NumTurrets > 0) + { + if (AICOMM_ResearchTech(pBot, NearestTF, RESEARCH_ELECTRICAL)) + { + return true; + } + } + } + + if (pBot->Player->GetResources() > 60) + { + if (Hive->HiveResNodeRef && Hive->HiveResNodeRef->OwningTeam == pBot->Player->GetTeam()) + { + edict_t* Tower = Hive->HiveResNodeRef->ActiveTowerEntity; + if (!FNullEnt(Tower) && UTIL_StructureIsFullyBuilt(Tower) && !UTIL_IsStructureElectrified(Tower)) + { + AvHAIBuildableStructure* ResTower = AITAC_GetDeployableRefFromEdict(Tower); + + if (ResTower && AICOMM_ResearchTech(pBot, ResTower, RESEARCH_ELECTRICAL)) + { + return true; + } + } + } + } + + } + + DeployableSearchFilter StructureFilter; StructureFilter.DeployableTeam = CommanderTeam; StructureFilter.ReachabilityTeam = CommanderTeam; @@ -1252,7 +1312,7 @@ const AvHAIResourceNode* AICOMM_GetNearestResourceNodeCapOpportunity(const AvHTe return Result; } -bool AICOMM_PerformNextSiegeHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinition* HiveToSiege, commander_action* Action) +bool AICOMM_PerformNextSiegeHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinition* HiveToSiege) { AvHTeamNumber CommanderTeam = pBot->Player->GetTeam(); @@ -1385,7 +1445,7 @@ bool AICOMM_PerformNextSiegeHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinit } -bool AICOMM_PerformNextSecureHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinition* HiveToSecure, commander_action* Action) +bool AICOMM_PerformNextSecureHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinition* HiveToSecure) { DeployableSearchFilter StructureFilter; StructureFilter.DeployableTypes = STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY | STRUCTURE_MARINE_PHASEGATE; @@ -1481,16 +1541,10 @@ bool AICOMM_PerformNextSecureHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefini return false; } - if (!UTIL_IsStructureElectrified(ExistingTF->edict)) - { - return AICOMM_ResearchTech(pBot, ExistingTF, RESEARCH_ELECTRICAL); - } - - return false; } -bool AICOMM_BuildInfantryPortal(AvHAIPlayer* pBot, edict_t* CommChair, commander_action* Action) +bool AICOMM_BuildInfantryPortal(AvHAIPlayer* pBot, edict_t* CommChair) { if (FNullEnt(CommChair) || !UTIL_StructureIsFullyBuilt(CommChair)) { return false; } @@ -1840,8 +1894,8 @@ void AICOMM_CommanderThink(AvHAIPlayer* pBot) if (AICOMM_CheckForNextRecycleAction(pBot)) { return; } if (AICOMM_CheckForNextSupportAction(pBot)) { return; } - if (AICOMM_CheckForNextBuildAction(pBot, &pBot->BuildAction)) { return; } - if (AICOMM_CheckForNextResearchAction(pBot, &pBot->ResearchAction)) { return; } + if (AICOMM_CheckForNextBuildAction(pBot)) { return; } + if (AICOMM_CheckForNextResearchAction(pBot)) { return; } } bool AICOMM_IsCommanderActionValid(AvHAIPlayer* pBot, commander_action* Action) @@ -1931,7 +1985,7 @@ const AvHAIHiveDefinition* AICOMM_GetEmptyHiveOpportunityNearestLocation(AvHAIPl if (Hive->Status != HIVE_STATUS_UNBUILT) { continue; } - if (AICOMM_IsHiveFullySecured(CommanderBot, Hive, true)) { continue; } + if (AICOMM_IsHiveFullySecured(CommanderBot, Hive, false)) { continue; } Vector SecureLocation = Hive->FloorLocation; diff --git a/main/source/mod/AIPlayers/AvHAICommander.h b/main/source/mod/AIPlayers/AvHAICommander.h index f1a33728..472b4602 100644 --- a/main/source/mod/AIPlayers/AvHAICommander.h +++ b/main/source/mod/AIPlayers/AvHAICommander.h @@ -35,10 +35,10 @@ bool AICOMM_DoesPlayerOrderNeedReminder(AvHAIPlayer* pBot, ai_commander_order* O void AICOMM_IssueOrderForAssignedJob(AvHAIPlayer* pBot, ai_commander_order* Order); void AICOMM_ClearAction(commander_action* Action); -bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot, commander_action* Action); +bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot); bool AICOMM_CheckForNextSupportAction(AvHAIPlayer* pBot); bool AICOMM_CheckForNextRecycleAction(AvHAIPlayer* pBot); -bool AICOMM_CheckForNextResearchAction(AvHAIPlayer* pBot, commander_action* Action); +bool AICOMM_CheckForNextResearchAction(AvHAIPlayer* pBot); void AICOMM_SetDropHealthAction(AvHAIPlayer* pBot, commander_action* Action, edict_t* Recipient); void AICOMM_SetDropAmmoAction(AvHAIPlayer* pBot, commander_action* Action, edict_t* Recipient); void AICOMM_SetDeployStructureAction(AvHAIPlayer* pBot, commander_action* Action, AvHAIDeployableStructureType StructureToBuild, const Vector Location, bool bIsUrgent); @@ -48,9 +48,9 @@ void AICOMM_CommanderThink(AvHAIPlayer* pBot); const AvHAIHiveDefinition* AICOMM_GetEmptyHiveOpportunityNearestLocation(AvHAIPlayer* CommanderBot, const Vector SearchLocation); -bool AICOMM_BuildInfantryPortal(AvHAIPlayer* pBot, edict_t* CommChair, commander_action* Action); -bool AICOMM_PerformNextSiegeHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinition* HiveToSiege, commander_action* Action); -bool AICOMM_PerformNextSecureHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinition* HiveToSecure, commander_action* Action); +bool AICOMM_BuildInfantryPortal(AvHAIPlayer* pBot, edict_t* CommChair); +bool AICOMM_PerformNextSiegeHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinition* HiveToSiege); +bool AICOMM_PerformNextSecureHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinition* HiveToSecure); ai_commander_request* AICOMM_GetExistingRequestForPlayer(AvHAIPlayer* pBot, edict_t* Requestor); void AICOMM_CheckNewRequests(AvHAIPlayer* pBot); diff --git a/main/source/mod/AIPlayers/AvHAIConstants.h b/main/source/mod/AIPlayers/AvHAIConstants.h index a60c9bf0..b9a211b3 100644 --- a/main/source/mod/AIPlayers/AvHAIConstants.h +++ b/main/source/mod/AIPlayers/AvHAIConstants.h @@ -204,7 +204,7 @@ typedef enum _AVHAIBOTROLE typedef struct _OFF_MESH_CONN { unsigned int ConnectionRefs[2]; - unsigned short ConnectionFlags = 0; + unsigned int ConnectionFlags = 0; Vector FromLocation = g_vecZero; Vector ToLocation = g_vecZero; edict_t* TargetObject = nullptr; @@ -379,6 +379,14 @@ typedef enum } BotAttackResult; +typedef enum +{ + BUILD_ATTEMPT_NONE = 0, + BUILD_ATTEMPT_PENDING, + BUILD_ATTEMPT_SUCCESS, + BUILD_ATTEMPT_FAILED +} BotBuildAttemptStatus; + // Bot path node. A path will be several of these strung together to lead the bot to its destination typedef struct _BOT_PATH_NODE @@ -386,7 +394,7 @@ typedef struct _BOT_PATH_NODE Vector FromLocation = g_vecZero; // Location to move from Vector Location = g_vecZero; // Location to move to float requiredZ = 0.0f; // If climbing a up ladder or wall, how high should they aim to get before dismounting. - unsigned short flag = 0; // Is this a ladder movement, wall climb, walk etc + unsigned int flag = 0; // Is this a ladder movement, wall climb, walk etc unsigned char area = 0; // Is this a crouch area, normal walking area etc unsigned int poly = 0; // The nav mesh poly this point resides on } bot_path_node; @@ -447,6 +455,16 @@ typedef struct _AVH_AI_PLAYER_TASK float TaskLength = 0.0f; // If a task has gone on longer than this time, it will be considered completed } AvHAIPlayerTask; +typedef struct _AVH_AI_BUILD_ATTEMPT +{ + AvHAIDeployableStructureType AttemptedStructureType = STRUCTURE_NONE; + Vector AttemptedLocation = g_vecZero; + int NumAttempts = 0; + BotBuildAttemptStatus BuildStatus = BUILD_ATTEMPT_NONE; + float BuildAttemptTime = 0.0f; + AvHAIBuildableStructure* LinkedStructure = nullptr; +} AvHAIBuildAttempt; + // Contains the bot's current navigation info, such as current path typedef struct _NAV_STATUS { @@ -486,7 +504,7 @@ typedef struct _NAV_STATUS BotMoveStyle MoveStyle = MOVESTYLE_NORMAL; // Current desired move style (e.g. normal, ambush, hide). Will trigger new path calculations if this changes float LastPathCalcTime = 0.0f; // When the bot last calculated a path, to limit how frequently it can recalculate - bool bPendingRecalculation = false; // This bot should recalculate its path as soon as it can + float NextForceRecalc = 0.0f; // If set, then the bot will force-recalc its current path bool bZig; // Is the bot zigging, or zagging? float NextZigTime; // Controls how frequently they zig or zag @@ -496,7 +514,7 @@ typedef struct _NAV_STATUS nav_profile NavProfile; bool bNavProfileChanged = false; - unsigned short SpecialMovementFlags = 0; // Any special movement flags required for this path (e.g. needs a welder, needs a jetpack etc.) + unsigned int SpecialMovementFlags = 0; // Any special movement flags required for this path (e.g. needs a welder, needs a jetpack etc.) } nav_status; @@ -629,11 +647,7 @@ typedef struct AVH_AI_PLAYER nav_status BotNavInfo; // Bot's movement information, their current path, where in the path they are etc. - commander_action BuildAction; - commander_action ResearchAction; - commander_action SupportAction; - commander_action RecycleAction; - commander_action* CurrentAction; + AvHAIBuildAttempt BuildAttempts; vector ActiveRequests; vector ActiveOrders; diff --git a/main/source/mod/AIPlayers/AvHAIHelper.cpp b/main/source/mod/AIPlayers/AvHAIHelper.cpp index 146b761a..8064f86a 100644 --- a/main/source/mod/AIPlayers/AvHAIHelper.cpp +++ b/main/source/mod/AIPlayers/AvHAIHelper.cpp @@ -223,6 +223,25 @@ bool IsEdictStructure(const edict_t* edict) return (GetDeployableObjectTypeFromEdict(edict) != STRUCTURE_NONE); } +bool IsDamagingStructure(const edict_t* StructureEdict) +{ + return IsDamagingStructure(GetStructureTypeFromEdict(StructureEdict)); +} + +bool IsDamagingStructure(AvHAIDeployableStructureType StructureType) +{ + switch (StructureType) + { + case STRUCTURE_ALIEN_OFFENCECHAMBER: + case STRUCTURE_MARINE_TURRETFACTORY: + return true; + default: + return false; + } + + return false; +} + AvHAIDeployableStructureType GetStructureTypeFromEdict(const edict_t* StructureEdict) { if (FNullEnt(StructureEdict)) { return STRUCTURE_NONE; } diff --git a/main/source/mod/AIPlayers/AvHAIHelper.h b/main/source/mod/AIPlayers/AvHAIHelper.h index 01528049..1c39a056 100644 --- a/main/source/mod/AIPlayers/AvHAIHelper.h +++ b/main/source/mod/AIPlayers/AvHAIHelper.h @@ -28,6 +28,11 @@ bool IsEdictStructure(const edict_t* edict); AvHAIDeployableStructureType GetStructureTypeFromEdict(const edict_t* StructureEdict); +// Returns true if this structure shoots back (turret or offence chamber) +bool IsDamagingStructure(const edict_t* StructureEdict); +// Returns true if this structure shoots back (turret or offence chamber) +bool IsDamagingStructure(AvHAIDeployableStructureType StructureType); + bool GetNearestMapLocationAtPoint(vec3_t SearchLocation, string& outLocation); AvHAIDeployableStructureType GetDeployableObjectTypeFromEdict(const edict_t* StructureEdict); diff --git a/main/source/mod/AIPlayers/AvHAINavigation.cpp b/main/source/mod/AIPlayers/AvHAINavigation.cpp index 1aaf2849..d0160df9 100644 --- a/main/source/mod/AIPlayers/AvHAINavigation.cpp +++ b/main/source/mod/AIPlayers/AvHAINavigation.cpp @@ -126,7 +126,7 @@ struct OffMeshConnectionDef bool bBiDir = false; float Rad = 0.0f; unsigned char Area = 0; - unsigned short Flag = 0; + unsigned int Flag = 0; bool bPendingDelete = false; bool bDirty = false; }; @@ -244,13 +244,13 @@ struct MeshProcess : public dtTileCacheMeshProcess } else if (polyAreas[i] == DT_TILECACHE_TEAM1STRUCTURE_AREA) { - polyAreas[i] = SAMPLE_POLYAREA_BLOCKED; - polyFlags[i] = SAMPLE_POLYFLAGS_BLOCKED | SAMPLE_POLYFLAGS_TEAM1STRUCTURE; + polyAreas[i] = SAMPLE_POLYAREA_STRUCTUREBLOCK; + polyFlags[i] = SAMPLE_POLYFLAGS_TEAM1STRUCTURE; } else if (polyAreas[i] == DT_TILECACHE_TEAM2STRUCTURE_AREA) { - polyAreas[i] = SAMPLE_POLYAREA_BLOCKED; - polyFlags[i] = SAMPLE_POLYFLAGS_BLOCKED | SAMPLE_POLYFLAGS_TEAM2STRUCTURE; + polyAreas[i] = SAMPLE_POLYAREA_STRUCTUREBLOCK; + polyFlags[i] = SAMPLE_POLYFLAGS_TEAM2STRUCTURE; } else if (polyAreas[i] == DT_TILECACHE_WELD_AREA) { @@ -431,7 +431,7 @@ unsigned int UTIL_AddTemporaryObstacle(unsigned int NavMeshIndex, const Vector L ObstacleNum = (unsigned int)ObsRef; - if (ObstacleNum > 0) + if (ObstacleNum > 0 && NavMeshIndex != BUILDING_NAV_MESH) { bNavMeshModified = true; } @@ -894,6 +894,7 @@ void UTIL_PopulateBaseNavProfiles() BaseNavProfiles[MARINE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_OBSTRUCTION, 2.0f); BaseNavProfiles[MARINE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_CROUCH, 2.0f); BaseNavProfiles[MARINE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_BLOCKED, 2.0f); + BaseNavProfiles[MARINE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_STRUCTUREBLOCK, 20.0f); BaseNavProfiles[MARINE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_FALLDAMAGE, 10.0f); BaseNavProfiles[MARINE_BASE_NAV_PROFILE].Filters.setIncludeFlags(SAMPLE_POLYFLAGS_ALL); BaseNavProfiles[MARINE_BASE_NAV_PROFILE].Filters.removeIncludeFlags(SAMPLE_POLYFLAGS_FLY | SAMPLE_POLYFLAGS_WALLCLIMB | SAMPLE_POLYFLAGS_WELD); @@ -906,6 +907,8 @@ void UTIL_PopulateBaseNavProfiles() BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_GROUND, 1.0f); BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_CROUCH, 1.0f); BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_OBSTRUCTION, 2.0f); + BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_BLOCKED, 2.0f); + BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_STRUCTUREBLOCK, 20.0f); BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setIncludeFlags(SAMPLE_POLYFLAGS_ALL); BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.removeIncludeFlags(SAMPLE_POLYFLAGS_TEAM1PHASEGATE | SAMPLE_POLYFLAGS_TEAM2PHASEGATE | SAMPLE_POLYFLAGS_DUCKJUMP | SAMPLE_POLYFLAGS_WELD | SAMPLE_POLYFLAGS_FLY); BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setExcludeFlags(0); @@ -919,6 +922,7 @@ void UTIL_PopulateBaseNavProfiles() BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_OBSTRUCTION, 2.0f); BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_BLOCKED, 2.0f); BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_FALLDAMAGE, 10.0f); + BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_STRUCTUREBLOCK, 20.0f); BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.setIncludeFlags(SAMPLE_POLYFLAGS_ALL); BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.setExcludeFlags(0); BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.removeIncludeFlags(SAMPLE_POLYFLAGS_WALLCLIMB); @@ -934,6 +938,8 @@ void UTIL_PopulateBaseNavProfiles() BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_GROUND, 1.0f); BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_CROUCH, 1.0f); BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_OBSTRUCTION, 2.0f); + BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_BLOCKED, 2.0f); + BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_STRUCTUREBLOCK, 20.0f); BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.setIncludeFlags(SAMPLE_POLYFLAGS_ALL); BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.setExcludeFlags(0); BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.removeIncludeFlags(SAMPLE_POLYFLAGS_TEAM1PHASEGATE); @@ -947,6 +953,8 @@ void UTIL_PopulateBaseNavProfiles() BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_CROUCH, 1.0f); BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_OBSTRUCTION, 2.0f); BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_CROUCH, 1.5f); + BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_BLOCKED, 2.0f); + BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_STRUCTUREBLOCK, 20.0f); BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setIncludeFlags(SAMPLE_POLYFLAGS_ALL); BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setExcludeFlags(0); BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.removeIncludeFlags(SAMPLE_POLYFLAGS_TEAM1PHASEGATE); @@ -961,6 +969,8 @@ void UTIL_PopulateBaseNavProfiles() BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_OBSTRUCTION, 2.0f); BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_CROUCH, 2.0f); BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_FALLDAMAGE, 10.0f); + BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_BLOCKED, 2.0f); + BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_STRUCTUREBLOCK, 5.0f); // Onos is a wrecking machine, structures shouldn't be such an obstacle for them! BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.setIncludeFlags(SAMPLE_POLYFLAGS_ALL); BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.setExcludeFlags(0); BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.removeIncludeFlags(SAMPLE_POLYFLAGS_WALLCLIMB); @@ -1360,7 +1370,7 @@ dtStatus FindFlightPathToPoint(const nav_profile &NavProfile, Vector FromLocatio m_navMesh->getPolyArea(StraightPolyPath[0], &CurrArea); m_navMesh->getPolyFlags(StraightPolyPath[0], &CurrFlags); - CurrFlags &= ~(SAMPLE_POLYFLAGS_TEAM1STRUCTURE | SAMPLE_POLYFLAGS_TEAM2STRUCTURE | SAMPLE_POLYFLAGS_NOONOS); + CurrFlags &= ~(SAMPLE_POLYFLAGS_NOONOS); // At this point we have our path. Copy it to the path store int nIndex = 0; @@ -1380,7 +1390,7 @@ dtStatus FindFlightPathToPoint(const nav_profile &NavProfile, Vector FromLocatio m_navMesh->getPolyArea(StraightPolyPath[nVert], &ThisArea); m_navMesh->getPolyFlags(StraightPolyPath[nVert], &ThisFlags); - ThisFlags &= ~(SAMPLE_POLYFLAGS_TEAM1STRUCTURE | SAMPLE_POLYFLAGS_TEAM2STRUCTURE); + ThisFlags &= ~(SAMPLE_POLYFLAGS_NOONOS); if (ThisArea == SAMPLE_POLYAREA_GROUND || ThisArea == SAMPLE_POLYAREA_CROUCH) { @@ -1605,7 +1615,7 @@ dtStatus FindPathClosestToPoint(const nav_profile& NavProfile, const Vector From m_navMesh->getPolyFlags(StraightPolyPath[0], &CurrFlags); m_navMesh->getPolyArea(StraightPolyPath[0], &CurrArea); - CurrFlags &= ~(SAMPLE_POLYFLAGS_TEAM1STRUCTURE | SAMPLE_POLYFLAGS_TEAM2STRUCTURE | SAMPLE_POLYFLAGS_NOONOS); + CurrFlags &= ~(SAMPLE_POLYFLAGS_NOONOS); // At this point we have our path. Copy it to the path store int nIndex = 0; @@ -1627,7 +1637,7 @@ dtStatus FindPathClosestToPoint(const nav_profile& NavProfile, const Vector From m_navMesh->getPolyArea(StraightPolyPath[nVert], &ThisArea); m_navMesh->getPolyFlags(StraightPolyPath[nVert], &ThisFlags); - ThisFlags &= ~(SAMPLE_POLYFLAGS_TEAM1STRUCTURE | SAMPLE_POLYFLAGS_TEAM2STRUCTURE | SAMPLE_POLYFLAGS_NOONOS); + ThisFlags &= ~(SAMPLE_POLYFLAGS_NOONOS); if (ThisArea == SAMPLE_POLYAREA_GROUND || ThisArea == SAMPLE_POLYAREA_CROUCH) { @@ -1774,7 +1784,7 @@ dtStatus FindPathClosestToPoint(AvHAIPlayer* pBot, const BotMoveStyle MoveStyle, m_navMesh->getPolyFlags(StraightPolyPath[0], &CurrFlags); m_navMesh->getPolyArea(StraightPolyPath[0], &CurrArea); - CurrFlags &= ~(SAMPLE_POLYFLAGS_TEAM1STRUCTURE | SAMPLE_POLYFLAGS_TEAM2STRUCTURE | SAMPLE_POLYFLAGS_NOONOS); + CurrFlags &= ~(SAMPLE_POLYFLAGS_NOONOS); // At this point we have our path. Copy it to the path store int nIndex = 0; @@ -1861,7 +1871,7 @@ dtStatus FindPathClosestToPoint(AvHAIPlayer* pBot, const BotMoveStyle MoveStyle, m_navMesh->getPolyFlags(StraightPolyPath[nVert], &CurrFlags); m_navMesh->getPolyArea(StraightPolyPath[nVert], &CurrArea); - CurrFlags &= ~(SAMPLE_POLYFLAGS_TEAM1STRUCTURE | SAMPLE_POLYFLAGS_TEAM2STRUCTURE | SAMPLE_POLYFLAGS_NOONOS); + CurrFlags &= ~(SAMPLE_POLYFLAGS_NOONOS); NodeFromLocation = NextPathNode.Location; @@ -1971,6 +1981,8 @@ bool HasBotReachedPathPoint(const AvHAIPlayer* pBot) return ((vDist2D(pEdict->v.origin, MoveTo) < playerRadius && bDestIsDirectlyReachable) || bAtOrPastDestination); } case SAMPLE_POLYFLAGS_BLOCKED: + case SAMPLE_POLYFLAGS_TEAM1STRUCTURE: + case SAMPLE_POLYFLAGS_TEAM2STRUCTURE: return bAtOrPastDestination; case SAMPLE_POLYFLAGS_FALL: case SAMPLE_POLYFLAGS_JUMP: @@ -2019,7 +2031,7 @@ bool HasBotReachedPathPoint(const AvHAIPlayer* pBot) void CheckAndHandleDoorObstruction(AvHAIPlayer* pBot) { - edict_t* BlockingDoorEdict = UTIL_GetDoorBlockingPathPoint(pBot->Edict->v.origin, pBot->BotNavInfo.CurrentPathPoint->Location, SAMPLE_POLYAREA_GROUND, nullptr); + edict_t* BlockingDoorEdict = UTIL_GetDoorBlockingPathPoint(pBot->Edict->v.origin, pBot->BotNavInfo.CurrentPathPoint->Location, pBot->BotNavInfo.CurrentPathPoint->flag, nullptr); if (FNullEnt(BlockingDoorEdict)) { @@ -2114,7 +2126,7 @@ void CheckAndHandleDoorObstruction(AvHAIPlayer* pBot) // Door must be shot to open if (Door->ActivationType == DOOR_SHOOT) { - BotAttackTarget(pBot, Door->DoorEdict); + BotAttackNonPlayerTarget(pBot, Door->DoorEdict); return; } @@ -2386,7 +2398,7 @@ edict_t* UTIL_GetBreakableBlockingPathPoint(AvHAIPlayer* pBot, bot_path_node* Pa return nullptr; } -edict_t* UTIL_GetBreakableBlockingPathPoint(AvHAIPlayer* pBot, const Vector FromLocation, const Vector ToLocation, const unsigned short MovementFlag, edict_t* SearchBreakable) +edict_t* UTIL_GetBreakableBlockingPathPoint(AvHAIPlayer* pBot, const Vector FromLocation, const Vector ToLocation, const unsigned int MovementFlag, edict_t* SearchBreakable) { Vector FromLoc = FromLocation; Vector ToLoc = ToLocation; @@ -2495,7 +2507,7 @@ edict_t* UTIL_GetBreakableBlockingPathPoint(AvHAIPlayer* pBot, const Vector From return nullptr; } -edict_t* UTIL_GetDoorBlockingPathPoint(const Vector FromLocation, const Vector ToLocation, const unsigned short MovementFlag, edict_t* SearchDoor) +edict_t* UTIL_GetDoorBlockingPathPoint(const Vector FromLocation, const Vector ToLocation, const unsigned int MovementFlag, edict_t* SearchDoor) { Vector FromLoc = FromLocation; @@ -2839,6 +2851,10 @@ void NewMove(AvHAIPlayer* pBot) case SAMPLE_POLYFLAGS_BLOCKED: BlockedMove(pBot, MoveFrom, MoveTo); break; + case SAMPLE_POLYFLAGS_TEAM1STRUCTURE: + case SAMPLE_POLYFLAGS_TEAM2STRUCTURE: + StructureBlockedMove(pBot, MoveFrom, MoveTo); + break; case SAMPLE_POLYFLAGS_WALLCLIMB: { if (IsPlayerSkulk(pBot->Edict)) @@ -3024,6 +3040,50 @@ void FallMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint) } } +void StructureBlockedMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint) +{ + Vector vForward = UTIL_GetVectorNormal2D(EndPoint - StartPoint); + + pBot->desiredMovementDir = vForward; + + DeployableSearchFilter BlockingFilter; + BlockingFilter.DeployableTeam = AIMGR_GetEnemyTeam(pBot->Player->GetTeam()); + BlockingFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(3.0f); + + vector BlockingStructures = AITAC_FindAllDeployables(pBot->Edict->v.origin, &BlockingFilter); + + AvHAIBuildableStructure* CulpritStructure = nullptr; + float MinDist = 0.0f; + + for (auto it = BlockingStructures.begin(); it != BlockingStructures.end(); it++) + { + AvHAIBuildableStructure* ThisStructure = (*it); + + float ThisDist = vDistanceFromLine2DSq(StartPoint, EndPoint, ThisStructure->Location); + + if (!CulpritStructure || ThisDist < MinDist) + { + CulpritStructure = ThisStructure; + } + } + + if (CulpritStructure) + { + BotMoveLookAt(pBot, CulpritStructure->Location); + + AvHAIWeapon AttackWeapon = (IsPlayerAlien(pBot->Edict)) ? BotAlienChooseBestWeaponForStructure(pBot, CulpritStructure->edict) : BotMarineChooseBestWeaponForStructure(pBot, CulpritStructure->edict); + + if (GetPlayerCurrentWeapon(pBot->Player) != AttackWeapon) + { + pBot->DesiredMoveWeapon = AttackWeapon; + } + else + { + BotShootTarget(pBot, AttackWeapon, CulpritStructure->edict); + } + } +} + void BlockedMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint) { @@ -4876,7 +4936,7 @@ bool AbortCurrentMove(AvHAIPlayer* pBot, const Vector NewDestination) Vector MoveFrom = pBot->BotNavInfo.CurrentPathPoint->FromLocation; Vector MoveTo = pBot->BotNavInfo.CurrentPathPoint->Location; - unsigned short flag = pBot->BotNavInfo.CurrentPathPoint->flag; + unsigned int flag = pBot->BotNavInfo.CurrentPathPoint->flag; Vector ClosestPointOnLine = vClosestPointOnLine2D(MoveFrom, MoveTo, pBot->Edict->v.origin); @@ -4893,7 +4953,7 @@ bool AbortCurrentMove(AvHAIPlayer* pBot, const Vector NewDestination) if (flag == SAMPLE_POLYFLAGS_WALK) { - if (UTIL_PointIsDirectlyReachable(pBot->Edict->v.origin, MoveFrom) || UTIL_PointIsDirectlyReachable(pBot->Edict->v.origin, MoveTo)) + if (UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, MoveFrom) || UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, MoveTo)) { return true; } @@ -5040,31 +5100,37 @@ void SetBaseNavProfile(AvHAIPlayer* pBot) void UpdateBotMoveProfile(AvHAIPlayer* pBot, BotMoveStyle MoveStyle) { - - if (IsPlayerMarine(pBot->Player)) - { - MarineUpdateBotMoveProfile(pBot, MoveStyle); - return; - } - switch (pBot->Edict->v.iuser3) { + case AVH_USER3_MARINE_PLAYER: + MarineUpdateBotMoveProfile(pBot, MoveStyle); + break; case AVH_USER3_ALIEN_PLAYER1: SkulkUpdateBotMoveProfile(pBot, MoveStyle); - return; + break; case AVH_USER3_ALIEN_PLAYER2: GorgeUpdateBotMoveProfile(pBot, MoveStyle); - return; + break; case AVH_USER3_ALIEN_PLAYER3: LerkUpdateBotMoveProfile(pBot, MoveStyle); - return; + break; case AVH_USER3_ALIEN_PLAYER4: FadeUpdateBotMoveProfile(pBot, MoveStyle); - return; + break; case AVH_USER3_ALIEN_PLAYER5: OnosUpdateBotMoveProfile(pBot, MoveStyle); - return; + break; + } + if (pBot->Player->GetTeam() == GetGameRules()->GetTeamANumber()) + { + pBot->BotNavInfo.NavProfile.Filters.removeExcludeFlags(SAMPLE_POLYFLAGS_TEAM2STRUCTURE); + pBot->BotNavInfo.NavProfile.Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM1STRUCTURE); + } + else + { + pBot->BotNavInfo.NavProfile.Filters.removeExcludeFlags(SAMPLE_POLYFLAGS_TEAM1STRUCTURE); + pBot->BotNavInfo.NavProfile.Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM2STRUCTURE); } } @@ -5303,6 +5369,8 @@ bool MoveTo(AvHAIPlayer* pBot, const Vector Destination, const BotMoveStyle Move bool bIsFlyingProfile = pBot->BotNavInfo.NavProfile.bFlyingProfile; + bool bForceRecalculation = (pBot->BotNavInfo.NextForceRecalc > 0.0f && gpGlobals->time >= pBot->BotNavInfo.NextForceRecalc); + if (BotNavInfo->CurrentPath.size() > 0) { if (pBot->BotNavInfo.CurrentPathPoint == pBot->BotNavInfo.CurrentPath.end()) @@ -5327,10 +5395,10 @@ bool MoveTo(AvHAIPlayer* pBot, const Vector Destination, const BotMoveStyle Move } bool bUltimateDestinationChanged = !vEquals(Destination, BotNavInfo->TargetDestination, GetPlayerRadius(pBot->Player)) && !vEquals(Destination, MoveTaskDestination) && !vEquals(Destination, MoveTaskOrigin) && !vEquals(Destination, MoveSecondaryOrigin); - + bool bHasReachedDestination = BotIsAtLocation(pBot, BotNavInfo->TargetDestination); - if (bUltimateDestinationChanged || bNavProfileChanged || bHasReachedDestination) + if (bUltimateDestinationChanged || bNavProfileChanged || bHasReachedDestination || bForceRecalculation) { // First abort our current move so we don't try to recalculate half-way up a wall or ladder if (bIsFlyingProfile || AbortCurrentMove(pBot, Destination)) @@ -5363,7 +5431,7 @@ bool MoveTo(AvHAIPlayer* pBot, const Vector Destination, const BotMoveStyle Move bool bCanRecalculatePath = (gpGlobals->time - pBot->BotNavInfo.LastPathCalcTime > MIN_PATH_RECALC_TIME); // Only recalculate the path if there isn't a path, or something has changed and enough time has elapsed since the last path calculation - bool bShouldCalculatePath = bCanRecalculatePath && (BotNavInfo->CurrentPath.size() == 0 || !vEquals(Destination, BotNavInfo->PathDestination)); + bool bShouldCalculatePath = bCanRecalculatePath && (bForceRecalculation || BotNavInfo->CurrentPath.size() == 0 || !vEquals(Destination, BotNavInfo->PathDestination)); if (bShouldCalculatePath) { @@ -5374,8 +5442,8 @@ bool MoveTo(AvHAIPlayer* pBot, const Vector Destination, const BotMoveStyle Move } pBot->BotNavInfo.LastPathCalcTime = gpGlobals->time; - BotNavInfo->bPendingRecalculation = false; BotNavInfo->bNavProfileChanged = false; + BotNavInfo->NextForceRecalc = 0.0f; if (vIsZero(BotNavInfo->TargetDestination)) { @@ -5831,7 +5899,7 @@ void BotFollowPath(AvHAIPlayer* pBot) Vector MoveTo = BotNavInfo->CurrentPathPoint->Location; - unsigned short CurrentFlag = BotNavInfo->CurrentPathPoint->flag; + unsigned int CurrentFlag = BotNavInfo->CurrentPathPoint->flag; bool bIsUsingPhaseGate = (CurrentFlag == SAMPLE_POLYFLAGS_TEAM1PHASEGATE || CurrentFlag == SAMPLE_POLYFLAGS_TEAM2PHASEGATE); @@ -7882,7 +7950,7 @@ nav_door* UTIL_GetClosestLiftToPoints(const Vector StartPoint, const Vector EndP return Result; } -void UTIL_AddOffMeshConnection(Vector StartLoc, Vector EndLoc, unsigned char area, unsigned short flags, bool bBiDirectional, AvHAIOffMeshConnection* RemoveConnectionDef) +void UTIL_AddOffMeshConnection(Vector StartLoc, Vector EndLoc, unsigned char area, unsigned int flags, bool bBiDirectional, AvHAIOffMeshConnection* RemoveConnectionDef) { Vector ConnStart, ConnEnd; diff --git a/main/source/mod/AIPlayers/AvHAINavigation.h b/main/source/mod/AIPlayers/AvHAINavigation.h index b5d9cbc9..ba4866fb 100644 --- a/main/source/mod/AIPlayers/AvHAINavigation.h +++ b/main/source/mod/AIPlayers/AvHAINavigation.h @@ -37,12 +37,14 @@ constexpr auto MAX_PATH_POLY = 512; // Max nav mesh polys that can be traversed // Possible area types. Water, Road, Door and Grass are not used (left-over from Detour library) enum SamplePolyAreas { - SAMPLE_POLYAREA_GROUND = 0, // Regular ground movement - SAMPLE_POLYAREA_CROUCH = 1, // Requires crouched movement - SAMPLE_POLYAREA_BLOCKED = 2, // Requires a jump to get over - SAMPLE_POLYAREA_FALLDAMAGE = 3, // Requires taking fall damage (if not immune to it) - SAMPLE_POLYAREA_WALLCLIMB = 4, // Requires the ability to wall-stick, fly or blink - SAMPLE_POLYAREA_OBSTRUCTION = 5 // There is a door or weldable object in the way + SAMPLE_POLYAREA_GROUND = 0, // Regular ground movement + SAMPLE_POLYAREA_CROUCH = 1, // Requires crouched movement + SAMPLE_POLYAREA_BLOCKED = 2, // Requires a jump to get over + SAMPLE_POLYAREA_FALLDAMAGE = 3, // Requires taking fall damage (if not immune to it) + SAMPLE_POLYAREA_WALLCLIMB = 4, // Requires the ability to wall-stick, fly or blink + SAMPLE_POLYAREA_OBSTRUCTION = 5, // There is a door or weldable object in the way + SAMPLE_POLYAREA_STRUCTUREBLOCK = 6, // An enemy structure is blocking the way that must be destroyed + SAMPLE_POLYAREA_PHASEGATE = 7 // Phase gate area, for area cost calculation }; // Possible movement types. Swim and door are not used @@ -247,6 +249,8 @@ void GroundMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoin void JumpMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint); // Called by NewMove, determines movement direction and jump inputs to hop over obstructions (structures) void BlockedMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint); +// Called by NewMove, determines which structure is in the way and attacks it +void StructureBlockedMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint); // Called by NewMove, determines the movement direction and inputs required to drop down from start to end points void FallMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint); // Called by NewMove, determines the movement direction and inputs required to climb a ladder to reach endpoint @@ -275,9 +279,9 @@ DoorTrigger* UTIL_GetNearestDoorTriggerFromLift(edict_t* LiftEdict, nav_door* Do bool UTIL_IsPathBlockedByDoor(const Vector StartLoc, const Vector EndLoc, edict_t* SearchDoor); edict_t* UTIL_GetDoorBlockingPathPoint(AvHAIPlayer* pBot, bot_path_node* PathNode, edict_t* SearchDoor); -edict_t* UTIL_GetDoorBlockingPathPoint(const Vector FromLocation, const Vector ToLocation, const unsigned short MovementFlag, edict_t* SearchDoor); +edict_t* UTIL_GetDoorBlockingPathPoint(const Vector FromLocation, const Vector ToLocation, const unsigned int MovementFlag, edict_t* SearchDoor); edict_t* UTIL_GetBreakableBlockingPathPoint(AvHAIPlayer* pBot, bot_path_node* PathNode, edict_t* SearchBreakable); -edict_t* UTIL_GetBreakableBlockingPathPoint(AvHAIPlayer* pBot, const Vector FromLocation, const Vector ToLocation, const unsigned short MovementFlag, edict_t* SearchBreakable); +edict_t* UTIL_GetBreakableBlockingPathPoint(AvHAIPlayer* pBot, const Vector FromLocation, const Vector ToLocation, const unsigned int MovementFlag, edict_t* SearchBreakable); Vector UTIL_GetButtonFloorLocation(const Vector UserLocation, edict_t* ButtonEdict); @@ -316,7 +320,7 @@ void UTIL_RemoveTemporaryObstacle(unsigned int ObstacleRef); void UTIL_RemoveTemporaryObstacles(unsigned int* ObstacleRefs); -void UTIL_AddOffMeshConnection(Vector StartLoc, Vector EndLoc, unsigned char area, unsigned short flags, bool bBiDirectional, AvHAIOffMeshConnection* RemoveConnectionDef); +void UTIL_AddOffMeshConnection(Vector StartLoc, Vector EndLoc, unsigned char area, unsigned int flags, bool bBiDirectional, AvHAIOffMeshConnection* RemoveConnectionDef); void UTIL_RemoveOffMeshConnections(AvHAIOffMeshConnection* RemoveConnectionDef); diff --git a/main/source/mod/AIPlayers/AvHAIPlayer.cpp b/main/source/mod/AIPlayers/AvHAIPlayer.cpp index 251544e5..cf2f5f9d 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayer.cpp +++ b/main/source/mod/AIPlayers/AvHAIPlayer.cpp @@ -341,53 +341,6 @@ void BotLeap(AvHAIPlayer* pBot, const Vector TargetLocation) } } - -void LinkDeployedObjectToCommanderAction(AvHAIPlayer* Commander, AvHAIBuildableStructure* NewStructure) -{ - if (!Commander || !NewStructure || FNullEnt(Commander->Edict)) { return; } - - commander_action* Action = nullptr; - - if (Commander->BuildAction.bIsAwaitingBuildLink && Commander->BuildAction.StructureToBuild == NewStructure->StructureType) - { - if (vDist2DSq(Commander->BuildAction.BuildLocation, NewStructure->Location) < sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) - { - Action = &Commander->BuildAction; - } - } - - if (!Action) - { - if (Commander->SupportAction.bIsAwaitingBuildLink && Commander->SupportAction.StructureToBuild == NewStructure->StructureType) - { - if (vDist2DSq(Commander->SupportAction.BuildLocation, NewStructure->Location) < sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) - { - Action = &Commander->SupportAction; - } - } - } - - if (!Action) { return; } - - NewStructure->LastSuccessfulCommanderLocation = Action->LastAttemptedCommanderLocation; - NewStructure->LastSuccessfulCommanderAngle = Action->LastAttemptedCommanderAngle; - NewStructure->Purpose = Action->ActionPurpose; - - float CoolDown = (Action->NumDesiredInstances > 1) ? 0.33f : commander_action_cooldown; - - Commander->next_commander_action_time = gpGlobals->time + CoolDown; - - Action->NumInstances++; - - if (Action->NumDesiredInstances > 1) - { - Action->BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(BaseNavProfiles[MARINE_BASE_NAV_PROFILE], Action->BuildLocation, UTIL_MetresToGoldSrcUnits(1.0f)); - } - - Action->bIsAwaitingBuildLink = false; - -} - bot_msg* GetAvailableBotMsgSlot(AvHAIPlayer* pBot) { for (int i = 0; i < 5; i++) @@ -466,28 +419,9 @@ void BotDropWeapon(AvHAIPlayer* pBot) } } -void BotAttackTarget(AvHAIPlayer* pBot, edict_t* Target) +void BotAlienAttackNonPlayerTarget(AvHAIPlayer* pBot, edict_t* Target) { - if (FNullEnt(Target) || (Target->v.deadflag != DEAD_NO)) { return; } - - AvHAIWeapon Weapon = WEAPON_INVALID; - - if (IsPlayerMarine(pBot->Edict)) - { - Weapon = BotMarineChooseBestWeaponForStructure(pBot, Target); - } - else - { - Weapon = BotAlienChooseBestWeaponForStructure(pBot, Target); - } - - // Add special logic for grenade launchers since they aren't used like regular marine hitscan weapons - // This will handle things like firing from around corners, making sure they have cover from allies etc. - if (Weapon == WEAPON_MARINE_GL) - { - BombardierAttackTarget(pBot, Target); - return; - } + AvHAIWeapon Weapon = BotAlienChooseBestWeaponForStructure(pBot, Target); BotAttackResult AttackResult = PerformAttackLOSCheck(pBot, Weapon, Target); @@ -502,21 +436,7 @@ void BotAttackTarget(AvHAIPlayer* pBot, edict_t* Target) pBot->Button |= IN_DUCK; } - if (IsPlayerLerk(pBot->Edict)) - { - if (AITAC_ShouldBotBeCautious(pBot)) - { - MoveTo(pBot, Target->v.origin, MOVESTYLE_HIDE, 100.0f); - } - else - { - MoveTo(pBot, Target->v.origin, MOVESTYLE_NORMAL, 100.0f); - } - - return; - } - - Vector AttackPoint = (IsEdictPlayer(Target) || IsEdictStructure(Target)) ? Target->v.origin : UTIL_GetButtonFloorLocation(pBot->Edict->v.origin, Target); + Vector AttackPoint = (IsEdictStructure(Target)) ? Target->v.origin : UTIL_GetButtonFloorLocation(pBot->Edict->v.origin, Target); if (StructureType == STRUCTURE_ALIEN_HIVE) { @@ -528,28 +448,42 @@ void BotAttackTarget(AvHAIPlayer* pBot, edict_t* Target) } } - MoveTo(pBot, AttackPoint, MOVESTYLE_NORMAL, WeaponRange); - - if (IsPlayerMarine(pBot->Edict)) + if (IsPlayerLerk(pBot->Edict)) { - if (gpGlobals->time - pBot->LastCombatTime > 5.0f) + if (AITAC_ShouldBotBeCautious(pBot)) { - BotReloadWeapons(pBot); + MoveTo(pBot, AttackPoint, MOVESTYLE_HIDE, 100.0f); } + else + { + MoveTo(pBot, AttackPoint, MOVESTYLE_NORMAL, 100.0f); + } + + return; } + MoveTo(pBot, AttackPoint, MOVESTYLE_NORMAL, WeaponRange); + return; } if (AttackResult == ATTACK_BLOCKED) { - if (!(IsEdictPlayer(Target) && !IsEdictStructure(Target))) + + // We're attacking a shootable trigger + if (!IsEdictStructure(Target)) { Vector AttackPoint = UTIL_GetButtonFloorLocation(pBot->Edict->v.origin, Target); MoveTo(pBot, AttackPoint, MOVESTYLE_NORMAL, WeaponRange); return; } + // If we have regen and are hurt and are attacking a damaging structure, let us heal up a bit + if ((StructureType == STRUCTURE_MARINE_TURRET || StructureType == STRUCTURE_ALIEN_OFFENCECHAMBER) && GetPlayerOverallHealthPercent(pBot->Edict) < 0.75f && AvHGetAlienUpgradeLevel(pBot->Edict->v.iuser4, MASK_UPGRADE_2) > 0) + { + return; + } + if (vIsZero(pBot->BotNavInfo.ActualMoveDestination) || UTIL_TraceEntity(pBot->Edict, pBot->BotNavInfo.ActualMoveDestination + Vector(0.0f, 0.0f, 32.0f), UTIL_GetCentreOfEntity(Target)) != Target) { Vector NewAttackLocation = ZERO_VECTOR; @@ -589,6 +523,130 @@ void BotAttackTarget(AvHAIPlayer* pBot, edict_t* Target) } } +void BotMarineAttackNonPlayerTarget(AvHAIPlayer* pBot, edict_t* Target) +{ + AvHAIWeapon Weapon = BotMarineChooseBestWeaponForStructure(pBot, Target); + + // Add special logic for grenade launchers since they aren't used like regular marine hitscan weapons + // This will handle things like firing from around corners, making sure they have cover from allies etc. + if (Weapon == WEAPON_MARINE_GL) + { + BombardierAttackTarget(pBot, Target); + return; + } + + BotAttackResult AttackResult = PerformAttackLOSCheck(pBot, Weapon, Target); + + float WeaponRange = GetMaxIdealWeaponRange(Weapon); + + AvHAIDeployableStructureType StructureType = GetStructureTypeFromEdict(Target); + + if (AttackResult == ATTACK_OUTOFRANGE) + { + if (vDist2DSq(pBot->Edict->v.origin, Target->v.origin) < sqrf(max_player_use_reach)) + { + pBot->Button |= IN_DUCK; + } + + Vector AttackPoint = (IsEdictStructure(Target)) ? Target->v.origin : UTIL_GetButtonFloorLocation(pBot->Edict->v.origin, Target); + + if (StructureType == STRUCTURE_ALIEN_HIVE) + { + const AvHAIHiveDefinition* HiveDefinition = AITAC_GetHiveFromEdict(Target); + + if (HiveDefinition) + { + AttackPoint = HiveDefinition->FloorLocation; + } + } + + MoveTo(pBot, AttackPoint, MOVESTYLE_NORMAL, WeaponRange); + + return; + } + + if (AttackResult == ATTACK_BLOCKED) + { + // Finish reloading, we are probably behind cover + if (IsPlayerReloading(pBot->Player)) + { + return; + } + + // We're attacking a shootable trigger + if (!IsEdictStructure(Target)) + { + Vector AttackPoint = UTIL_GetButtonFloorLocation(pBot->Edict->v.origin, Target); + MoveTo(pBot, AttackPoint, MOVESTYLE_NORMAL, WeaponRange); + return; + } + + if (vIsZero(pBot->BotNavInfo.ActualMoveDestination) || UTIL_TraceEntity(pBot->Edict, pBot->BotNavInfo.ActualMoveDestination + Vector(0.0f, 0.0f, 32.0f), UTIL_GetCentreOfEntity(Target)) != Target) + { + Vector NewAttackLocation = ZERO_VECTOR; + + if (vIsZero(pBot->BotNavInfo.ActualMoveDestination)) + { + NewAttackLocation = FindClosestNavigablePointToDestination(pBot->BotNavInfo.NavProfile, pBot->CurrentFloorPosition, UTIL_GetEntityGroundLocation(Target), WeaponRange); + } + else + { + NewAttackLocation = UTIL_GetRandomPointOnNavmeshInRadius(pBot->BotNavInfo.NavProfile, pBot->CurrentFloorPosition, 2.0f); + + // Did we find a clear spot we could attack from? If so, make that our new move destination + if (NewAttackLocation != ZERO_VECTOR && UTIL_TraceEntity(pBot->Edict, NewAttackLocation + Vector(0.0f, 0.0f, 32.0f), UTIL_GetCentreOfEntity(Target)) == Target) + { + MoveTo(pBot, NewAttackLocation, MOVESTYLE_NORMAL); + } + } + } + else + { + MoveTo(pBot, pBot->BotNavInfo.TargetDestination, MOVESTYLE_NORMAL); + } + + return; + } + + if (AttackResult == ATTACK_SUCCESS) + { + if (IsPlayerReloading(pBot->Player)) + { + if (StructureType == STRUCTURE_MARINE_TURRET || StructureType == STRUCTURE_ALIEN_OFFENCECHAMBER) + { + MoveTo(pBot, AITAC_GetTeamStartingLocation(pBot->Player->GetTeam()), MOVESTYLE_NORMAL); + return; + } + } + + // If we were ducking before then keep ducking + if (pBot->Edict->v.oldbuttons & IN_DUCK) + { + pBot->Button |= IN_DUCK; + } + + BotShootTarget(pBot, Weapon, Target); + } + +} + +void BotAttackNonPlayerTarget(AvHAIPlayer* pBot, edict_t* Target) +{ + if (FNullEnt(Target) || (Target->v.deadflag != DEAD_NO)) { return; } + + AvHAIWeapon Weapon = WEAPON_INVALID; + + if (IsPlayerMarine(pBot->Edict)) + { + BotMarineAttackNonPlayerTarget(pBot, Target); + } + else + { + BotAlienAttackNonPlayerTarget(pBot, Target); + } + +} + void BotShootTarget(AvHAIPlayer* pBot, AvHAIWeapon AttackWeapon, edict_t* Target) { if (FNullEnt(Target) || (Target->v.deadflag != DEAD_NO)) { return; } @@ -926,8 +984,23 @@ void BotShootLocation(AvHAIPlayer* pBot, AvHAIWeapon AttackWeapon, const Vector } } -void BotEvolveLifeform(AvHAIPlayer* pBot, AvHMessageID TargetLifeform) +void BotEvolveLifeform(AvHAIPlayer* pBot, Vector DesiredEvolveLocation, AvHMessageID TargetLifeform) { + if (!IsPlayerAlien(pBot->Edict)) { return; } + + Vector EvolvePoint = UTIL_ProjectPointToNavmesh(DesiredEvolveLocation, GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE)); + + if (vIsZero(EvolvePoint)) + { + EvolvePoint = DesiredEvolveLocation; + } + + if (vDist2DSq(pBot->Edict->v.origin, EvolvePoint) > sqrf(32.0f)) + { + MoveTo(pBot, EvolvePoint, MOVESTYLE_NORMAL); + return; + } + pBot->Impulse = TargetLifeform; } @@ -1487,6 +1560,15 @@ void StartNewBotFrame(AvHAIPlayer* pBot) UpdateCommanderOrders(pBot); } + // If we tried placing a building as gorge, and nothing has appeared within 0.5s, the placement failed. + if (pBot->BuildAttempts.BuildStatus == BUILD_ATTEMPT_PENDING) + { + if ((gpGlobals->time - pBot->BuildAttempts.BuildAttemptTime) > 0.5f) + { + pBot->BuildAttempts.BuildStatus = BUILD_ATTEMPT_FAILED; + } + } + } void CustomThink(AvHAIPlayer* pBot) @@ -1510,7 +1592,14 @@ void DroneThink(AvHAIPlayer* pBot) BotProgressTask(pBot, &pBot->PrimaryBotTask); } - //AIDEBUG_DrawBotPath(pBot); + AIDEBUG_DrawBotPath(pBot); + + AvHAIWeapon DesiredWeapon = (pBot->DesiredMoveWeapon != WEAPON_NONE) ? pBot->DesiredMoveWeapon : pBot->DesiredCombatWeapon; + + if (DesiredWeapon != WEAPON_NONE && GetPlayerCurrentWeapon(pBot->Player) != DesiredWeapon) + { + BotSwitchToWeapon(pBot, DesiredWeapon); + } } void SetNewAIPlayerRole(AvHAIPlayer* pBot, AvHAIBotRole NewRole) @@ -2032,6 +2121,12 @@ void AIPlayerDMThink(AvHAIPlayer* pBot) void AIPlayerThink(AvHAIPlayer* pBot) { + if (pBot == AIMGR_GetDebugAIPlayer()) + { + bool bBreak = true; + + } + switch (GetGameRules()->GetMapMode()) { case MAP_MODE_NS: diff --git a/main/source/mod/AIPlayers/AvHAIPlayer.h b/main/source/mod/AIPlayers/AvHAIPlayer.h index 9c673e04..ec254be9 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayer.h +++ b/main/source/mod/AIPlayers/AvHAIPlayer.h @@ -22,21 +22,21 @@ float GetLeapCost(AvHAIPlayer* pBot); void BotReloadWeapons(AvHAIPlayer* pBot); -void LinkDeployedObjectToCommanderAction(AvHAIPlayer* Commander, AvHAIBuildableStructure* NewStructure); - // Make the bot type something in either global or team chat void BotSay(AvHAIPlayer* pBot, bool bTeamSay, float Delay, char* textToSay); bot_msg* GetAvailableBotMsgSlot(AvHAIPlayer* pBot); void BotDropWeapon(AvHAIPlayer* pBot); -void BotAttackTarget(AvHAIPlayer* pBot, edict_t* Target); +void BotAttackNonPlayerTarget(AvHAIPlayer* pBot, edict_t* Target); +void BotMarineAttackNonPlayerTarget(AvHAIPlayer* pBot, edict_t* Target); +void BotAlienAttackNonPlayerTarget(AvHAIPlayer* pBot, edict_t* Target); void BotShootTarget(AvHAIPlayer* pBot, AvHAIWeapon AttackWeapon, edict_t* Target); void BotShootLocation(AvHAIPlayer* pBot, AvHAIWeapon AttackWeapon, const Vector TargetLocation); void BombardierAttackTarget(AvHAIPlayer* pBot, edict_t* Target); -void BotEvolveLifeform(AvHAIPlayer* pBot, AvHMessageID Lifeform); +void BotEvolveLifeform(AvHAIPlayer* pBot, Vector DesiredEvolveLocation, AvHMessageID TargetLifeform); enemy_status* GetTrackedEnemyRefForTarget(AvHAIPlayer* pBot, edict_t* Target); diff --git a/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp b/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp index 4e36c1c3..921162cb 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp +++ b/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp @@ -32,6 +32,8 @@ extern int m_spriteTexture; Vector DebugVector1 = ZERO_VECTOR; Vector DebugVector2 = ZERO_VECTOR; +AvHAIPlayer* DebugAIPlayer = nullptr; + vector DebugPath; string BotNames[MAX_PLAYERS] = { "MrRobot", @@ -70,17 +72,17 @@ string BotNames[MAX_PLAYERS] = { "MrRobot", AvHAICommanderMode AIMGR_GetCommanderMode() { - if (avh_botcommandermode.value == 0) + if (avh_botcommandermode.value == 1) { - return COMMANDERMODE_DISABLED; + return COMMANDERMODE_ENABLED; } - if (avh_botcommandermode.value == 1) + if (avh_botcommandermode.value == 2) { return COMMANDERMODE_IFNOHUMAN; } - return COMMANDERMODE_ENABLED; + return COMMANDERMODE_DISABLED; } @@ -124,15 +126,15 @@ void AIMGR_UpdateAIPlayerCounts() return; } - if (avh_botautomode.value == 1) // Balance only: bots will only be added and removed to ensure teams remain balanced + if (avh_botautomode.value == 1) // Fill teams: bots will be added and removed to maintain a minimum player count { - AIMGR_UpdateTeamBalance(); + AIMGR_UpdateFillTeams(); return; } - if (avh_botautomode.value == 2) // Fill teams: bots will be added and removed to maintain a minimum player count + if (avh_botautomode.value == 2) // Balance only: bots will only be added and removed to ensure teams remain balanced { - AIMGR_UpdateFillTeams(); + AIMGR_UpdateTeamBalance(); return; } @@ -455,6 +457,12 @@ void AIMGR_AddAIPlayerToTeam(int Team) NewAIPlayer.Edict = BotEnt; NewAIPlayer.Team = theNewAIPlayer->GetTeam(); + NewAIPlayer.CurrentTask = nullptr; + NewAIPlayer.PrimaryBotTask.TaskType = TASK_NONE; + NewAIPlayer.SecondaryBotTask.TaskType = TASK_NONE; + NewAIPlayer.WantsAndNeedsTask.TaskType = TASK_NONE; + NewAIPlayer.CommanderTask.TaskType = TASK_NONE; + const bot_skill BotSkillSettings = CONFIG_GetGlobalBotSkillLevel(); memcpy(&NewAIPlayer.BotSkillSettings, &BotSkillSettings, sizeof(bot_skill)); @@ -565,9 +573,7 @@ void AIMGR_UpdateAIPlayers() UpdateBotChat(bot); - AIPlayerThink(bot); - - AIDEBUG_DrawPath(DebugPath, 0.0f); + DroneThink(bot); BotUpdateDesiredViewRotation(bot); } @@ -813,43 +819,6 @@ AvHAIPlayer* AIMGR_GetAICommander(AvHTeamNumber Team) return nullptr; } -AvHAIPlayer* AIMGR_FindPlayerOnTeamWaitingBuildLink(const AvHTeamNumber Team, const AvHAIDeployableStructureType NewStructure, const Vector BuildLocation) -{ - vector TeamPlayers = AIMGR_GetAIPlayersOnTeam(Team); - - for (auto it = TeamPlayers.begin(); it != TeamPlayers.end(); it++) - { - AvHAIPlayer* AIPlayer = (*it); - - if (AIPlayer->PrimaryBotTask.bIsWaitingForBuildLink && AIPlayer->PrimaryBotTask.StructureType == NewStructure) - { - if (vDist2DSq(BuildLocation, AIPlayer->PrimaryBotTask.TaskLocation) < sqrf(UTIL_MetresToGoldSrcUnits(2.0f))) - { - return AIPlayer; - } - - } - - if (AIPlayer->SecondaryBotTask.bIsWaitingForBuildLink && AIPlayer->SecondaryBotTask.StructureType == NewStructure) - { - if (vDist2DSq(BuildLocation, AIPlayer->SecondaryBotTask.TaskLocation) < sqrf(UTIL_MetresToGoldSrcUnits(2.0f))) - { - return AIPlayer; - } - } - - if (AIPlayer->WantsAndNeedsTask.bIsWaitingForBuildLink && AIPlayer->WantsAndNeedsTask.StructureType == NewStructure) - { - if (vDist2DSq(BuildLocation, AIPlayer->WantsAndNeedsTask.TaskLocation) < sqrf(UTIL_MetresToGoldSrcUnits(2.0f))) - { - return AIPlayer; - } - } - } - - return nullptr; -} - AvHTeamNumber AIMGR_GetEnemyTeam(const AvHTeamNumber FriendlyTeam) { AvHTeamNumber TeamANumber = GetGameRules()->GetTeamANumber(); @@ -858,6 +827,15 @@ AvHTeamNumber AIMGR_GetEnemyTeam(const AvHTeamNumber FriendlyTeam) return (FriendlyTeam == TeamANumber) ? TeamBNumber : TeamANumber; } +AvHClassType AIMGR_GetEnemyTeamType(const AvHTeamNumber FriendlyTeam) +{ + AvHTeamNumber EnemyTeamNumber = AIMGR_GetEnemyTeam(FriendlyTeam); + + AvHTeam* TeamRef = GetGameRules()->GetTeam(EnemyTeamNumber); + + return (TeamRef) ? TeamRef->GetTeamType() : AVH_CLASS_TYPE_UNDEFINED; +} + vector AIMGR_GetAllAIPlayers() { vector Result; @@ -904,3 +882,20 @@ void AIMGR_BotPrecache() { m_spriteTexture = PRECACHE_MODEL("sprites/zbeam6.spr"); } + +AvHAIPlayer* AIMGR_GetDebugAIPlayer() +{ + return DebugAIPlayer; +} + +void AIMGR_SetDebugAIPlayer(edict_t* AIPlayer) +{ + for (auto it = ActiveAIPlayers.begin(); it != ActiveAIPlayers.end(); it++) + { + if (it->Edict == AIPlayer) + { + DebugAIPlayer = &(*it); + return; + } + } +} \ No newline at end of file diff --git a/main/source/mod/AIPlayers/AvHAIPlayerManager.h b/main/source/mod/AIPlayers/AvHAIPlayerManager.h index ee05c64d..dc12c32d 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayerManager.h +++ b/main/source/mod/AIPlayers/AvHAIPlayerManager.h @@ -58,13 +58,17 @@ int AIMGR_GetNumAIPlayersWithRoleOnTeam(AvHTeamNumber Team, AvHAIBotRole Role, A AvHAIPlayer* AIMGR_GetAICommander(AvHTeamNumber Team); -AvHAIPlayer* AIMGR_FindPlayerOnTeamWaitingBuildLink(const AvHTeamNumber Team, const AvHAIDeployableStructureType NewStructure, const Vector BuildLocation); + AvHTeamNumber AIMGR_GetEnemyTeam(const AvHTeamNumber FriendlyTeam); +AvHClassType AIMGR_GetEnemyTeamType(const AvHTeamNumber FriendlyTeam); vector AIMGR_GetAllAIPlayers(); vector AIMGR_GetAIPlayersOnTeam(AvHTeamNumber Team); void AIMGR_ClearBotData(); +AvHAIPlayer* AIMGR_GetDebugAIPlayer(); +void AIMGR_SetDebugAIPlayer(edict_t* AIPlayer); + #endif \ No newline at end of file diff --git a/main/source/mod/AIPlayers/AvHAITactical.cpp b/main/source/mod/AIPlayers/AvHAITactical.cpp index 0358549f..f3299e6f 100644 --- a/main/source/mod/AIPlayers/AvHAITactical.cpp +++ b/main/source/mod/AIPlayers/AvHAITactical.cpp @@ -221,9 +221,9 @@ AvHAIBuildableStructure* AITAC_FindClosestDeployableToLocation(const Vector& Loc { for (auto& it : TeamAStructureMap) { - if (it.second.StructureStatusFlags & Filter->ExcludeStatusFlags) { continue; } - if ((it.second.StructureStatusFlags & Filter->IncludeStatusFlags) != Filter->IncludeStatusFlags) { continue; } if (!(it.second.StructureType & Filter->DeployableTypes)) { continue; } + if (it.second.StructureStatusFlags & Filter->ExcludeStatusFlags) { continue; } + if ((it.second.StructureStatusFlags & Filter->IncludeStatusFlags) != Filter->IncludeStatusFlags) { continue; } unsigned int StructureReachabilityFlags = (it.second.TeamAReachabilityFlags | it.second.TeamBReachabilityFlags); @@ -248,10 +248,10 @@ AvHAIBuildableStructure* AITAC_FindClosestDeployableToLocation(const Vector& Loc { for (auto& it : TeamBStructureMap) { + if (!(it.second.StructureType & Filter->DeployableTypes)) { continue; } if (it.second.StructureStatusFlags & Filter->ExcludeStatusFlags) { continue; } if ((it.second.StructureStatusFlags & Filter->IncludeStatusFlags) != Filter->IncludeStatusFlags) { continue; } - if (!(it.second.StructureType & Filter->DeployableTypes)) { continue; } - + unsigned int StructureReachabilityFlags = (it.second.TeamAReachabilityFlags | it.second.TeamBReachabilityFlags); if (Filter->ReachabilityTeam != TEAM_IND) @@ -1182,6 +1182,18 @@ void AITAC_OnNavMeshModified() { it->bReachabilityMarkedDirty = true; } + + vector AllAIPlayers = AIMGR_GetAllAIPlayers(); + + for (auto it = AllAIPlayers.begin(); it != AllAIPlayers.end(); it++) + { + AvHAIPlayer* ThisPlayer = (*it); + + if (IsPlayerActiveInGame(ThisPlayer->Edict) && ThisPlayer->BotNavInfo.CurrentPath.size() > 0) + { + ThisPlayer->BotNavInfo.NextForceRecalc = gpGlobals->time + frandrange(0.0f, 1.0f); + } + } } void AITAC_RefreshBuildableStructures() @@ -1660,6 +1672,8 @@ AvHAIBuildableStructure* AITAC_UpdateBuildableStructure(CBaseEntity* Structure) std::unordered_map& BuildingMap = (BaseBuildable->GetTeamNumber() == TeamANumber) ? TeamAStructureMap : TeamBStructureMap; + BuildingMap[EntIndex].StructureType = StructureType; + // This is the first time we've seen this structure, so it must be new if (BuildingMap[EntIndex].LastSeen == 0) { @@ -1674,8 +1688,6 @@ AvHAIBuildableStructure* AITAC_UpdateBuildableStructure(CBaseEntity* Structure) AITAC_OnStructureCreated(&BuildingMap[EntIndex]); } - BuildingMap[EntIndex].StructureType = StructureType; - if (vIsZero(BuildingMap[EntIndex].Location) || !vEquals(BaseBuildable->pev->origin, BuildingMap[EntIndex].Location, 5.0f)) { AITAC_RefreshReachabilityForStructure(&BuildingMap[EntIndex]); @@ -1754,24 +1766,9 @@ void AITAC_OnStructureCreated(AvHAIBuildableStructure* NewStructure) if (!Team) { return; } - if (Team->GetTeamType() == AVH_CLASS_TYPE_MARINE) + if (Team->GetTeamType() == AVH_CLASS_TYPE_ALIEN) { - AvHAIPlayer* ActiveAICommander = AIMGR_GetAICommander(StructureTeam); - - if (ActiveAICommander) - { - LinkDeployedObjectToCommanderAction(ActiveAICommander, NewStructure); - } - } - else - { - AvHAIPlayer* BuildingPlayer = AIMGR_FindPlayerOnTeamWaitingBuildLink(StructureTeam, NewStructure->StructureType, NewStructure->Location); - - if (BuildingPlayer) - { - AITAC_LinkAlienStructureToTask(BuildingPlayer, NewStructure); - } - + AITAC_LinkAlienStructureToPlayer(NewStructure); } } @@ -1805,7 +1802,7 @@ void AITAC_OnStructureCompleted(AvHAIBuildableStructure* NewStructure) NewConnection.TargetObject = OtherPhaseGate->edict; memset(&NewConnection.ConnectionRefs[0], 0, sizeof(NewConnection.ConnectionRefs)); - UTIL_AddOffMeshConnection(NewStructure->Location, OtherPhaseGate->Location, SAMPLE_POLYAREA_GROUND, NewFlag, true, &NewConnection); + UTIL_AddOffMeshConnection(NewStructure->Location, OtherPhaseGate->Location, SAMPLE_POLYAREA_PHASEGATE, NewFlag, true, &NewConnection); NewStructure->OffMeshConnections.push_back(NewConnection); @@ -1875,9 +1872,24 @@ void AITAC_OnStructureDestroyed(AvHAIBuildableStructure* DestroyedStructure) } } -void AITAC_LinkAlienStructureToTask(AvHAIPlayer* pBot, AvHAIBuildableStructure* NewStructure) +void AITAC_LinkAlienStructureToPlayer(AvHAIBuildableStructure* NewStructure) { + vector AllTeamPlayers = AIMGR_GetAIPlayersOnTeam((AvHTeamNumber)NewStructure->edict->v.team); + for (auto it = AllTeamPlayers.begin(); it != AllTeamPlayers.end(); it++) + { + AvHAIPlayer* Player = (*it); + + if (Player->BuildAttempts.BuildStatus == BUILD_ATTEMPT_PENDING && Player->BuildAttempts.AttemptedStructureType == NewStructure->StructureType) + { + if (vDist2DSq(NewStructure->Location, Player->BuildAttempts.AttemptedLocation) < sqrf(UTIL_MetresToGoldSrcUnits(2.0f))) + { + Player->BuildAttempts.BuildStatus = BUILD_ATTEMPT_SUCCESS; + Player->BuildAttempts.LinkedStructure = NewStructure; + } + + } + } } void AITAC_LinkDeployedItemToAction(AvHAIPlayer* CommanderBot, const AvHAIDroppedItem* NewItem) @@ -2044,18 +2056,18 @@ unsigned char UTIL_GetAreaForObstruction(AvHAIDeployableStructureType StructureT AvHTeamNumber TeamA = GetGameRules()->GetTeamANumber(); AvHTeamNumber TeamB = GetGameRules()->GetTeamBNumber(); - unsigned char StructureArea = (BuildingEdict->v.team == TeamA) ? DT_TILECACHE_TEAM1STRUCTURE_AREA : DT_TILECACHE_TEAM2STRUCTURE_AREA; + unsigned char TeamStructureArea = (BuildingEdict->v.team == TeamA) ? DT_TILECACHE_TEAM1STRUCTURE_AREA : DT_TILECACHE_TEAM2STRUCTURE_AREA; switch (StructureType) { - case STRUCTURE_MARINE_RESTOWER: case STRUCTURE_MARINE_COMMCHAIR: case STRUCTURE_MARINE_ARMOURY: case STRUCTURE_MARINE_ADVARMOURY: case STRUCTURE_MARINE_OBSERVATORY: case STRUCTURE_ALIEN_RESTOWER: + case STRUCTURE_MARINE_RESTOWER: case STRUCTURE_ALIEN_HIVE: - return StructureArea; + return TeamStructureArea; default: return DT_TILECACHE_BLOCKED_AREA; } @@ -2072,6 +2084,8 @@ float UTIL_GetStructureRadiusForObstruction(AvHAIDeployableStructureType Structu case STRUCTURE_MARINE_TURRETFACTORY: case STRUCTURE_MARINE_COMMCHAIR: return 60.0f; + case STRUCTURE_MARINE_TURRET: + return 30.0f; default: return 40.0f; diff --git a/main/source/mod/AIPlayers/AvHAITactical.h b/main/source/mod/AIPlayers/AvHAITactical.h index c70ad8cf..f6dff8f7 100644 --- a/main/source/mod/AIPlayers/AvHAITactical.h +++ b/main/source/mod/AIPlayers/AvHAITactical.h @@ -44,7 +44,7 @@ void AITAC_OnStructureCompleted(AvHAIBuildableStructure* NewStructure); void AITAC_OnStructureBeginRecycling(AvHAIBuildableStructure* RecyclingStructure); void AITAC_OnStructureDestroyed(AvHAIBuildableStructure* DestroyedStructure); void AITAC_LinkDeployedItemToAction(AvHAIPlayer* CommanderBot, const AvHAIDroppedItem* NewItem); -void AITAC_LinkAlienStructureToTask(AvHAIPlayer* pBot, AvHAIBuildableStructure* NewStructure); +void AITAC_LinkAlienStructureToPlayer(AvHAIBuildableStructure* NewStructure); float AITAC_GetPhaseDistanceBetweenPoints(const Vector StartPoint, const Vector EndPoint); diff --git a/main/source/mod/AIPlayers/AvHAITask.cpp b/main/source/mod/AIPlayers/AvHAITask.cpp index 5ed1710b..a28b2ef4 100644 --- a/main/source/mod/AIPlayers/AvHAITask.cpp +++ b/main/source/mod/AIPlayers/AvHAITask.cpp @@ -137,6 +137,8 @@ void AITASK_ClearBotTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) Task->LastBuildAttemptTime = 0.0f; Task->BuildAttempts = 0; Task->StructureType = STRUCTURE_NONE; + + memset(&pBot->BuildAttempts, 0, sizeof(AvHAIBuildAttempt)); } bool AITASK_IsTaskUrgent(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) @@ -714,8 +716,6 @@ bool AITASK_IsAlienCapResNodeTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* return false; } - if (!IsPlayerSkulk(pBot->Edict) && !IsPlayerGorge(pBot->Edict)) { return false; } - const AvHAIResourceNode* ResNodeIndex = AITAC_GetNearestResourceNodeToLocation(Task->TaskLocation); if (!ResNodeIndex) @@ -723,8 +723,53 @@ bool AITASK_IsAlienCapResNodeTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* return false; } + AvHTeamNumber BotTeam = pBot->Player->GetTeam(); + + // Don't waste resources switching down to gorge if we're a lerk, fade or onos + // but we can still clear the area of enemy structures + if (!IsPlayerSkulk(pBot->Edict) && !IsPlayerGorge(pBot->Edict)) + { + DeployableSearchFilter EnemyStructuresFilter; + EnemyStructuresFilter.DeployableTeam = AIMGR_GetEnemyTeam(BotTeam); + EnemyStructuresFilter.ReachabilityTeam = BotTeam; + EnemyStructuresFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; + EnemyStructuresFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); + + return AITAC_DeployableExistsAtLocation(ResNodeIndex->Location, &EnemyStructuresFilter); + } + + // We can attack structures basically if we aren't stuck with Gorge's spit attack + bool bCanAttackStructures = (!IsPlayerGorge(pBot->Edict) || PlayerHasWeapon(pBot->Player, WEAPON_GORGE_BILEBOMB)); + + if (ResNodeIndex->bIsOccupied) + { + // If we have a tower on the node, we can still help build it if it's not finished yet, + // or we can clear the area if we're able to + if (ResNodeIndex->OwningTeam == BotTeam) + { + if (IsPlayerGorge(pBot->Edict) && !UTIL_StructureIsFullyBuilt(ResNodeIndex->ActiveTowerEntity)) { return true; } + + // If the tower is fully built and we don't have bile bomb then our work here is done + if (!bCanAttackStructures) { return false; } + + // If we can clear the area of enemy junk then do so, otherwise we're finished + DeployableSearchFilter EnemyStructuresFilter; + EnemyStructuresFilter.DeployableTeam = AIMGR_GetEnemyTeam(BotTeam); + EnemyStructuresFilter.ReachabilityTeam = BotTeam; + EnemyStructuresFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; + EnemyStructuresFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); + + return AITAC_DeployableExistsAtLocation(ResNodeIndex->Location, &EnemyStructuresFilter); + } + else + { + // Enemy owns the res node, but we can cap it if we're able to attack structures, or there's a friend in the area who can. + return (bCanAttackStructures || AITAC_GetNumPlayersOfTeamInArea(BotTeam, ResNodeIndex->Location, UTIL_MetresToGoldSrcUnits(5.0f), false, pBot->Edict, AVH_USER3_ALIEN_PLAYER2) > 0); + } + } + // If another gorge is claiming this spot, then move on - if (!IsPlayerGorge(pBot->Edict) && !ResNodeIndex->bIsOccupied) + if (!IsPlayerGorge(pBot->Edict)) { edict_t* OtherBuilder = AITAC_GetNearestPlayerOfClassInArea(pBot->Player->GetTeam(), ResNodeIndex->Location, UTIL_MetresToGoldSrcUnits(5.0f), false, pBot->Edict, AVH_USER3_ALIEN_PLAYER2); @@ -732,36 +777,15 @@ bool AITASK_IsAlienCapResNodeTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* { if (GetPlayerResources(OtherBuilder) >= (int)(kResourceTowerCost * 0.7f)) { - return false; - } - } - } + // If we can clear the area of enemy junk then do so, otherwise we will let the other guy place the tower + DeployableSearchFilter EnemyStructuresFilter; + EnemyStructuresFilter.DeployableTeam = AIMGR_GetEnemyTeam(BotTeam); + EnemyStructuresFilter.ReachabilityTeam = BotTeam; + EnemyStructuresFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; + EnemyStructuresFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); - - if (ResNodeIndex->bIsOccupied) - { - if (IsPlayerGorge(pBot->Edict)) - { - if (ResNodeIndex->OwningTeam == pBot->Player->GetTeam()) - { - return !UTIL_StructureIsFullyBuilt(ResNodeIndex->ActiveTowerEntity); + return AITAC_DeployableExistsAtLocation(ResNodeIndex->Location, &EnemyStructuresFilter); } - else - { - return PlayerHasWeapon(pBot->Player, WEAPON_GORGE_BILEBOMB); - } - - } - else - { - return (ResNodeIndex->OwningTeam != pBot->Player->GetTeam()); - } - } - else - { - if (Task->BuildAttempts > 3) - { - return false; } } @@ -776,7 +800,9 @@ bool AITASK_IsMarineCapResNodeTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* if (!ResNodeIndex) { return false; } - // Always obey commander orders even if there's a bunch of other marines already there + AvHTeamNumber BotTeam = pBot->Player->GetTeam(); + + // Always obey commander orders even if there's a bunch of other marines already there, otherwise don't bother if someone else is securing it if (!Task->bIssuedByCommander) { int DesiredNumCappers = (ResNodeIndex->OwningTeam == AIMGR_GetEnemyTeam(pBot->Player->GetTeam())) ? 2 : 1; @@ -785,15 +811,24 @@ bool AITASK_IsMarineCapResNodeTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* if (NumMarinesNearby >= DesiredNumCappers && vDist2DSq(pBot->Edict->v.origin, Task->TaskLocation) > sqrf(UTIL_MetresToGoldSrcUnits(4.0f))) { return false; } } - if (ResNodeIndex->bIsOccupied) - { - if (ResNodeIndex->OwningTeam == pBot->Player->GetTeam()) - { - return (FNullEnt(ResNodeIndex->ActiveTowerEntity) || !UTIL_StructureIsFullyBuilt(ResNodeIndex->ActiveTowerEntity)); - } - } + // Obviously still valid task if the node is empty + if (!ResNodeIndex->bIsOccupied) { return true; } - return true; + // Also obviously still valid if it's owned by the enemy + if (ResNodeIndex->OwningTeam != BotTeam) { return true; } + + // Likewise, still valid if there isn't a tower or it's not fully built + if (FNullEnt(ResNodeIndex->ActiveTowerEntity) || !UTIL_StructureIsFullyBuilt(ResNodeIndex->ActiveTowerEntity)) { return true; } + + // At this point, the node is capped fully. However, don't consider a res node secured if the enemy still has their junk lying around. Clear it all out. + DeployableSearchFilter EnemyStructures; + EnemyStructures.DeployableTypes = SEARCH_ALL_STRUCTURES; + EnemyStructures.DeployableTeam = AIMGR_GetEnemyTeam(BotTeam); + EnemyStructures.ReachabilityTeam = BotTeam; + EnemyStructures.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; + EnemyStructures.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); + + return AITAC_DeployableExistsAtLocation(ResNodeIndex->Location, &EnemyStructures); } bool AITASK_IsDefendTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) @@ -823,69 +858,77 @@ bool AITASK_IsReinforceStructureTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTas if (!FNullEnt(Task->TaskSecondaryTarget) && !UTIL_StructureIsFullyBuilt(Task->TaskSecondaryTarget)) { return true; } - if (Task->TaskTarget->v.team != pBot->Player->GetTeam()) { return false; } + AvHTeamNumber BotTeam = pBot->Player->GetTeam(); + if (Task->TaskTarget->v.team != BotTeam) { return false; } + + // The reinforce structure task is true if we have an undecided hive available that we could build a new chamber with bool bActiveHiveWithoutTechExists = AITAC_TeamHiveWithTechExists(pBot->Player->GetTeam(), MESSAGE_NULL); if (bActiveHiveWithoutTechExists) { return true; } DeployableSearchFilter StructureFilter; - StructureFilter.DeployableTypes = STRUCTURE_ALIEN_OFFENCECHAMBER; + StructureFilter.DeployableTypes = STRUCTURE_ALIEN_OFFENCECHAMBER | ALIEN_BUILD_DEFENSE_CHAMBER | ALIEN_BUILD_MOVEMENT_CHAMBER | ALIEN_BUILD_SENSORY_CHAMBER; StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); StructureFilter.DeployableTeam = pBot->Player->GetTeam(); - // At least 2 offence chambers - int NumOffenceChambers = AITAC_GetNumDeployablesNearLocation(Task->TaskTarget->v.origin, &StructureFilter); + vector AllNearbyStructures = AITAC_FindAllDeployables(Task->TaskTarget->v.origin, &StructureFilter); - if (NumOffenceChambers < 2) { return true; } + bool bUnfinishedStructureExists = false; + int NumOffenceChambers = 0; + int NumDefenceChambers = 0; + int NumMovementChambers = 0; + int NumSensoryChambers = 0; - // At least 2 defence chambers, if the hive exists for it - if (AITAC_TeamHiveWithTechExists(pBot->Player->GetTeam(), ALIEN_BUILD_DEFENSE_CHAMBER)) + for (auto it = AllNearbyStructures.begin(); it != AllNearbyStructures.end(); it++) { - StructureFilter.DeployableTypes = STRUCTURE_ALIEN_DEFENCECHAMBER; - int NumDefenceChambers = AITAC_GetNumDeployablesNearLocation(Task->TaskTarget->v.origin, &StructureFilter); + AvHAIBuildableStructure* ThisStructure = (*it); - if (NumDefenceChambers < 2) { return true; } + if (!(ThisStructure->StructureStatusFlags & STRUCTURE_STATUS_COMPLETED)) { bUnfinishedStructureExists = true; } - StructureFilter.MaxSearchRadius = 0.0f; - int NumTotalDefenceChambers = AITAC_GetNumDeployablesNearLocation(Task->TaskTarget->v.origin, &StructureFilter); + switch (ThisStructure->StructureType) + { + case STRUCTURE_ALIEN_OFFENCECHAMBER: + NumOffenceChambers++; + break; + case STRUCTURE_ALIEN_DEFENCECHAMBER: + NumDefenceChambers++; + break; + case STRUCTURE_ALIEN_MOVEMENTCHAMBER: + NumMovementChambers++; + break; + case STRUCTURE_ALIEN_SENSORYCHAMBER: + NumSensoryChambers++; + break; + default: + break; - if (NumTotalDefenceChambers < 3) { return true; } + } } - // At least 1 movement and sensory chamber, if the hive exists for them - if (AITAC_TeamHiveWithTechExists(pBot->Player->GetTeam(), ALIEN_BUILD_MOVEMENT_CHAMBER)) - { - StructureFilter.DeployableTypes = STRUCTURE_ALIEN_MOVEMENTCHAMBER; - StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); + // Task is still valid if we have any missing structures, or we're a gorge at the target site and there is an incomplete structure that we can finish off - bool bHasMoveChamber = AITAC_DeployableExistsAtLocation(Task->TaskTarget->v.origin, &StructureFilter); + if (NumOffenceChambers < 2 + || (AITAC_TeamHiveWithTechExists(BotTeam, ALIEN_BUILD_DEFENSE_CHAMBER) && NumDefenceChambers < 2) + || (AITAC_TeamHiveWithTechExists(BotTeam, ALIEN_BUILD_MOVEMENT_CHAMBER) && NumMovementChambers < 1) + || (AITAC_TeamHiveWithTechExists(BotTeam, ALIEN_BUILD_SENSORY_CHAMBER) && NumSensoryChambers < 1) + || (IsPlayerGorge(pBot->Edict) && bUnfinishedStructureExists && vDist2DSq(pBot->Edict->v.origin, Task->TaskTarget->v.origin) <= sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) + ) { return true; } - if (!bHasMoveChamber) { return true; } + // Otherwise, are there any enemy structures lying around we could clear out? - StructureFilter.MaxSearchRadius = 0.0f; - int NumTotalMoveChambers = AITAC_GetNumDeployablesNearLocation(Task->TaskTarget->v.origin, &StructureFilter); + bool bCanAttackStructures = (!IsPlayerGorge(pBot->Edict) || PlayerHasWeapon(pBot->Player, WEAPON_GORGE_BILEBOMB)); - if (NumTotalMoveChambers < 3) { return true; } - } + if (!bCanAttackStructures) { return false; } - if (AITAC_TeamHiveWithTechExists(pBot->Player->GetTeam(), ALIEN_BUILD_SENSORY_CHAMBER)) - { - StructureFilter.DeployableTypes = STRUCTURE_ALIEN_SENSORYCHAMBER; - StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); + DeployableSearchFilter EnemyStuff; + EnemyStuff.DeployableTypes = SEARCH_ALL_STRUCTURES; + EnemyStuff.DeployableTeam = AIMGR_GetEnemyTeam(BotTeam); + EnemyStuff.ReachabilityTeam = BotTeam; + EnemyStuff.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; + EnemyStuff.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); - bool bHasSensoryChamber = AITAC_DeployableExistsAtLocation(Task->TaskTarget->v.origin, &StructureFilter); - - if (!bHasSensoryChamber) { return true; } - - StructureFilter.MaxSearchRadius = 0.0f; - int NumTotalSensoryChambers = AITAC_GetNumDeployablesNearLocation(Task->TaskTarget->v.origin, &StructureFilter); - - if (NumTotalSensoryChambers < 3) { return true; } - } - - // We have all available chambers set up - return false; + return AITAC_DeployableExistsAtLocation(Task->TaskTarget->v.origin, &EnemyStuff); } bool AITASK_IsMarineSecureHiveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) @@ -946,7 +989,18 @@ bool AITASK_IsMarineSecureHiveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* bool bSecuredResNode = (!ResNode || (ResNode->OwningTeam == BotTeam && !FNullEnt(ResNode->ActiveTowerEntity) && UTIL_StructureIsFullyBuilt(ResNode->ActiveTowerEntity))); - return !((!bPhaseGatesAvailable || bHasPhaseGate) && bHasTurretFactory && NumTurrets >= 5 && bSecuredResNode); + if ((bPhaseGatesAvailable && !bHasPhaseGate) || !bHasTurretFactory || NumTurrets < 5) { return true; } + + // Don't consider a hive secured if the enemy still has their junk in there. Clear it all out. + + DeployableSearchFilter EnemyStructures; + EnemyStructures.DeployableTypes = SEARCH_ALL_STRUCTURES; + EnemyStructures.DeployableTeam = AIMGR_GetEnemyTeam(BotTeam); + EnemyStructures.ReachabilityTeam = BotTeam; + EnemyStructures.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; + EnemyStructures.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); + + return AITAC_DeployableExistsAtLocation(HiveToSecure->FloorLocation, &EnemyStructures); } bool AITASK_IsEvolveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) @@ -1214,7 +1268,6 @@ void BotProgressReinforceStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) if (!FNullEnt(Task->TaskSecondaryTarget)) { - if (UTIL_StructureIsFullyBuilt(Task->TaskSecondaryTarget)) { Task->TaskSecondaryTarget = nullptr; @@ -1448,6 +1501,24 @@ void BotProgressReinforceStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) if (pBot->Player->GetResources() < ResRequired) { + if (!IsPlayerGorge(pBot->Edict) || PlayerHasWeapon(pBot->Player, WEAPON_GORGE_BILEBOMB)) + { + DeployableSearchFilter EnemyStuff; + EnemyStuff.DeployableTeam = AIMGR_GetEnemyTeam(pBot->Player->GetTeam()); + EnemyStuff.DeployableTypes = SEARCH_ALL_STRUCTURES; + EnemyStuff.ReachabilityTeam = pBot->Player->GetTeam(); + EnemyStuff.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; + EnemyStuff.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f); + + AvHAIBuildableStructure* NearestEnemyStructure = AITAC_FindClosestDeployableToLocation(Task->TaskTarget->v.origin, &EnemyStuff); + + if (NearestEnemyStructure) + { + BotAttackNonPlayerTarget(pBot, NearestEnemyStructure->edict); + return; + } + } + BotGuardLocation(pBot, Task->TaskLocation); return; } @@ -1460,7 +1531,7 @@ void BotProgressReinforceStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) if (!IsPlayerGorge(pBot->Edict)) { - AITASK_SetEvolveTask(pBot, &pBot->WantsAndNeedsTask, pBot->Edict->v.origin, ALIEN_LIFEFORM_TWO, true); + BotEvolveLifeform(pBot, pBot->Edict->v.origin, ALIEN_LIFEFORM_TWO); return; } @@ -1579,14 +1650,6 @@ void AIPlayerBuildStructure(AvHAIPlayer* pBot, edict_t* BuildTarget) MoveTo(pBot, BuildTarget->v.origin, MOVESTYLE_NORMAL); - if (IsPlayerMarine(pBot->Edict)) - { - if (gpGlobals->time - pBot->LastCombatTime > 5.0f) - { - BotReloadWeapons(pBot); - } - } - if (vDist2DSq(pBot->Edict->v.origin, BuildTarget->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) { BotLookAt(pBot, UTIL_GetCentreOfEntity(BuildTarget)); @@ -2204,107 +2267,140 @@ void AlienProgressCapResNodeTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) if (!ResNodeIndex) { return; } - int NumResourcesRequired = (IsPlayerGorge(pBot->Edict) ? BALANCE_VAR(kResourceTowerCost) : (BALANCE_VAR(kResourceTowerCost) + BALANCE_VAR(kGorgeCost))); + AvHTeamNumber BotTeam = pBot->Player->GetTeam(); - float DistFromNode = vDist2DSq(pBot->Edict->v.origin, Task->TaskLocation); + // We can attack structures if we're not a gorge stuck with spit as our only offensive weapon + bool bBotCanAttackStructures = !IsPlayerGorge(pBot->Edict) || PlayerHasWeapon(pBot->Player, WEAPON_GORGE_BILEBOMB); - if (DistFromNode > sqrf(UTIL_MetresToGoldSrcUnits(2.0f)) || !UTIL_QuickTrace(pBot->Edict, pBot->CurrentEyePosition, (Task->TaskLocation + Vector(0.0f, 0.0f, 50.0f)))) + // First, clear out any marine phase gates nearby so they can't send backup, if we can actually do meaningful damage to them + if (bBotCanAttackStructures) { - MoveTo(pBot, Task->TaskLocation, MOVESTYLE_NORMAL); - return; + AvHClassType EnemyClassType = AIMGR_GetEnemyTeamType(BotTeam); + + if (EnemyClassType == AVH_CLASS_TYPE_MARINE) + { + DeployableSearchFilter PGFilter; + PGFilter.DeployableTeam = AIMGR_GetEnemyTeam(BotTeam); + PGFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE; + PGFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); + PGFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + + AvHAIBuildableStructure* PG = AITAC_FindClosestDeployableToLocation(ResNodeIndex->Location, &PGFilter); + + if (PG) + { + BotAttackNonPlayerTarget(pBot, PG->edict); + return; + } + } } if (ResNodeIndex->bIsOccupied) { - Task->TaskTarget = ResNodeIndex->ActiveTowerEntity; - - if (ResNodeIndex->OwningTeam != pBot->Player->GetTeam()) - { - - if (IsPlayerGorge(pBot->Edict) && !PlayerHasWeapon(pBot->Player, WEAPON_GORGE_BILEBOMB)) - { - int NumAllies = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), Task->TaskLocation, UTIL_MetresToGoldSrcUnits(5.0f), false, pBot->Edict, AVH_USER3_ALIEN_PLAYER2); - if (NumAllies > 0) - { - BotGuardLocation(pBot, Task->TaskLocation); - } - else - { - BotEvolveLifeform(pBot, ALIEN_LIFEFORM_ONE); - } - } - else - { - AvHAIWeapon AttackWeapon = BotAlienChooseBestWeaponForStructure(pBot, Task->TaskTarget); - - float MaxRange = GetMaxIdealWeaponRange(AttackWeapon); - bool bHullSweep = IsMeleeWeapon(AttackWeapon); - - if (UTIL_PlayerHasLOSToEntity(pBot->Edict, Task->TaskTarget, MaxRange, bHullSweep)) - { - pBot->DesiredCombatWeapon = AttackWeapon; - - if (GetPlayerCurrentWeapon(pBot->Player) == AttackWeapon) - { - BotShootTarget(pBot, pBot->DesiredCombatWeapon, Task->TaskTarget); - return; - } - } - else - { - MoveTo(pBot, Task->TaskTarget->v.origin, MOVESTYLE_NORMAL); - } - } - return; - } - else + // If we have a tower on there already then help build it if we're gorge + if (ResNodeIndex->OwningTeam == BotTeam) { if (!UTIL_StructureIsFullyBuilt(ResNodeIndex->ActiveTowerEntity)) { - if (UTIL_PlayerHasLOSToEntity(pBot->Edict, ResNodeIndex->ActiveTowerEntity, max_player_use_reach, true)) + if (IsPlayerGorge(pBot->Edict)) { - BotUseObject(pBot, ResNodeIndex->ActiveTowerEntity, true); - if (vDist2DSq(pBot->Edict->v.origin, ResNodeIndex->ActiveTowerEntity->v.origin) > sqrf(60.0f)) - { - MoveDirectlyTo(pBot, ResNodeIndex->ActiveTowerEntity->v.origin); - } + AIPlayerBuildStructure(pBot, ResNodeIndex->ActiveTowerEntity); return; - } + } + } + } + else + { + // If the enemy owns it then destroy it if we can, or go Skulk to do so. + if (bBotCanAttackStructures) + { + BotAttackNonPlayerTarget(pBot, ResNodeIndex->ActiveTowerEntity); + return; + } + else + { + BotEvolveLifeform(pBot, pBot->Edict->v.origin, ALIEN_LIFEFORM_ONE); + return; + } + } + } + else + { + // Node is empty and not capped by either side - MoveTo(pBot, ResNodeIndex->ActiveTowerEntity->v.origin, MOVESTYLE_NORMAL); + int NumResourcesRequired = (IsPlayerGorge(pBot->Edict) ? BALANCE_VAR(kResourceTowerCost) : (BALANCE_VAR(kResourceTowerCost) + BALANCE_VAR(kGorgeCost))); - if (vDist2DSq(pBot->Edict->v.origin, ResNodeIndex->ActiveTowerEntity->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) + // We have enough resources to place the tower (includes cost of evolving to gorge if necessary) + if (pBot->Player->GetResources() >= NumResourcesRequired) + { + float CurrDist = vDist2DSq(pBot->CurrentFloorPosition, ResNodeIndex->Location); + + // Get close enough to place the tower if we aren't + if (CurrDist > sqrf(UTIL_MetresToGoldSrcUnits(3.0f))) + { + MoveTo(pBot, ResNodeIndex->Location, MOVESTYLE_NORMAL); + return; + } + + // Back up a bit if we're too close + if (CurrDist < sqrf(UTIL_MetresToGoldSrcUnits(1.0f))) + { + BotLookAt(pBot, ResNodeIndex->Location); + Vector Orientation = UTIL_GetVectorNormal2D(pBot->Edict->v.origin - ResNodeIndex->Location); + Vector NewMoveLoc = ResNodeIndex->Location + (Orientation * UTIL_MetresToGoldSrcUnits(2.0f)); + MoveToWithoutNav(pBot, NewMoveLoc); + + return; + } + + if (!IsPlayerGorge(pBot->Edict)) + { + BotEvolveLifeform(pBot, pBot->Edict->v.origin, ALIEN_LIFEFORM_TWO); + return; + } + else + { + BotLookAt(pBot, Task->TaskLocation); + + if (gpGlobals->time - Task->LastBuildAttemptTime < 1.0f) { return; } + + float LookDot = UTIL_GetDotProduct2D(UTIL_GetForwardVector2D(pBot->Edict->v.v_angle), UTIL_GetVectorNormal2D(Task->TaskLocation - pBot->Edict->v.origin)); + + if (LookDot > 0.9f) { - BotLookAt(pBot, UTIL_GetCentreOfEntity(ResNodeIndex->ActiveTowerEntity)); + + pBot->Impulse = ALIEN_BUILD_RESOURCES; + Task->LastBuildAttemptTime = gpGlobals->time + 1.0f; + Task->bIsWaitingForBuildLink = true; + Task->BuildAttempts++; } return; } } - - return; } - if (!IsPlayerGorge(pBot->Edict)) + // We don't have enough resources to cap the node yet, so take out any enemy structures in the area while we wait if we can + if (bBotCanAttackStructures) { - BotEvolveLifeform(pBot, ALIEN_LIFEFORM_TWO); - return; + DeployableSearchFilter EnemyStructureFilter; + EnemyStructureFilter.DeployableTeam = AIMGR_GetEnemyTeam(BotTeam); + EnemyStructureFilter.DeployableTypes = SEARCH_ALL_STRUCTURES; + EnemyStructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); + + AvHAIBuildableStructure* AttackTarget = AITAC_FindClosestDeployableToLocation(ResNodeIndex->Location, &EnemyStructureFilter); + + if (AttackTarget) + { + BotAttackNonPlayerTarget(pBot, AttackTarget->edict); + return; + } } - BotLookAt(pBot, Task->TaskLocation); + // No structures to take out, just wait for resources + BotGuardLocation(pBot, ResNodeIndex->Location); - if (gpGlobals->time - Task->LastBuildAttemptTime < 1.0f) { return; } - float LookDot = UTIL_GetDotProduct2D(UTIL_GetForwardVector2D(pBot->Edict->v.v_angle), UTIL_GetVectorNormal2D(Task->TaskLocation - pBot->Edict->v.origin)); - - if (LookDot > 0.9f) - { - - pBot->Impulse = ALIEN_BUILD_RESOURCES; - Task->LastBuildAttemptTime = gpGlobals->time + 1.0f; - Task->bIsWaitingForBuildLink = true; - Task->BuildAttempts++; - } } void BotProgressTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) @@ -2477,17 +2573,27 @@ void MarineProgressSecureHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) StructureFilter.DeployableTeam = BotTeam; StructureFilter.ReachabilityTeam = BotTeam; StructureFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; - StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING | STRUCTURE_STATUS_COMPLETED; + StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; vector BuildableStructures = AITAC_FindAllDeployables(Hive->FloorLocation, &StructureFilter); + bool bKeyStructureBuilt = false; + AvHAIBuildableStructure* StructureToBuild = nullptr; float MinDist = 0.0f; for (auto it = BuildableStructures.begin(); it != BuildableStructures.end(); it++) { AvHAIBuildableStructure* ThisStructure = (*it); + + if ((ThisStructure->StructureStatusFlags & STRUCTURE_STATUS_COMPLETED) && (ThisStructure->StructureType & (STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY | STRUCTURE_MARINE_PHASEGATE))) + { + bKeyStructureBuilt = true; + } + + if (ThisStructure->StructureStatusFlags & STRUCTURE_STATUS_COMPLETED) { continue; } + // Phase gates always take priority, so just go and build it if there is one if (ThisStructure->StructureType == STRUCTURE_MARINE_PHASEGATE) { AIPlayerBuildStructure(pBot, ThisStructure->edict); @@ -2515,8 +2621,13 @@ void MarineProgressSecureHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { if (ResNode->OwningTeam != BotTeam) { - BotAttackTarget(pBot, ResNode->ActiveTowerEntity); - return; + // Don't attack the RT until we have build a TF or PG. Avoids giving the game away too quickly + if (bKeyStructureBuilt) + { + BotAttackNonPlayerTarget(pBot, ResNode->ActiveTowerEntity); + return; + } + } else { @@ -2528,6 +2639,25 @@ void MarineProgressSecureHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) } } + // We won't start attacking enemy structures until we have built a turret factory or phase gate so we don't reveal our evil plans until we're ready + if (bKeyStructureBuilt) + { + DeployableSearchFilter EnemyStructures; + EnemyStructures.DeployableTypes = SEARCH_ALL_STRUCTURES; + EnemyStructures.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); + EnemyStructures.DeployableTeam = AIMGR_GetEnemyTeam(BotTeam); + EnemyStructures.ReachabilityTeam = BotTeam; + EnemyStructures.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; + + AvHAIBuildableStructure* EnemyStructure = AITAC_FindClosestDeployableToLocation(Hive->FloorLocation, &EnemyStructures); + + if (EnemyStructure) + { + BotAttackNonPlayerTarget(pBot, EnemyStructure->edict); + return; + } + } + BotGuardLocation(pBot, Task->TaskLocation); } @@ -2536,34 +2666,26 @@ void MarineProgressCapResNodeTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { if (!Task) { return; } - float DistFromNode = vDist2DSq(pBot->Edict->v.origin, Task->TaskLocation); + const AvHAIResourceNode* ResNodeIndex = AITAC_GetNearestResourceNodeToLocation(Task->TaskLocation); - if (DistFromNode > sqrf(UTIL_MetresToGoldSrcUnits(5.0f)) || !UTIL_QuickTrace(pBot->Edict, pBot->CurrentEyePosition, (Task->TaskLocation + Vector(0.0f, 0.0f, 50.0f)))) + // This shouldn't happen, but if somehow it does then at least do SOMETHING + if (!ResNodeIndex) { MoveTo(pBot, Task->TaskLocation, MOVESTYLE_NORMAL); - - if (gpGlobals->time - pBot->LastCombatTime > 5.0f) - { - BotReloadWeapons(pBot); - } - return; } - const AvHAIResourceNode* ResNodeIndex = AITAC_GetNearestResourceNodeToLocation(Task->TaskLocation); - - if (!ResNodeIndex) { return; } - + // There is a res tower, ours or the enemies if (ResNodeIndex->bIsOccupied) { Task->TaskTarget = ResNodeIndex->ActiveTowerEntity; - // Cancel the waiting timeout since a tower has been placed for us - Task->TaskLength = 0.0f; - + if (ResNodeIndex->OwningTeam == pBot->Player->GetTeam()) { if (!UTIL_StructureIsFullyBuilt(ResNodeIndex->ActiveTowerEntity)) { + // Cancel the waiting timeout since there is something for us to do + Task->TaskLength = 0.0f; AIPlayerBuildStructure(pBot, ResNodeIndex->ActiveTowerEntity); return; @@ -2571,29 +2693,63 @@ void MarineProgressCapResNodeTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) } else { - AvHAIWeapon AttackWeapon = BotMarineChooseBestWeaponForStructure(pBot, Task->TaskTarget); + // Cancel the waiting timeout since there is something for us to do + Task->TaskLength = 0.0f; - float MaxRange = GetMaxIdealWeaponRange(AttackWeapon); - bool bHullSweep = IsMeleeWeapon(AttackWeapon); + // If we're playing MvM, then check the enemy hasn't got a phase gate nearby which could bring in defenders. If so, take that out first. + AvHClassType EnemyType = AIMGR_GetEnemyTeamType(pBot->Player->GetTeam()); - if (UTIL_PlayerHasLOSToEntity(pBot->Edict, Task->TaskTarget, MaxRange, bHullSweep)) + if (EnemyType == AVH_CLASS_TYPE_MARINE) { - pBot->DesiredCombatWeapon = AttackWeapon; + DeployableSearchFilter EnemyStructureFilter; + EnemyStructureFilter.DeployableTeam = AIMGR_GetEnemyTeam(pBot->Player->GetTeam()); + EnemyStructureFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE; + EnemyStructureFilter.ReachabilityTeam = pBot->Player->GetTeam(); + EnemyStructureFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; + EnemyStructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); - if (GetPlayerCurrentWeapon(pBot->Player) == AttackWeapon) + AvHAIBuildableStructure* EnemyStructure = AITAC_FindClosestDeployableToLocation(Task->TaskLocation, &EnemyStructureFilter); + + if (EnemyStructure) { - //BotShootTarget(pBot, pBot->DesiredCombatWeapon, Task->TaskTarget); + BotAttackNonPlayerTarget(pBot, EnemyStructure->edict); + return; } } - else - { - MoveTo(pBot, Task->TaskTarget->v.origin, MOVESTYLE_NORMAL); - } + + BotAttackNonPlayerTarget(pBot, ResNodeIndex->ActiveTowerEntity); + return; } } + + // Clear out any enemy structures around the node + DeployableSearchFilter EnemyStructureFilter; + + EnemyStructureFilter.DeployableTeam = AIMGR_GetEnemyTeam(pBot->Player->GetTeam()); + EnemyStructureFilter.DeployableTypes = SEARCH_ALL_STRUCTURES; + EnemyStructureFilter.ReachabilityTeam = pBot->Player->GetTeam(); + EnemyStructureFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; + EnemyStructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); + + AvHAIBuildableStructure* EnemyStructure = AITAC_FindClosestDeployableToLocation(Task->TaskLocation, &EnemyStructureFilter); + + if (EnemyStructure) + { + // Cancel the waiting timeout since we have something useful to do + Task->TaskLength = 0.0f; + BotAttackNonPlayerTarget(pBot, EnemyStructure->edict); + return; + } else { - // Give the commander 30 seconds to drop a tower for us, or give up and move on + // If we're not at our destination yet, go there + if (vDist2DSq(pBot->Edict->v.origin, Task->TaskLocation) > UTIL_MetresToGoldSrcUnits(5.0f)) + { + MoveTo(pBot, Task->TaskLocation, MOVESTYLE_NORMAL); + return; + } + + // Empty res node with nothing to do but wait, stick around for 30 seconds and then move on if the commander doesn't drop an RT to build if (Task->TaskLength == 0.0f) { Task->TaskStartedTime = gpGlobals->time; @@ -3104,9 +3260,11 @@ void AITASK_SetCapResNodeTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, const Av AITASK_ClearBotTask(pBot, Task); + Vector WaitLocation = UTIL_GetRandomPointOnNavmeshInRadius(pBot->BotNavInfo.NavProfile, NodeRef->Location, UTIL_MetresToGoldSrcUnits(1.0f)); + Task->TaskType = TASK_CAP_RESNODE; Task->StructureType = NodeStructureType; - Task->TaskLocation = NodeRef->Location; + Task->TaskLocation = (!vIsZero(WaitLocation)) ? WaitLocation : NodeRef->Location; if (!FNullEnt(NodeRef->ActiveTowerEntity)) { @@ -3247,6 +3405,8 @@ void AITASK_SetReinforceStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, AITASK_ClearBotTask(pBot, Task); + if (FNullEnt(Target) || Target->v.deadflag != DEAD_NO) { return; } + Task->TaskType = TASK_REINFORCE_STRUCTURE; Task->TaskTarget = Target; Task->bTaskIsUrgent = bIsUrgent; diff --git a/main/source/mod/AIPlayers/AvHAIWeaponHelper.cpp b/main/source/mod/AIPlayers/AvHAIWeaponHelper.cpp index be15a302..55e1c995 100644 --- a/main/source/mod/AIPlayers/AvHAIWeaponHelper.cpp +++ b/main/source/mod/AIPlayers/AvHAIWeaponHelper.cpp @@ -682,6 +682,11 @@ AvHAIWeapon BotAlienChooseBestWeaponForStructure(AvHAIPlayer* pBot, edict_t* tar return WEAPON_GORGE_BILEBOMB; } + if (PlayerHasWeapon(pBot->Player, WEAPON_FADE_ACIDROCKET) && StructureType == STRUCTURE_ALIEN_HIVE || IsDamagingStructure(StructureType)) + { + return WEAPON_FADE_ACIDROCKET; + } + // If we have xenocide, then choose it if we have lots of good targets in blast radius if (PlayerHasWeapon(pBot->Player, WEAPON_SKULK_XENOCIDE)) { @@ -718,7 +723,7 @@ AvHAIWeapon BotMarineChooseBestWeaponForStructure(AvHAIPlayer* pBot, edict_t* ta { AvHAIDeployableStructureType StructureType = GetStructureTypeFromEdict(target); - if (StructureType == STRUCTURE_NONE) + if (StructureType == STRUCTURE_NONE || StructureType == STRUCTURE_ALIEN_HIVE || IsDamagingStructure(StructureType)) { if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0 || UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0) { @@ -734,34 +739,14 @@ AvHAIWeapon BotMarineChooseBestWeaponForStructure(AvHAIPlayer* pBot, edict_t* ta } } - if (StructureType == STRUCTURE_ALIEN_HIVE || StructureType == STRUCTURE_ALIEN_OFFENCECHAMBER) - { - if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0 || UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0) - { - return UTIL_GetPlayerPrimaryWeapon(pBot->Player); - } - else if (BotGetSecondaryWeaponClipAmmo(pBot) > 0 || BotGetSecondaryWeaponAmmoReserve(pBot) > 0) - { - return GetBotMarineSecondaryWeapon(pBot); - } - else - { - return WEAPON_MARINE_KNIFE; - } - } - else - { - AvHAIWeapon PrimaryWeapon = UTIL_GetPlayerPrimaryWeapon(pBot->Player); + AvHAIWeapon PrimaryWeapon = UTIL_GetPlayerPrimaryWeapon(pBot->Player); - if ((PrimaryWeapon == WEAPON_MARINE_GL || PrimaryWeapon == WEAPON_MARINE_SHOTGUN) && (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0 || UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0)) - { - return PrimaryWeapon; - } - - return WEAPON_MARINE_KNIFE; + if ((PrimaryWeapon == WEAPON_MARINE_GL || PrimaryWeapon == WEAPON_MARINE_SHOTGUN) && (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0 || UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0)) + { + return PrimaryWeapon; } - return UTIL_GetPlayerPrimaryWeapon(pBot->Player); + return WEAPON_MARINE_KNIFE; } AvHAIWeapon GorgeGetBestWeaponForCombatTarget(AvHAIPlayer* pBot, edict_t* Target) diff --git a/main/source/mod/AvHConsoleCommands.cpp b/main/source/mod/AvHConsoleCommands.cpp index bf77d7ee..c873c587 100644 --- a/main/source/mod/AvHConsoleCommands.cpp +++ b/main/source/mod/AvHConsoleCommands.cpp @@ -1498,27 +1498,39 @@ BOOL AvHGamerules::ClientCommand( CBasePlayer *pPlayer, const char *pcmd ) theSuccess = true; } - else if (FStrEq(pcmd, "testcommanderbuild")) + else if (FStrEq(pcmd, "setdebugaiplayer")) { - AvHAIPlayer* AIComm = AIMGR_GetAICommander(theAvHPlayer->GetTeam()); + CBaseEntity* SpectatedPlayer = theAvHPlayer->GetSpectatingEntity(); - if (AIComm) + if (SpectatedPlayer) { + AIMGR_SetDebugAIPlayer(SpectatedPlayer->edict()); + } - Vector TraceStart = GetPlayerEyePosition(theAvHPlayer->edict()); // origin + pev->view_ofs - Vector LookDir = UTIL_GetForwardVector(theAvHPlayer->edict()->v.v_angle); // Converts view angles to normalized unit vector + theSuccess = true; + } + else if (FStrEq(pcmd, "testalienreinforce")) + { + vector AlienPlayers = AIMGR_GetAIPlayersOnTeam(TEAM_TWO); - Vector TraceEnd = TraceStart + (LookDir * 1000.0f); + if (AlienPlayers.size() > 0) + { + AvHAIPlayer* NewCapper = AlienPlayers[0]; - TraceResult Hit; - - UTIL_TraceLine(TraceStart, TraceEnd, ignore_monsters, theAvHPlayer->edict(), &Hit); - - if (Hit.flFraction < 1.0f) + if (NewCapper) { - AICOMM_DeployStructure(AIComm, STRUCTURE_MARINE_ARMOURY, Hit.vecEndPos); - } + DeployableSearchFilter ResNodeFilter; + ResNodeFilter.DeployableTeam = TEAM_TWO; + ResNodeFilter.ReachabilityTeam = TEAM_TWO; + ResNodeFilter.ReachabilityFlags = NewCapper->BotNavInfo.NavProfile.ReachabilityFlag; + AvHAIResourceNode* ResNode = AITAC_FindNearestResourceNodeToLocation(NewCapper->Edict->v.origin, &ResNodeFilter); + + if (ResNode) + { + AITASK_SetReinforceStructureTask(NewCapper, &NewCapper->PrimaryBotTask, ResNode->ActiveTowerEntity, true); + } + } } theSuccess = true;