From 6021daababf3d2779397f881589f5b8e4316c732 Mon Sep 17 00:00:00 2001 From: RGreenlees Date: Thu, 2 May 2024 20:53:25 +0100 Subject: [PATCH] Wave attacking implementation --- main/source/mod/AvHAIConstants.h | 22 +- main/source/mod/AvHAIHelper.cpp | 65 +++-- main/source/mod/AvHAIHelper.h | 4 +- main/source/mod/AvHAIPlayer.cpp | 371 +++++++++++++++---------- main/source/mod/AvHAIPlayer.h | 5 +- main/source/mod/AvHAIPlayerManager.cpp | 33 ++- main/source/mod/AvHAIPlayerManager.h | 3 +- main/source/mod/AvHAIPlayerUtil.cpp | 2 + main/source/mod/AvHAITactical.cpp | 223 ++++++++++++++- main/source/mod/AvHAITactical.h | 6 + main/source/mod/AvHAITask.cpp | 149 +++++++++- main/source/mod/AvHAITask.h | 4 + main/source/mod/AvHConsoleCommands.cpp | 6 +- 13 files changed, 695 insertions(+), 198 deletions(-) diff --git a/main/source/mod/AvHAIConstants.h b/main/source/mod/AvHAIConstants.h index 34629fe2..3d2a0e25 100644 --- a/main/source/mod/AvHAIConstants.h +++ b/main/source/mod/AvHAIConstants.h @@ -323,7 +323,7 @@ typedef struct _BOT_MSG bool bIsTeamSay = false; // Is this a team-only message? } bot_msg; -typedef struct _BOT_GUARD_INFO +typedef struct _AVH_AI_GUARD_INFO { Vector GuardLocation = g_vecZero; // What position are we guarding? Vector GuardStandPosition = g_vecZero; // Where the bot should stand to guard position (moves around a bit) @@ -414,7 +414,8 @@ typedef enum TASK_TOUCH, TASK_REINFORCE_STRUCTURE, TASK_SECURE_HIVE, - TASK_PLACE_MINE + TASK_PLACE_MINE, + TASK_ATTACK_BASE } BotTaskType; @@ -809,7 +810,24 @@ typedef struct AVH_AI_PLAYER float HearingThreshold = 0.0f; // How loud does a sound need to be before the bot detects it? This is set when hearing a sound so that louder sounds drown out quieter ones, and decrements quickly + int DebugValue = 0; // Used for debugging the bot + } AvHAIPlayer; +typedef struct _AVH_AI_SQUAD +{ + AvHTeamNumber SquadTeam = TEAM_IND; // Which team this squad is for + vector SquadMembers; // Which bots are assigned to this + Vector SquadGatherLocation = g_vecZero; // Where should the squad gather before attempting the objective? + edict_t* SquadTarget = nullptr; // The target of the objective + BotTaskType SquadObjective = TASK_NONE; // What to do with the objective + bool bExecuteObjective = false; // Are we at the gather or execute phase? + + bool IsValid() + { + return (SquadMembers.size() > 0 && !FNullEnt(SquadTarget)); + } +} AvHAISquad; + #endif \ No newline at end of file diff --git a/main/source/mod/AvHAIHelper.cpp b/main/source/mod/AvHAIHelper.cpp index 7d836450..a2297f74 100644 --- a/main/source/mod/AvHAIHelper.cpp +++ b/main/source/mod/AvHAIHelper.cpp @@ -302,12 +302,12 @@ bool GetNearestMapLocationAtPoint(vec3_t SearchLocation, string& outLocation) return theSuccess; } -void AIDEBUG_DrawBotPath(AvHAIPlayer* pBot, float DrawTime) +void AIDEBUG_DrawBotPath(edict_t* OutputPlayer, AvHAIPlayer* pBot, float DrawTime) { - AIDEBUG_DrawPath(pBot->BotNavInfo.CurrentPath, DrawTime); + AIDEBUG_DrawPath(OutputPlayer, pBot->BotNavInfo.CurrentPath, DrawTime); } -void AIDEBUG_DrawPath(vector& path, float DrawTime) +void AIDEBUG_DrawPath(edict_t* OutputPlayer, vector& path, float DrawTime) { if (path.size() == 0) { return; } @@ -320,28 +320,28 @@ void AIDEBUG_DrawPath(vector& path, float DrawTime) { case SAMPLE_POLYFLAGS_WELD: case SAMPLE_POLYFLAGS_DOOR: - UTIL_DrawLine(INDEXENT(1), FromLoc, ToLoc, DrawTime, 255, 0, 0); + UTIL_DrawLine(OutputPlayer, FromLoc, ToLoc, DrawTime, 255, 0, 0); break; case SAMPLE_POLYFLAGS_JUMP: case SAMPLE_POLYFLAGS_DUCKJUMP: - UTIL_DrawLine(INDEXENT(1), FromLoc, ToLoc, DrawTime, 255, 255, 0); + UTIL_DrawLine(OutputPlayer, FromLoc, ToLoc, DrawTime, 255, 255, 0); break; case SAMPLE_POLYFLAGS_LADDER: case SAMPLE_POLYFLAGS_LIFT: - UTIL_DrawLine(INDEXENT(1), FromLoc, ToLoc, DrawTime, 0, 0, 255); + UTIL_DrawLine(OutputPlayer, FromLoc, ToLoc, DrawTime, 0, 0, 255); break; case SAMPLE_POLYFLAGS_WALLCLIMB: - UTIL_DrawLine(INDEXENT(1), FromLoc, ToLoc, DrawTime, 0, 128, 0); + UTIL_DrawLine(OutputPlayer, FromLoc, ToLoc, DrawTime, 0, 128, 0); break; case SAMPLE_POLYFLAGS_BLOCKED: - UTIL_DrawLine(INDEXENT(1), FromLoc, ToLoc, DrawTime, 128, 128, 128); + UTIL_DrawLine(OutputPlayer, FromLoc, ToLoc, DrawTime, 128, 128, 128); break; case SAMPLE_POLYFLAGS_TEAM1PHASEGATE: case SAMPLE_POLYFLAGS_TEAM2PHASEGATE: - UTIL_DrawLine(INDEXENT(1), FromLoc, ToLoc, DrawTime, 255, 128, 128); + UTIL_DrawLine(OutputPlayer, FromLoc, ToLoc, DrawTime, 255, 128, 128); break; default: - UTIL_DrawLine(INDEXENT(1), FromLoc, ToLoc, DrawTime); + UTIL_DrawLine(OutputPlayer, FromLoc, ToLoc, DrawTime); break; } } @@ -525,32 +525,29 @@ void UTIL_DrawHUDText(edict_t* pEntity, char channel, float x, float y, unsigned { if (FNullEnt(pEntity)) { return; } - float FrameDelta = AIMGR_GetFrameDelta(); - FrameDelta *= 256.0f; + float delta = AIMGR_GetFrameDelta(); - short Duration = (short)roundf(FrameDelta); + // TODO: Be able to turn these off as a preference + hudtextparms_t theTextParms; - MESSAGE_BEGIN(MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, NULL, pEntity); - WRITE_BYTE(TE_TEXTMESSAGE); - WRITE_BYTE(channel); // channel - WRITE_SHORT((int)(x * 8192.0f)); // x coordinates * 8192 - WRITE_SHORT((int)(y * 8192.0f)); // y coordinates * 8192 - WRITE_BYTE(0); // effect (fade in/out) - WRITE_BYTE(r); // initial RED - WRITE_BYTE(g); // initial GREEN - WRITE_BYTE(b); // initial BLUE - WRITE_BYTE(1); // initial ALPHA - WRITE_BYTE(r); // effect RED - WRITE_BYTE(g); // effect GREEN - WRITE_BYTE(b); // effect BLUE - WRITE_BYTE(1); // effect ALPHA - WRITE_SHORT(1); // fade-in time in seconds * 256 - WRITE_SHORT(Duration); // fade-out time in seconds * 256 - WRITE_SHORT(Duration); // hold time in seconds * 256 - WRITE_STRING(string);//string); // send the string - MESSAGE_END(); // end + // Init text parms + theTextParms.x = x; + theTextParms.y = y; + theTextParms.effect = 0; + theTextParms.r1 = 240; + theTextParms.g1 = 240; + theTextParms.b1 = 240; + theTextParms.a1 = 128; + theTextParms.r2 = 240; + theTextParms.g2 = 240; + theTextParms.b2 = 240; + theTextParms.a2 = 128; + theTextParms.fadeinTime = .0f; + theTextParms.fadeoutTime = .0f; + theTextParms.holdTime = 0.2f; + theTextParms.channel = channel; - return; + UTIL_HudMessage(CBaseEntity::Instance(pEntity), theTextParms, string); } void UTIL_ClearLocalizations() @@ -682,6 +679,8 @@ char* UTIL_TaskTypeToChar(const BotTaskType TaskType) return "Touch Trigger"; case TASK_WELD: return "Weld Target"; + case TASK_ATTACK_BASE: + return "Attack Enemy Base"; default: return "None"; } diff --git a/main/source/mod/AvHAIHelper.h b/main/source/mod/AvHAIHelper.h index a1654393..b2fea79e 100644 --- a/main/source/mod/AvHAIHelper.h +++ b/main/source/mod/AvHAIHelper.h @@ -39,8 +39,8 @@ bool GetNearestMapLocationAtPoint(vec3_t SearchLocation, string& outLocation); AvHAIDeployableStructureType GetDeployableObjectTypeFromEdict(const edict_t* StructureEdict); -void AIDEBUG_DrawBotPath(AvHAIPlayer* pBot, float DrawTime = 0.0f); -void AIDEBUG_DrawPath(vector& path, float DrawTime = 0.0f); +void AIDEBUG_DrawBotPath(edict_t* OutputPlayer, AvHAIPlayer* pBot, float DrawTime = 0.0f); +void AIDEBUG_DrawPath(edict_t* OutputPlayer, vector& path, float DrawTime = 0.0f); // Draws a white line between start and end for the given player (pEntity) for 0.1s void UTIL_DrawLine(edict_t* pEntity, Vector start, Vector end); diff --git a/main/source/mod/AvHAIPlayer.cpp b/main/source/mod/AvHAIPlayer.cpp index c50f5088..12be0f4b 100644 --- a/main/source/mod/AvHAIPlayer.cpp +++ b/main/source/mod/AvHAIPlayer.cpp @@ -21,6 +21,10 @@ extern nav_profile BaseNavProfiles[MAX_NAV_PROFILES]; // Array of nav profiles extern cvar_t avh_botdebugmode; +#ifdef BOTDEBUG +extern edict_t* DebugBots[MAX_PLAYERS]; +#endif + void BotJump(AvHAIPlayer* pBot) { if (pBot->BotNavInfo.IsOnGround) @@ -1861,7 +1865,8 @@ void EndBotFrame(AvHAIPlayer* pBot) void CustomThink(AvHAIPlayer* pBot) { - DEBUG_PrintCombatInfo(pBot); + // Test Combat Stuff + //DEBUG_PrintCombatInfo(INDEXENT(1), pBot); pBot->CurrentEnemy = BotGetNextEnemyTarget(pBot); @@ -1876,6 +1881,66 @@ void CustomThink(AvHAIPlayer* pBot) AlienCombatThink(pBot); } } + else + { + if (AITAC_ShouldBotBeCautious(pBot)) + { + MoveTo(pBot, AITAC_GetTeamStartingLocation(AIMGR_GetEnemyTeam(pBot->Player->GetTeam())), MOVESTYLE_AMBUSH); + } + else + { + MoveTo(pBot, AITAC_GetTeamStartingLocation(AIMGR_GetEnemyTeam(pBot->Player->GetTeam())), MOVESTYLE_NORMAL); + } + + } + + /*AITASK_BotUpdateAndClearTasks(pBot); + + if (pBot->PrimaryBotTask.TaskType != TASK_SECURE_HIVE) + { + AvHTeamNumber BotTeam = pBot->Player->GetTeam(); + AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam); + + AvHAIHiveDefinition* HiveToClear = nullptr; + float MinDist = 0.0f; + + vector Hives = AITAC_GetAllHives(); + + for (auto it = Hives.begin(); it != Hives.end(); it++) + { + AvHAIHiveDefinition* ThisHive = (*it); + + if (ThisHive->Status == HIVE_STATUS_UNBUILT) + { + DeployableSearchFilter EnemyStuffFilter; + EnemyStuffFilter.DeployableTeam = EnemyTeam; + EnemyStuffFilter.DeployableTypes = SEARCH_ALL_STRUCTURES; + EnemyStuffFilter.ReachabilityTeam = BotTeam; + EnemyStuffFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; + EnemyStuffFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); + + if (AITAC_DeployableExistsAtLocation(ThisHive->FloorLocation, &EnemyStuffFilter)) + { + float ThisDist = vDist2DSq(pBot->Edict->v.origin, ThisHive->FloorLocation); + + if (!HiveToClear || ThisDist < MinDist) + { + HiveToClear = ThisHive; + MinDist = ThisDist; + } + } + } + } + + if (HiveToClear) + { + AITASK_SetSecureHiveTask(pBot, &pBot->PrimaryBotTask, HiveToClear->HiveEdict, HiveToClear->FloorLocation, true); + } + } + else + { + BotProgressTask(pBot, &pBot->PrimaryBotTask); + }*/ } void DroneThink(AvHAIPlayer* pBot) @@ -2482,6 +2547,23 @@ AvHAICombatStrategy GetLerkCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_stat float EnemyHealthPercent = GetPlayerOverallHealthPercent(EnemyEdict); int NumAllies = AITAC_GetNumPlayersOnTeamWithLOS(EnemyTeam, EnemyEdict->v.origin, UTIL_MetresToGoldSrcUnits(20.0f), EnemyEdict); + DeployableSearchFilter TurretFilter; + TurretFilter.DeployableTeam = EnemyTeam; + TurretFilter.DeployableTypes = (STRUCTURE_MARINE_TURRET | STRUCTURE_ALIEN_OFFENCECHAMBER); + TurretFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + TurretFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + TurretFilter.MaxSearchRadius = BALANCE_VAR(kTurretRange); + + vector Turrets = AITAC_FindAllDeployables(EnemyEdict->v.origin, &TurretFilter); + + for (auto it = Turrets.begin(); it != Turrets.end(); it++) + { + if (UTIL_QuickTrace(pBot->Edict, GetPlayerTopOfCollisionHull(it->edict), EnemyEdict->v.origin)) + { + NumAllies++; + } + } + float DistToEnemy = vDist2DSq(pBot->Edict->v.origin, EnemyEdict->v.origin); float RetreatHealthPercent = (NumAllies > 1) ? 0.5f : 0.35f; @@ -2775,7 +2857,7 @@ AvHAICombatStrategy GetMarineCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_st case TASK_CAP_RESNODE: case TASK_GUARD: case TASK_SECURE_HIVE: - ThreatThreshold = 1.0f; + ThreatThreshold = (IsPlayerMarine(pBot->Edict)) ? 1.0f : 2.0f; break; default: break; @@ -4370,17 +4452,9 @@ bool AIPlayerMustFinishCurrentTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) if (Task->ActiveBuildInfo.BuildStatus == BUILD_ATTEMPT_PENDING) { return true; } // If we're already capping a node, are at the node and there is an unfinished tower on there, then finish the job and don't move on yet - if (Task->TaskType == TASK_CAP_RESNODE) + if (Task->TaskType == TASK_CAP_RESNODE && vDist2DSq(pBot->Edict->v.origin, Task->TaskLocation) < sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) { - const AvHAIResourceNode* ResNodeIndex = AITAC_GetNearestResourceNodeToLocation(Task->TaskLocation); - - if (ResNodeIndex && ResNodeIndex->OwningTeam == BotTeam) - { - if (!FNullEnt(ResNodeIndex->ActiveTowerEntity) && !UTIL_StructureIsFullyBuilt(ResNodeIndex->ActiveTowerEntity)) - { - return true; - } - } + return true; } } @@ -5524,17 +5598,22 @@ void AIPlayerDMThink(AvHAIPlayer* pBot) void AIPlayerThink(AvHAIPlayer* pBot) { -//#ifdef DEBUG - if (pBot == AIMGR_GetDebugAIPlayer()) +#ifdef BOTDEBUG + for (int i = 0; i < gpGlobals->maxClients; i++) { - bool bBreak = true; // Add a break point here if you want to debug a specific bot + edict_t* PlayerEdict = INDEXENT(i + 1); - AIDEBUG_DrawBotPath(pBot); + if (DebugBots[i] == pBot->Edict && !FNullEnt(PlayerEdict) && IsEdictPlayer(PlayerEdict) && IsPlayerHuman(PlayerEdict)) + { + AvHAIPlayer* BotRef = AIMGR_GetBotRefFromEdict(DebugBots[i]); - DEBUG_PrintTaskInfo(pBot); - DEBUG_PrintCombatInfo(pBot); + if (BotRef) + { + DEBUG_PrintBotDebugInfo(PlayerEdict, BotRef); + } + } } -//#endif +#endif pBot->ThinkDelta = fminf(gpGlobals->time - pBot->LastThinkTime, 0.1f); pBot->LastThinkTime = gpGlobals->time; @@ -6327,6 +6406,8 @@ void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task if (Task->TaskType == TASK_EVOLVE) { return; } + // CHECK TO SEE IF WE NEED TO EVOLVE INTO FADE/ONOS + if (!IsPlayerFade(pBot->Edict) && !IsPlayerOnos(pBot->Edict)) { @@ -6366,6 +6447,8 @@ void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task } + // CHECK TO SEE IF WE NEED TO HELP BREAK A SIEGE THAT IS ACTIVE (I.E. HAS SIEGE TURRETS) + const AvHAIHiveDefinition* NearestSiegedHive = AITAC_GetNearestHiveUnderActiveSiege(EnemyTeam, pBot->Edict->v.origin); if (NearestSiegedHive) @@ -6434,7 +6517,8 @@ void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task } } - // If we're up against marines, look out for any siege stuff + // CHECK IF MARINES ARE TRYING TO BUILD A SIEGE BASE + if (AIMGR_GetTeamType(EnemyTeam) == AVH_CLASS_TYPE_MARINE) { // Check if we're already trying to break a siege attempt, so we don't get torn between multiple potentials @@ -6513,11 +6597,11 @@ void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task } + // CHECK IF WE NEED TO HOLD AN EMPTY HIVE FOR US TO BUILD IN Vector EnemyBaseLocation = AITAC_GetTeamStartingLocation(EnemyTeam); AvHAIHiveDefinition* HiveToGuard = nullptr; - AvHAIHiveDefinition* HiveToSecure = nullptr; vector AllHives = AITAC_GetAllHives(); @@ -6551,84 +6635,72 @@ void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task for (auto it = AllHives.begin(); it != AllHives.end(); it++) { + if (!bShouldGuardEmptyHive) { continue; } + AvHAIHiveDefinition* ThisHive = (*it); - if (ThisHive->OwningTeam != TEAM_IND) { continue; } + if (ThisHive->Status != HIVE_STATUS_UNBUILT) { continue; } bool bEnemyIsSecuring = AITAC_DeployableExistsAtLocation(ThisHive->FloorLocation, &EnemyStuffFilter); - if (bEnemyIsSecuring) + if (bEnemyIsSecuring) { continue; } + + DeployableSearchFilter FriendlyStuffFilter; + FriendlyStuffFilter.DeployableTeam = BotTeam; + FriendlyStuffFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); + FriendlyStuffFilter.DeployableTypes = STRUCTURE_ALIEN_OFFENCECHAMBER; + + // Don't guard a hive if some defences are already present + if (AITAC_GetNumDeployablesNearLocation(ThisHive->FloorLocation, &FriendlyStuffFilter) >= 2) { continue; } + + // Only guard empty hives if a gorge is in there + if (AITAC_GetNumPlayersOfTeamAndClassInArea(BotTeam, ThisHive->FloorLocation, UTIL_MetresToGoldSrcUnits(20.0f), false, pBot->Edict, AVH_USER3_ALIEN_PLAYER2) == 0) { continue; } + + bool bNeedsExtraGuards = true; + int NumGuards = 0; + + vector HumanPlayers = AIMGR_GetNonAIPlayersOnTeam(BotTeam); + vector AITeamPlayers = AIMGR_GetAIPlayersOnTeam(BotTeam); + + for (auto AIIt = AITeamPlayers.begin(); AIIt != AITeamPlayers.end(); AIIt++) + { + if ((*AIIt) == pBot) { continue; } + + if ((*AIIt)->PrimaryBotTask.TaskType == TASK_GUARD && (*AIIt)->PrimaryBotTask.TaskTarget == ThisHive->HiveEdict) + { + if ((*AIIt)->Player->GetUser3() >= AVH_USER3_ALIEN_PLAYER3) { bNeedsExtraGuards = false; } + NumGuards++; + } + } + + for (auto GuardIt = HumanPlayers.begin(); GuardIt != HumanPlayers.end(); GuardIt++) + { + AvHPlayer* ThisGuard = (*GuardIt); + + if (IsPlayerActiveInGame(ThisGuard->edict()) && vDist2DSq(ThisGuard->edict()->v.origin, ThisHive->FloorLocation) < sqrf(UTIL_MetresToGoldSrcUnits(15.0f))) + { + if (ThisGuard->GetUser3() >= AVH_USER3_ALIEN_PLAYER3) { bNeedsExtraGuards = false; } + NumGuards++; + } + } + + bNeedsExtraGuards = bNeedsExtraGuards && NumGuards < 2; + + if (bNeedsExtraGuards) { float ThisDist = vDist2DSq(ThisHive->FloorLocation, EnemyBaseLocation); if (ThisDist > MaxSecureDist) { - HiveToSecure = ThisHive; - MaxSecureDist = ThisDist; + HiveToGuard = ThisHive; + MaxGuardDist = ThisDist; } } else { - if (!bShouldGuardEmptyHive) { continue; } - - DeployableSearchFilter FriendlyStuffFilter; - - FriendlyStuffFilter.DeployableTeam = BotTeam; - FriendlyStuffFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); - FriendlyStuffFilter.DeployableTypes = STRUCTURE_ALIEN_OFFENCECHAMBER; - - // Don't guard a hive if some defences are already present - if (AITAC_GetNumDeployablesNearLocation(ThisHive->FloorLocation, &FriendlyStuffFilter) >= 2) { continue; } - - // Only guard empty hives if a gorge is in there - if (AITAC_GetNumPlayersOfTeamAndClassInArea(BotTeam, ThisHive->FloorLocation, UTIL_MetresToGoldSrcUnits(20.0f), false, pBot->Edict, AVH_USER3_ALIEN_PLAYER2) == 0) { continue; } - - bool bNeedsExtraGuards = true; - int NumGuards = 0; - - vector HumanPlayers = AIMGR_GetNonAIPlayersOnTeam(BotTeam); - vector AITeamPlayers = AIMGR_GetAIPlayersOnTeam(BotTeam); - - for (auto AIIt = AITeamPlayers.begin(); AIIt != AITeamPlayers.end(); AIIt++) - { - if ((*AIIt) == pBot) { continue; } - - if ((*AIIt)->PrimaryBotTask.TaskType == TASK_GUARD && (*AIIt)->PrimaryBotTask.TaskTarget == ThisHive->HiveEdict) - { - if ((*AIIt)->Player->GetUser3() >= AVH_USER3_ALIEN_PLAYER3) { bNeedsExtraGuards = false; } - NumGuards++; - } - } - - for (auto GuardIt = HumanPlayers.begin(); GuardIt != HumanPlayers.end(); GuardIt++) - { - AvHPlayer* ThisGuard = (*GuardIt); - - if (IsPlayerActiveInGame(ThisGuard->edict()) && vDist2DSq(ThisGuard->edict()->v.origin, ThisHive->FloorLocation) < sqrf(UTIL_MetresToGoldSrcUnits(15.0f))) - { - if (ThisGuard->GetUser3() >= AVH_USER3_ALIEN_PLAYER3) { bNeedsExtraGuards = false; } - NumGuards++; - } - } - - bNeedsExtraGuards = bNeedsExtraGuards && NumGuards < 2; - - if (bNeedsExtraGuards) - { - float ThisDist = vDist2DSq(ThisHive->FloorLocation, EnemyBaseLocation); - - if (ThisDist > MaxSecureDist) - { - HiveToGuard = ThisHive; - MaxGuardDist = ThisDist; - } - } - else - { - // The purpose of this is to ensure we only guard one empty hive at a time, otherwise all the assault bots will be sitting around in empty hives and not pressuring marines - // If we have an empty hive already being guarded, then this bool will ensure the bot doesn't go guard an empty hive even if there are 2 - bShouldGuardEmptyHive = false; - } + // The purpose of this is to ensure we only guard one empty hive at a time, otherwise all the assault bots will be sitting around in empty hives and not pressuring marines + // If we have an empty hive already being guarded, then this bool will ensure the bot doesn't go guard an empty hive even if there are 2 + bShouldGuardEmptyHive = false; } } @@ -6641,70 +6713,61 @@ void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task Task->TaskLength = 60.0f; return; } - else if (HiveToSecure) + + // FIND A HIVE TO RETAKE. PICK THE WEAKEST ONE + + int MaxHiveStrength = 0; + AvHAIHiveDefinition* HiveToSecure = nullptr; + + for (auto it = AllHives.begin(); it != AllHives.end(); it++) { - // Check if we're already trying to clear out a hive - if (Task->TaskType == TASK_ATTACK) + AvHAIHiveDefinition* ThisHive = (*it); + + if (ThisHive->OwningTeam == BotTeam) { continue; } + + vector EnemyStructures = AITAC_FindAllDeployables(ThisHive->FloorLocation, &EnemyStuffFilter); + + // Enemy hasn't built anything here, so doesn't need clearing + if (ThisHive->OwningTeam != EnemyTeam && EnemyStructures.size() == 0) { continue; } + + int ThisStrength = 0; + + for (auto StructureIt = EnemyStructures.begin(); StructureIt != EnemyStructures.end(); StructureIt++) { - const AvHAIHiveDefinition* HiveNearestAttackTarget = AITAC_GetNearestTeamHive(BotTeam, Task->TaskTarget->v.origin, false); - - if (HiveNearestAttackTarget && vDist2DSq(HiveNearestAttackTarget->Location, Task->TaskTarget->v.origin) <= sqrf(UTIL_MetresToGoldSrcUnits(15.0f))) { return; } - } - - EnemyStuffFilter.DeployableTypes = SEARCH_ALL_STRUCTURES; - - // Don't attack electrified structures as skulk - if (pBot->Player->GetUser3() < AVH_USER3_ALIEN_PLAYER4) - { - EnemyStuffFilter.ExcludeStatusFlags = STRUCTURE_STATUS_ELECTRIFIED; - } - - vector AllEnemyThings = AITAC_FindAllDeployables(HiveToSecure->FloorLocation, &EnemyStuffFilter); - - AvHAIBuildableStructure StructureToAttack; - - for (auto it = AllEnemyThings.begin(); it != AllEnemyThings.end(); it++) - { - AvHAIBuildableStructure ThisStructure = (*it); - - // First prioritise phase gates or alien OCs - if (ThisStructure.StructureType == STRUCTURE_MARINE_PHASEGATE || ThisStructure.StructureType == STRUCTURE_ALIEN_OFFENCECHAMBER) + switch (StructureIt->StructureType) { - if (FNullEnt(StructureToAttack.edict) || StructureToAttack.StructureType != ThisStructure.StructureType || vDist2DSq(pBot->Edict->v.origin, ThisStructure.Location) < vDist2DSq(pBot->Edict->v.origin, StructureToAttack.Location)) - { - StructureToAttack = ThisStructure; - continue; - } - } - - if (!FNullEnt(StructureToAttack.edict) && (StructureToAttack.StructureType == STRUCTURE_MARINE_PHASEGATE || ThisStructure.StructureType == STRUCTURE_ALIEN_OFFENCECHAMBER)) { continue; } - - // Then prioritise turret factories - if (ThisStructure.StructureType == STRUCTURE_MARINE_TURRETFACTORY || ThisStructure.StructureType == STRUCTURE_MARINE_ADVTURRETFACTORY) - { - if (FNullEnt(StructureToAttack.edict) || StructureToAttack.StructureType != ThisStructure.StructureType || vDist2DSq(pBot->Edict->v.origin, ThisStructure.Location) < vDist2DSq(pBot->Edict->v.origin, StructureToAttack.Location)) - { - StructureToAttack = ThisStructure; - continue; - } - } - - if (!FNullEnt(StructureToAttack.edict) && (StructureToAttack.StructureType == STRUCTURE_MARINE_TURRETFACTORY || ThisStructure.StructureType == STRUCTURE_MARINE_ADVTURRETFACTORY)) { continue; } - - // Then target any other structures - if (FNullEnt(StructureToAttack.edict) || vDist2DSq(pBot->Edict->v.origin, ThisStructure.Location) < vDist2DSq(pBot->Edict->v.origin, StructureToAttack.Location)) - { - StructureToAttack = ThisStructure; + case STRUCTURE_MARINE_PHASEGATE: + ThisStrength += 2; + break; + case STRUCTURE_MARINE_TURRETFACTORY: + ThisStrength += (UTIL_IsStructureElectrified(StructureIt->edict)) ? 2 : 1; + break; + case STRUCTURE_MARINE_TURRET: + ThisStrength += 1; + break; + case STRUCTURE_ALIEN_OFFENCECHAMBER: + ThisStrength += 1; + break; + default: + break; } } - if (StructureToAttack.IsValid()) + if (!HiveToSecure || ThisStrength < MaxHiveStrength) { - AITASK_SetAttackTask(pBot, Task, StructureToAttack.edict, false); - return; + HiveToSecure = ThisHive; + MaxHiveStrength = ThisStrength; } } + if (HiveToSecure) + { + AITASK_SetSecureHiveTask(pBot, Task, HiveToSecure->HiveEdict, HiveToSecure->FloorLocation, false); + return; + } + + // ATTACK THE ENEMY BASE + DeployableSearchFilter EnemyInfPortalFilter; EnemyInfPortalFilter.DeployableTypes = STRUCTURE_MARINE_INFANTRYPORTAL; EnemyInfPortalFilter.DeployableTeam = EnemyTeam; @@ -6730,6 +6793,8 @@ void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task return; } + // FIND ANY LAST ENEMIES TO KILL AND END GAME + vector AllEnemyPlayers = AIMGR_GetAllPlayersOnTeam(EnemyTeam); edict_t* TargetPlayer = nullptr; @@ -8468,7 +8533,7 @@ bool OnosCombatThink(AvHAIPlayer* pBot) return true; } -void DEBUG_PrintTaskInfo(AvHAIPlayer* pBot) +void DEBUG_PrintTaskInfo(edict_t* OutputPlayer, AvHAIPlayer* pBot) { char buf[511]; char interbuf[164]; @@ -8516,19 +8581,19 @@ void DEBUG_PrintTaskInfo(AvHAIPlayer* pBot) if (!FNullEnt(pBot->CurrentTask->TaskTarget)) { - UTIL_DrawLine(INDEXENT(1), pBot->Edict->v.origin, pBot->CurrentTask->TaskTarget->v.origin, 255, 0, 0); + UTIL_DrawLine(OutputPlayer, pBot->Edict->v.origin, pBot->CurrentTask->TaskTarget->v.origin, 255, 0, 0); } if (!vIsZero(pBot->CurrentTask->TaskLocation)) { - UTIL_DrawLine(INDEXENT(1), pBot->Edict->v.origin, pBot->CurrentTask->TaskLocation, 255, 255, 0); + UTIL_DrawLine(OutputPlayer, pBot->Edict->v.origin, pBot->CurrentTask->TaskLocation, 255, 255, 0); } } - UTIL_DrawHUDText(INDEXENT(1), 0, 0.1f, 0.1f, 255, 255, 255, buf); + UTIL_DrawHUDText(OutputPlayer, 0, 0.1, 0.1f, 255, 255, 255, buf); } -void DEBUG_PrintCombatInfo(AvHAIPlayer* pBot) +void DEBUG_PrintCombatInfo(edict_t* OutputPlayer, AvHAIPlayer* pBot) { char buf[511]; char interbuf[164]; @@ -8550,7 +8615,7 @@ void DEBUG_PrintCombatInfo(AvHAIPlayer* pBot) if (TrackedEnemy < 0) { - UTIL_DrawHUDText(INDEXENT(1), 1, 0.6f, 0.1f, 255, 255, 255, buf); + UTIL_DrawHUDText(OutputPlayer, 1, 0.6f, 0.1f, 255, 255, 255, buf); return; } @@ -8606,11 +8671,23 @@ void DEBUG_PrintCombatInfo(AvHAIPlayer* pBot) strcat(buf, interbuf); - UTIL_DrawHUDText(INDEXENT(1), 1, 0.6f, 0.1f, 255, 255, 255, buf); + UTIL_DrawHUDText(OutputPlayer, 1, 0.6f, 0.1f, 255, 255, 255, buf); if (!vIsZero(TrackedInfo->LastDetectedLocation)) { - UTIL_DrawLine(INDEXENT(1), pBot->Edict->v.origin, TrackedInfo->LastDetectedLocation, 255, 0, 0); + UTIL_DrawLine(OutputPlayer, pBot->Edict->v.origin, TrackedInfo->LastDetectedLocation, 255, 0, 0); } +} + +void DEBUG_PrintBotDebugInfo(edict_t* OutputPlayer, AvHAIPlayer* pBot) +{ + if (FNullEnt(OutputPlayer) || OutputPlayer->free) { return; } + + bool bBreak = true; // Add a break point here if you want to debug a specific bot + + AIDEBUG_DrawBotPath(OutputPlayer, pBot); + + DEBUG_PrintTaskInfo(OutputPlayer, pBot); + DEBUG_PrintCombatInfo(OutputPlayer, pBot); } \ No newline at end of file diff --git a/main/source/mod/AvHAIPlayer.h b/main/source/mod/AvHAIPlayer.h index 5b788fe0..f5fb0815 100644 --- a/main/source/mod/AvHAIPlayer.h +++ b/main/source/mod/AvHAIPlayer.h @@ -184,7 +184,8 @@ bool OnosCombatThink(AvHAIPlayer* pBot); bool BombardierCombatThink(AvHAIPlayer* pBot); bool RegularMarineCombatThink(AvHAIPlayer* pBot); -void DEBUG_PrintTaskInfo(AvHAIPlayer* pBot); -void DEBUG_PrintCombatInfo(AvHAIPlayer* pBot); +void DEBUG_PrintBotDebugInfo(edict_t* OutputPlayer, AvHAIPlayer* pBot); +void DEBUG_PrintTaskInfo(edict_t* OutputPlayer, AvHAIPlayer* pBot); +void DEBUG_PrintCombatInfo(edict_t* OutputPlayer, AvHAIPlayer* pBot); #endif \ No newline at end of file diff --git a/main/source/mod/AvHAIPlayerManager.cpp b/main/source/mod/AvHAIPlayerManager.cpp index dbb6b750..844a93c5 100644 --- a/main/source/mod/AvHAIPlayerManager.cpp +++ b/main/source/mod/AvHAIPlayerManager.cpp @@ -54,6 +54,10 @@ bool bBotsEnabled = false; float CurrentFrameDelta = 0.01f; +#ifdef BOTDEBUG +edict_t* DebugBots[MAX_PLAYERS]; +#endif + AvHAICommanderMode AIMGR_GetCommanderMode() { if (avh_botcommandermode.value == 1) @@ -599,6 +603,7 @@ void AIMGR_UpdateAIPlayers() } AIMGR_ProcessPendingSounds(); + AITAC_UpdateSquads(); } int NumCommanders = AIMGR_GetNumAICommanders(); @@ -889,7 +894,11 @@ void AIMGR_ResetRound() { if (!AIMGR_IsBotEnabled()) { return; } // Do nothing if we're not using bots, as the data will be cleared out via AIMGR_OnBotDisabled() - AITAC_ClearMapAIData(false); + AITAC_ClearMapAIData(false); + +#ifdef BOTDEBUG + memset(DebugBots, 0, sizeof(DebugBots)); +#endif // AI Players would be 0 if the round is being reset because a new game is starting. If the round is reset // from a console command, or tournament mode readying up etc, then bot logic is unaffected @@ -1084,6 +1093,16 @@ AvHAIPlayer* AIMGR_GetBotRefFromPlayer(AvHPlayer* PlayerRef) return nullptr; } +AvHAIPlayer* AIMGR_GetBotRefFromEdict(edict_t* PlayerEdict) +{ + for (auto BotIt = ActiveAIPlayers.begin(); BotIt != ActiveAIPlayers.end(); BotIt++) + { + if (BotIt->Edict == PlayerEdict) { return &(*BotIt); } + } + + return nullptr; +} + AvHTeamNumber AIMGR_GetEnemyTeam(const AvHTeamNumber FriendlyTeam) { AvHTeamNumber TeamANumber = GetGameRules()->GetTeamANumber(); @@ -1259,11 +1278,14 @@ AvHAIPlayer* AIMGR_GetDebugAIPlayer() return DebugAIPlayer; } -void AIMGR_SetDebugAIPlayer(edict_t* AIPlayer) +void AIMGR_SetDebugAIPlayer(edict_t* SpectatingPlayer, edict_t* AIPlayer) { +#ifdef BOTDEBUG + int PlayerIndex = ENTINDEX(SpectatingPlayer) - 1; + if (FNullEnt(AIPlayer)) { - DebugAIPlayer = nullptr; + DebugBots[PlayerIndex] = nullptr; return; } @@ -1271,12 +1293,13 @@ void AIMGR_SetDebugAIPlayer(edict_t* AIPlayer) { if (it->Edict == AIPlayer) { - DebugAIPlayer = &(*it); + DebugBots[PlayerIndex] = it->Edict; return; } } - DebugAIPlayer = nullptr; + DebugBots[PlayerIndex] = nullptr; +#endif } void AIMGR_ReceiveCommanderRequest(AvHTeamNumber Team, edict_t* Requestor, const char* Request) diff --git a/main/source/mod/AvHAIPlayerManager.h b/main/source/mod/AvHAIPlayerManager.h index 9b50d6af..218a8fa1 100644 --- a/main/source/mod/AvHAIPlayerManager.h +++ b/main/source/mod/AvHAIPlayerManager.h @@ -86,6 +86,7 @@ void AIMGR_ReloadNavigationData(); AvHAIPlayer* AIMGR_GetAICommander(AvHTeamNumber Team); AvHAIPlayer* AIMGR_GetBotRefFromPlayer(AvHPlayer* PlayerRef); +AvHAIPlayer* AIMGR_GetBotRefFromEdict(edict_t* PlayerEdict); AvHTeamNumber AIMGR_GetEnemyTeam(const AvHTeamNumber FriendlyTeam); AvHClassType AIMGR_GetEnemyTeamType(const AvHTeamNumber FriendlyTeam); @@ -108,7 +109,7 @@ vector AIMGR_GetNonAIPlayersOnTeam(AvHTeamNumber Team); void AIMGR_ClearBotData(); AvHAIPlayer* AIMGR_GetDebugAIPlayer(); -void AIMGR_SetDebugAIPlayer(edict_t* AIPlayer); +void AIMGR_SetDebugAIPlayer(edict_t* SpectatingPlayer, edict_t* AIPlayer); void AIMGR_ReceiveCommanderRequest(AvHTeamNumber Team, edict_t* Requestor, const char* Request); diff --git a/main/source/mod/AvHAIPlayerUtil.cpp b/main/source/mod/AvHAIPlayerUtil.cpp index c36f3a53..be7d2c1f 100644 --- a/main/source/mod/AvHAIPlayerUtil.cpp +++ b/main/source/mod/AvHAIPlayerUtil.cpp @@ -78,6 +78,8 @@ bool IsPlayerInReadyRoom(const edict_t* Player) bool IsPlayerActiveInGame(const edict_t* Player) { + if (FNullEnt(Player)) { return false; } + return !IsPlayerInReadyRoom(Player) && Player->v.team != 0 && !IsPlayerSpectator(Player) && !IsPlayerDead(Player) && !IsPlayerBeingDigested(Player) && !IsPlayerCommander(Player); } diff --git a/main/source/mod/AvHAITactical.cpp b/main/source/mod/AvHAITactical.cpp index e35dd870..026d716a 100644 --- a/main/source/mod/AvHAITactical.cpp +++ b/main/source/mod/AvHAITactical.cpp @@ -66,6 +66,8 @@ edict_t* LastSeenLerkTeamB = nullptr; // Track who went lerk on team B last time float LastSeenLerkTeamATime = 0.0f; float LastSeenLerkTeamBTime = 0.0f; +vector ActiveSquads; + std::vector AITAC_FindAllDeployables(const Vector& Location, const DeployableSearchFilter* Filter) { std::vector Result; @@ -2562,7 +2564,7 @@ void AITAC_OnStructureCreated(AvHAIBuildableStructure* NewStructure) { NavHint* ThisHint = (*it); - if (vDist2DSq(NewStructure->edict->v.origin, ThisHint->Position) < sqrf(32.0f) && fabsf(NewStructure->Location.z - ThisHint->Position.z) < 50.0f) + if (vDist2DSq(NewStructure->edict->v.origin, ThisHint->Position) < sqrf(64.0f) && fabsf(NewStructure->Location.z - ThisHint->Position.z) < 50.0f) { ThisHint->OccupyingBuilding = NewStructure->edict; } @@ -2802,6 +2804,8 @@ void AITAC_ClearMapAIData(bool bInitialMapLoad) Hives.clear(); } + AITAC_ClearSquads(); + MarineDroppedItemMap.clear(); TeamAStructureMap.clear(); TeamBStructureMap.clear(); @@ -4266,6 +4270,27 @@ bool AITAC_ShouldBotBeCautious(AvHAIPlayer* pBot) int NumEnemiesAtDestination = AITAC_GetNumPlayersOnTeamWithLOS(EnemyTeam, CurrentPathNode.Location, UTIL_MetresToGoldSrcUnits(50.0f), pBot->Edict); + if (NumEnemiesAtDestination <= 1) + { + DeployableSearchFilter TurretFilter; + TurretFilter.DeployableTeam = EnemyTeam; + TurretFilter.DeployableTypes = (STRUCTURE_MARINE_TURRET | STRUCTURE_ALIEN_OFFENCECHAMBER); + TurretFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + TurretFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + TurretFilter.MaxSearchRadius = BALANCE_VAR(kTurretRange); + + vector Turrets = AITAC_FindAllDeployables(CurrentPathNode.Location, &TurretFilter); + + for (auto it = Turrets.begin(); it != Turrets.end(); it++) + { + if (UTIL_QuickTrace(pBot->Edict, GetPlayerTopOfCollisionHull(it->edict), CurrentPathNode.Location)) + { + NumEnemiesAtDestination++; + } + } + + } + if (NumEnemiesAtDestination > 1) { return (vDist2DSq(pBot->Edict->v.origin, CurrentPathNode.Location) < sqrf(UTIL_MetresToGoldSrcUnits(5.0f))); @@ -5403,4 +5428,200 @@ Vector AITAC_GetRandomBuildHintInLocation(const unsigned int StructureType, cons } return Result; +} + +bool AITAC_IsBotPursuingSquadObjective(AvHAIPlayer* pBot, AvHAISquad* Squad) +{ + // Bot is dead, no longer playing, or otherwise incapacitated + if (!IsPlayerActiveInGame(pBot->Edict) || pBot->Player->GetTeam() != Squad->SquadTeam) { return false; } + + // Bot no longer has this squad's objective as its primary task + if (pBot->PrimaryBotTask.TaskType != Squad->SquadObjective || pBot->PrimaryBotTask.TaskTarget != Squad->SquadTarget) { return false; } + + // Bot is focused on the job at hand + if (!pBot->CurrentTask || pBot->CurrentTask == &pBot->PrimaryBotTask) { return true; } + + // Bot isn't currently pursuing squad objective, so check if it's doing something in the vicinity + Vector TaskLocation = (!FNullEnt(pBot->CurrentTask->TaskTarget)) ? pBot->CurrentTask->TaskTarget->v.origin : pBot->CurrentTask->TaskLocation; + + return vDist2DSq(TaskLocation, Squad->SquadTarget->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(10.0f)); +} + +void AITAC_ManageSquads() +{ + for (auto it = ActiveSquads.begin(); it != ActiveSquads.end();) + { + for (auto pIt = it->SquadMembers.begin(); pIt != it->SquadMembers.end();) + { + AvHAIPlayer* ThisPlayer = (*pIt); + if (!AITAC_IsBotPursuingSquadObjective(ThisPlayer, &(*it))) + { + pIt = it->SquadMembers.erase(pIt); + } + else + { + pIt++; + } + } + + if (it->SquadMembers.size() == 0) + { + it = ActiveSquads.erase(it); + } + else + { + it++; + } + } +} + +void AITAC_UpdateSquads() +{ + AITAC_ManageSquads(); + + for (auto it = ActiveSquads.begin(); it != ActiveSquads.end(); it++) + { + if (it->SquadMembers.size() > 1) + { + if (vIsZero(it->SquadGatherLocation)) + { + vector TravelPath; + + dtStatus PathFindResult = FindPathClosestToPoint(GetBaseNavProfile(ONOS_BASE_NAV_PROFILE), AITAC_GetTeamStartingLocation(it->SquadTeam), UTIL_GetEntityGroundLocation(it->SquadTarget), TravelPath, UTIL_MetresToGoldSrcUnits(20.0f)); + + if (dtStatusSucceed(PathFindResult)) + { + for (auto pIt = TravelPath.rbegin(); pIt != TravelPath.rend(); pIt++) + { + if (pIt->area != SAMPLE_POLYAREA_GROUND || pIt->flag != SAMPLE_POLYFLAGS_WALK) { continue; } + + if (UTIL_QuickTrace(nullptr, pIt->Location, it->SquadTarget->v.origin)) { continue; } + + if (vDist2DSq(pIt->Location, it->SquadTarget->v.origin) > sqrf(UTIL_MetresToGoldSrcUnits(20.0f))) + { + DeployableSearchFilter EnemyStuff; + EnemyStuff.DeployableTeam = AIMGR_GetEnemyTeam(it->SquadTeam); + EnemyStuff.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + EnemyStuff.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); + + if (AITAC_DeployableExistsAtLocation(pIt->Location, &EnemyStuff)) + { + continue; + } + + it->SquadGatherLocation = pIt->Location; + break; + } + } + } + } + } + else + { + it->SquadGatherLocation = ZERO_VECTOR; + } + + if (!it->bExecuteObjective && !vIsZero(it->SquadGatherLocation)) + { + bool bAllHaveGathered = true; + + for (auto playerIt = it->SquadMembers.begin(); playerIt != it->SquadMembers.end(); playerIt++) + { + AvHAIPlayer* ThisBot = (*playerIt); + + if (vDist2DSq(ThisBot->Edict->v.origin, it->SquadGatherLocation) > sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) + { + bAllHaveGathered = false; + } + } + + if (bAllHaveGathered) + { + it->bExecuteObjective = true; + } + } + } +} + +AvHAISquad* AITAC_GetSquadForObjective(AvHAIPlayer* pBot, edict_t* TaskTarget, BotTaskType ObjectiveType) +{ + AvHAISquad* JoinSquad = nullptr; + + for (auto it = ActiveSquads.begin(); it != ActiveSquads.end(); it++) + { + if (it->SquadTeam == pBot->Player->GetTeam() && it->SquadTarget == TaskTarget && it->SquadObjective == ObjectiveType) + { + auto element = std::find(it->SquadMembers.begin(), it->SquadMembers.end(), pBot); + + if (element != it->SquadMembers.end()) + { + return &(*it); + } + else + { + if (!JoinSquad && !it->bExecuteObjective) + { + JoinSquad = &(*it); + } + } + } + } + + if (JoinSquad) + { + JoinSquad->SquadMembers.push_back(pBot); + return JoinSquad; + } + + AvHAISquad NewSquad; + NewSquad.SquadTeam = pBot->Player->GetTeam(); + NewSquad.SquadTarget = TaskTarget; + NewSquad.SquadObjective = ObjectiveType; + NewSquad.bExecuteObjective = false; + NewSquad.SquadGatherLocation = ZERO_VECTOR; + + ActiveSquads.push_back(NewSquad); + + return nullptr; +} + +void AITAC_ClearSquads() +{ + ActiveSquads.clear(); +} + +Vector AITAC_GetGatherLocationForSquad(AvHAISquad* Squad) +{ + if (!Squad || FNullEnt(Squad->SquadTarget)) { return ZERO_VECTOR; } + + vector TravelPath; + + dtStatus PathFindResult = FindPathClosestToPoint(GetBaseNavProfile(ONOS_BASE_NAV_PROFILE), AITAC_GetTeamStartingLocation(Squad->SquadTeam), UTIL_GetEntityGroundLocation(Squad->SquadTarget), TravelPath, UTIL_MetresToGoldSrcUnits(20.0f)); + + if (dtStatusSucceed(PathFindResult)) + { + for (auto pIt = TravelPath.rend(); pIt != TravelPath.rbegin(); pIt++) + { + if (pIt->area != SAMPLE_POLYAREA_GROUND || pIt->flag != SAMPLE_POLYFLAGS_WALK) { continue; } + + if (UTIL_QuickTrace(nullptr, pIt->Location, Squad->SquadTarget->v.origin)) { continue; } + + if (vDist2DSq(pIt->Location, Squad->SquadTarget->v.origin) > sqrf(UTIL_MetresToGoldSrcUnits(20.0f))) + { + DeployableSearchFilter EnemyStuff; + EnemyStuff.DeployableTeam = AIMGR_GetEnemyTeam(Squad->SquadTeam); + EnemyStuff.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + EnemyStuff.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); + + if (AITAC_DeployableExistsAtLocation(pIt->Location, &EnemyStuff)) + { + continue; + } + + return pIt->Location; + } + } + } + + return ZERO_VECTOR; } \ No newline at end of file diff --git a/main/source/mod/AvHAITactical.h b/main/source/mod/AvHAITactical.h index 066347b0..81c0f542 100644 --- a/main/source/mod/AvHAITactical.h +++ b/main/source/mod/AvHAITactical.h @@ -204,4 +204,10 @@ edict_t* AITAC_GetLastSeenLerkForTeam(AvHTeamNumber Team, float& LastSeenTime); bool AITAC_IsCompletedStructureOfTypeNearLocation(AvHTeamNumber Team, unsigned int StructureType, Vector SearchLocation, float SearchRadius); bool AITAC_IsStructureOfTypeNearLocation(AvHTeamNumber Team, unsigned int StructureType, Vector SearchLocation, float SearchRadius); +void AITAC_UpdateSquads(); +void AITAC_ManageSquads(); +void AITAC_ClearSquads(); +AvHAISquad* AITAC_GetSquadForObjective(AvHAIPlayer* pBot, edict_t* TaskTarget, BotTaskType ObjectiveType); +Vector AITAC_GetGatherLocationForSquad(AvHAISquad* Squad); + #endif \ No newline at end of file diff --git a/main/source/mod/AvHAITask.cpp b/main/source/mod/AvHAITask.cpp index 1f08eb41..38612734 100644 --- a/main/source/mod/AvHAITask.cpp +++ b/main/source/mod/AvHAITask.cpp @@ -392,9 +392,11 @@ bool AITASK_IsTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) } else { - return false; + return AITASK_IsAlienSecureHiveTaskStillValid(pBot, Task); } } + case TASK_ATTACK_BASE: + return true; case TASK_DEFEND: return AITASK_IsDefendTaskStillValid(pBot, Task); case TASK_WELD: @@ -937,6 +939,28 @@ bool AITASK_IsReinforceStructureTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTas return AITAC_DeployableExistsAtLocation(Task->TaskTarget->v.origin, &EnemyStuff); } +bool AITASK_IsAlienSecureHiveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) +{ + if (!Task || FNullEnt(Task->TaskTarget) || !IsPlayerAlien(pBot->Edict)) { return false; } + + AvHAIHiveDefinition* HiveToSecure = AITAC_GetHiveFromEdict(Task->TaskTarget); + + if (!HiveToSecure) { return false; } + + if (HiveToSecure->Status != HIVE_STATUS_UNBUILT && HiveToSecure->OwningTeam != pBot->Player->GetTeam()) { return true; } + + AvHTeamNumber BotTeam = pBot->Player->GetTeam(); + + 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(15.0f); + + return AITAC_DeployableExistsAtLocation(HiveToSecure->FloorLocation, &EnemyStuff); +} + bool AITASK_IsMarineSecureHiveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { if (!Task || FNullEnt(Task->TaskTarget) || IsPlayerAlien(pBot->Edict)) { return false; } @@ -2530,6 +2554,10 @@ void BotProgressTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { MarineProgressSecureHiveTask(pBot, Task); } + else + { + AlienProgressSecureHiveTask(pBot, Task); + } } break; default: @@ -2617,6 +2645,121 @@ void BotProgressWeldTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) return; } +void AlienProgressSecureHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) +{ + const AvHAIHiveDefinition* Hive = AITAC_GetHiveFromEdict(Task->TaskTarget); + + if (!Hive) { return; } + + AvHAISquad* ActiveSquad = AITAC_GetSquadForObjective(pBot, Task->TaskTarget, Task->TaskType); + + if (ActiveSquad && !ActiveSquad->bExecuteObjective && !vIsZero(ActiveSquad->SquadGatherLocation)) + { + BotGuardLocation(pBot, ActiveSquad->SquadGatherLocation); + return; + } + + AvHTeamNumber BotTeam = pBot->Player->GetTeam(); + AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam); + + bool bEnemyIsMarines = AIMGR_GetTeamType(EnemyTeam) == AVH_CLASS_TYPE_MARINE; + // Don't prioritise electrified structures if we're skulk or lerk + bool bAvoidElectrified = (pBot->Player->GetUser3() == AVH_USER3_ALIEN_PLAYER1 || pBot->Player->GetUser3() == AVH_USER3_ALIEN_PLAYER3); + + DeployableSearchFilter EnemyStuffFilter; + EnemyStuffFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); + EnemyStuffFilter.DeployableTeam = EnemyTeam; + EnemyStuffFilter.ReachabilityTeam = BotTeam; + EnemyStuffFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; + EnemyStuffFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + + if (bEnemyIsMarines) + { + EnemyStuffFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE; + + AvHAIBuildableStructure PhaseGate = AITAC_FindClosestDeployableToLocation(Hive->FloorLocation, &EnemyStuffFilter); + + if (PhaseGate.IsValid()) + { + if (bAvoidElectrified) + { + EnemyStuffFilter.DeployableTypes = SEARCH_ALL_STRUCTURES; + EnemyStuffFilter.IncludeStatusFlags = STRUCTURE_STATUS_ELECTRIFIED; + EnemyStuffFilter.MaxSearchRadius = BALANCE_VAR(kElectricalRange); + + AvHAIBuildableStructure ElectrifiedStructure = AITAC_FindClosestDeployableToLocation(PhaseGate.Location, &EnemyStuffFilter); + + if (ElectrifiedStructure.IsValid()) + { + BotAlienAttackNonPlayerTarget(pBot, ElectrifiedStructure.edict); + return; + } + } + + BotAlienAttackNonPlayerTarget(pBot, PhaseGate.edict); + return; + } + + EnemyStuffFilter.DeployableTypes = (STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY); + EnemyStuffFilter.IncludeStatusFlags = STRUCTURE_STATUS_NONE; + EnemyStuffFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); + + AvHAIBuildableStructure TF = AITAC_FindClosestDeployableToLocation(Hive->FloorLocation, &EnemyStuffFilter); + + if (TF.IsValid()) + { + BotAlienAttackNonPlayerTarget(pBot, TF.edict); + return; + } + + EnemyStuffFilter.DeployableTypes = SEARCH_ALL_STRUCTURES; + + AvHAIBuildableStructure EnemyStructure = AITAC_FindClosestDeployableToLocation(Hive->FloorLocation, &EnemyStuffFilter); + + if (EnemyStructure.IsValid()) + { + BotAlienAttackNonPlayerTarget(pBot, EnemyStructure.edict); + return; + } + } + else + { + EnemyStuffFilter.DeployableTypes = STRUCTURE_ALIEN_OFFENCECHAMBER; + + AvHAIBuildableStructure EnemyOC = AITAC_FindClosestDeployableToLocation(Hive->FloorLocation, &EnemyStuffFilter); + + if (EnemyOC.IsValid()) + { + BotAlienAttackNonPlayerTarget(pBot, EnemyOC.edict); + return; + } + + if (Hive->Status == HIVE_STATUS_BUILT && Hive->OwningTeam != BotTeam) + { + BotAlienAttackNonPlayerTarget(pBot, Hive->HiveEdict); + return; + } + + EnemyStuffFilter.DeployableTypes = SEARCH_ALL_STRUCTURES; + + AvHAIBuildableStructure AnyEnemyStructure = AITAC_FindClosestDeployableToLocation(Hive->FloorLocation, &EnemyStuffFilter); + + if (AnyEnemyStructure.IsValid()) + { + BotAlienAttackNonPlayerTarget(pBot, AnyEnemyStructure.edict); + return; + } + + if (Hive->Status != HIVE_STATUS_UNBUILT && Hive->OwningTeam != BotTeam) + { + BotAlienAttackNonPlayerTarget(pBot, Hive->HiveEdict); + return; + } + } + + BotGuardLocation(pBot, Hive->FloorLocation); +} + void MarineProgressSecureHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { const AvHAIHiveDefinition* Hive = AITAC_GetHiveFromEdict(Task->TaskTarget); @@ -2624,6 +2767,7 @@ void MarineProgressSecureHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) if (!Hive) { return; } AvHTeamNumber BotTeam = pBot->Player->GetTeam(); + AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam); DeployableSearchFilter StructureFilter; StructureFilter.DeployableTypes = SEARCH_ALL_MARINE_STRUCTURES; @@ -2703,7 +2847,7 @@ void MarineProgressSecureHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) DeployableSearchFilter EnemyStructures; EnemyStructures.DeployableTypes = SEARCH_ALL_STRUCTURES; EnemyStructures.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); - EnemyStructures.DeployableTeam = AIMGR_GetEnemyTeam(BotTeam); + EnemyStructures.DeployableTeam = EnemyTeam; EnemyStructures.ReachabilityTeam = BotTeam; EnemyStructures.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; @@ -2927,7 +3071,6 @@ void AITASK_GenerateGuardWatchPoints(AvHAIPlayer* pBot, const Vector& GuardLocat vector path; path.clear(); - vector AllHives = AITAC_GetAllHives(); diff --git a/main/source/mod/AvHAITask.h b/main/source/mod/AvHAITask.h index 674bd49a..7d8613d7 100644 --- a/main/source/mod/AvHAITask.h +++ b/main/source/mod/AvHAITask.h @@ -45,6 +45,7 @@ bool AITASK_IsEvolveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); bool AITASK_IsReinforceStructureTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); bool AITASK_IsMarineSecureHiveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); +bool AITASK_IsAlienSecureHiveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); bool AITASK_IsAlienGetHealthTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); bool AITASK_IsAlienHealTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); @@ -93,6 +94,9 @@ void AIPlayerBuildStructure(AvHAIPlayer* pBot, edict_t* BuildTarget); void MarineProgressSecureHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); +// Clear all enemy structures +void AlienProgressSecureHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); + void AlienProgressGetHealthTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); void AlienProgressHealTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); void AlienProgressBuildTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); diff --git a/main/source/mod/AvHConsoleCommands.cpp b/main/source/mod/AvHConsoleCommands.cpp index ec1faf8f..38413473 100644 --- a/main/source/mod/AvHConsoleCommands.cpp +++ b/main/source/mod/AvHConsoleCommands.cpp @@ -1416,21 +1416,23 @@ BOOL AvHGamerules::ClientCommand( CBasePlayer *pPlayer, const char *pcmd ) theSuccess = true; } } +#ifdef BOTDEBUG else if (FStrEq(pcmd, "bot_setdebugaiplayer")) { CBaseEntity* SpectatedPlayer = theAvHPlayer->GetSpectatingEntity(); if (SpectatedPlayer) { - AIMGR_SetDebugAIPlayer(SpectatedPlayer->edict()); + AIMGR_SetDebugAIPlayer(theAvHPlayer->edict(), SpectatedPlayer->edict()); } else { - AIMGR_SetDebugAIPlayer(nullptr); + AIMGR_SetDebugAIPlayer(theAvHPlayer->edict(), nullptr); } theSuccess = true; } +#endif else if (FStrEq(pcmd, "bot_cometome")) { vector AIPlayers = AIMGR_GetAllAIPlayers();