From f391176841631deb262cb3c70ff36301e963afb7 Mon Sep 17 00:00:00 2001 From: RGreenlees Date: Sat, 23 Mar 2024 23:10:52 +0000 Subject: [PATCH] Door and movement improvements * Fixed buttons having overly long wait times with bots * Fixed issue with climbing walls in certain situations --- main/source/mod/AvHAICommander.cpp | 8 +- main/source/mod/AvHAIConfig.cpp | 9 +- main/source/mod/AvHAINavigation.cpp | 86 ++++++++-- main/source/mod/AvHAIPlayer.cpp | 233 ++++++++++++++++++++++------ 4 files changed, 269 insertions(+), 67 deletions(-) diff --git a/main/source/mod/AvHAICommander.cpp b/main/source/mod/AvHAICommander.cpp index 60700898..dc6d2a0a 100644 --- a/main/source/mod/AvHAICommander.cpp +++ b/main/source/mod/AvHAICommander.cpp @@ -276,7 +276,7 @@ void AICOMM_IssueOrderForAssignedJob(AvHAIPlayer* pBot, ai_commander_order* Orde } else { - Vector MoveLoc = (bIsSiegeHiveOrder) ? UTIL_GetRandomPointOnNavmeshInDonutIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), Hive->FloorLocation, UTIL_MetresToGoldSrcUnits(10.0f), UTIL_MetresToGoldSrcUnits(25.0f)) : Hive->FloorLocation; + Vector MoveLoc = (bIsSiegeHiveOrder) ? UTIL_GetRandomPointOnNavmeshInDonutIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), Hive->FloorLocation, UTIL_MetresToGoldSrcUnits(10.0f), UTIL_MetresToGoldSrcUnits(20.0f)) : Hive->FloorLocation; AICOMM_IssueMovementOrder(pBot, Order->Assignee, MoveLoc); Order->LastReminderTime = gpGlobals->time; @@ -1972,7 +1972,7 @@ bool AICOMM_PerformNextSiegeHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinit { Vector NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), SiegeLocation, UTIL_MetresToGoldSrcUnits(3.0f)); - if (!vIsZero(NextBuildPosition)) + if (!vIsZero(NextBuildPosition) && vDist2DSq(NextBuildPosition, HiveToSiege->Location) < sqrf(UTIL_MetresToGoldSrcUnits(22.0f))) { bool bSuccess = AICOMM_DeployStructure(pBot, NextStructure, NextBuildPosition, STRUCTURE_PURPOSE_SIEGE); @@ -1981,7 +1981,7 @@ bool AICOMM_PerformNextSiegeHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinit NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), SiegeLocation, UTIL_MetresToGoldSrcUnits(5.0f)); - if (!vIsZero(NextBuildPosition)) + if (!vIsZero(NextBuildPosition) && vDist2DSq(NextBuildPosition, HiveToSiege->Location) < sqrf(UTIL_MetresToGoldSrcUnits(22.0f))) { bool bSuccess = AICOMM_DeployStructure(pBot, NextStructure, NextBuildPosition, STRUCTURE_PURPOSE_SIEGE); @@ -1990,7 +1990,7 @@ bool AICOMM_PerformNextSiegeHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinit NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), SiegeLocation, UTIL_MetresToGoldSrcUnits(5.0f)); - if (!vIsZero(NextBuildPosition)) + if (!vIsZero(NextBuildPosition) && vDist2DSq(NextBuildPosition, HiveToSiege->Location) < sqrf(UTIL_MetresToGoldSrcUnits(22.0f))) { bool bSuccess = AICOMM_DeployStructure(pBot, NextStructure, NextBuildPosition, STRUCTURE_PURPOSE_SIEGE); diff --git a/main/source/mod/AvHAIConfig.cpp b/main/source/mod/AvHAIConfig.cpp index 1eef002b..64cd0a45 100644 --- a/main/source/mod/AvHAIConfig.cpp +++ b/main/source/mod/AvHAIConfig.cpp @@ -159,7 +159,7 @@ void CONFIG_ParseConfigFile() while (getline(cFile, line)) { - line.erase(std::remove_if(line.begin(), line.end(), ::isspace), + line.erase(std::remove_if(line.begin(), line.end(), isspace), line.end()); if (line[0] == '#' || line.empty()) continue; @@ -468,8 +468,8 @@ void CONFIG_RegenerateIniFile() fprintf(NewConfigFile, "### Skill Settings ###\n\n"); - fprintf(NewConfigFile, "# Bot skill settings. You can define as many settings as you like and reference them by name\n"); - fprintf(NewConfigFile, "# Format is BotSkillName = name, followed by one of the following:\n"); + fprintf(NewConfigFile, "# Bot skill settings. There are 4 settings from 0 - 3 for easiest - hardest\n"); + fprintf(NewConfigFile, "# Use BotSkillName= to start defining a skill level, then the following:\n"); fprintf(NewConfigFile, "# ReactionTime = How quickly in seconds the bot will react to sighting enemies\n"); fprintf(NewConfigFile, "# AimSkill = How accurately the bot can lock sights on you after seeing you (0.0 - 1.0)\n"); fprintf(NewConfigFile, "# MovementTracking = How accurately the bot can follow a moving target (0.0 - 1.0)\n"); @@ -514,7 +514,8 @@ void CONFIG_RegenerateIniFile() fprintf(NewConfigFile, "AlienReactionTime=0.1\n"); fprintf(NewConfigFile, "AlienAimSkill=1.0\n"); fprintf(NewConfigFile, "AlienMovementTracking=1.0\n"); - fprintf(NewConfigFile, "AlienViewSpeed=2.0\n\n"); + fprintf(NewConfigFile, "AlienViewSpeed=2.0\n\n\n"); + fprintf(NewConfigFile, "# Desired team sizes. Only used if bot fill mode is 'fillteams'\n"); fprintf(NewConfigFile, "# Format is TeamSize=mapname:nummarines/numaliens\n"); diff --git a/main/source/mod/AvHAINavigation.cpp b/main/source/mod/AvHAINavigation.cpp index a0a68ea0..76ecec79 100644 --- a/main/source/mod/AvHAINavigation.cpp +++ b/main/source/mod/AvHAINavigation.cpp @@ -2326,7 +2326,9 @@ bool HasBotCompletedClimbMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector { Vector PositionInMove = vClosestPointOnLine2D(MoveStart, MoveEnd, pBot->Edict->v.origin); - if (NextMoveFlag != SAMPLE_POLYFLAGS_DISABLED) + if (!vEquals2D(PositionInMove, MoveEnd, 4.0f)) { return false; } + + /*if (NextMoveFlag != SAMPLE_POLYFLAGS_DISABLED) { Vector ThisMoveDir = UTIL_GetVectorNormal2D(MoveEnd - MoveStart); Vector NextMoveDir = UTIL_GetVectorNormal2D(NextMoveDestination - MoveEnd); @@ -2344,11 +2346,40 @@ bool HasBotCompletedClimbMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector } } } + }*/ + + if (pBot->BotNavInfo.IsOnGround) + { + return UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, NextMoveDestination); + } + else + { + if (NextMoveFlag != SAMPLE_POLYFLAGS_DISABLED) + { + Vector ThisMoveDir = UTIL_GetVectorNormal2D(MoveEnd - MoveStart); + Vector NextMoveDir = UTIL_GetVectorNormal2D(NextMoveDestination - MoveEnd); + + float MoveDot = UTIL_GetDotProduct2D(ThisMoveDir, NextMoveDir); + + if (MoveDot > 0.0f) + { + if (pBot->Edict->v.origin.z >= RequiredClimbHeight && !pBot->BotNavInfo.IsOnGround) + { + if (UTIL_QuickTrace(pBot->Edict, pBot->Edict->v.origin, NextMoveDestination) + && fabsf(pBot->CollisionHullBottomLocation.z - MoveEnd.z) < 100.0f) + { + return true; + } + } + } + } + else + { + return false; + } } - if (!vEquals2D(PositionInMove, MoveEnd, 2.0f)) { return false; } - - return vPointOverlaps3D(MoveEnd, pBot->Edict->v.absmin, pBot->Edict->v.absmax) && UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, NextMoveDestination); + } bool HasBotCompletedJumpMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag) @@ -4081,7 +4112,7 @@ void LiftMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint) if (NearestLiftTrigger) { // If the trigger is on cooldown, or the door/train is designed to automatically return without being summoned, then just wait for it to come back - if (gpGlobals->time < NearestLiftTrigger->NextActivationTime || (NearestLift->DoorType == DOORTYPE_TRAIN && !(NearestLift->DoorEdict->v.spawnflags & SF_TRAIN_WAIT_RETRIGGER)) || (NearestLift->DoorType == DOORTYPE_DOOR && NearestLift->DoorEntity && NearestLift->DoorEntity->GetToggleState() == TS_AT_TOP && NearestLift->DoorEntity->m_flWait > 0.0f && !(NearestLift->DoorEdict->v.spawnflags & SF_DOOR_NO_AUTO_RETURN))) + if (gpGlobals->time < NearestLiftTrigger->NextActivationTime || (NearestLift->DoorType == DOORTYPE_TRAIN && !(NearestLift->DoorEdict->v.spawnflags & SF_TRAIN_WAIT_RETRIGGER)) || (NearestLift->DoorType == DOORTYPE_DOOR && NearestLift->DoorEntity && NearestLift->DoorEntity->GetToggleState() == TS_AT_TOP && NearestLift->DoorEntity->m_flWait > 0.0f && !FBitSet(NearestLift->DoorEdict->v.spawnflags, SF_DOOR_NO_AUTO_RETURN))) { if (!bIsOnLift && !bIsLiftAtOrNearStart) { @@ -4462,6 +4493,25 @@ void WallClimbMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndP { edict_t* pEdict = pBot->Edict; + if (UTIL_PointIsDirectlyReachable(pBot->BotNavInfo.NavProfile, pBot->CurrentFloorPosition, EndPoint)) + { + Vector PointOnMoveLine = vClosestPointOnLine2D(StartPoint, EndPoint, pBot->Edict->v.origin); + + if (vEquals2D(PointOnMoveLine, EndPoint, 4.0f)) + { + + // Stop holding crouch if we're a skulk so we can actually climb + if (IsPlayerSkulk(pBot->Edict)) + { + pBot->Button &= ~IN_DUCK; + } + + pBot->desiredMovementDir = UTIL_GetVectorNormal2D(EndPoint - pBot->CurrentFloorPosition); + + return; + } + } + Vector vForward = UTIL_GetVectorNormal2D(EndPoint - StartPoint); Vector vRight = UTIL_GetVectorNormal(UTIL_GetCrossProduct(vForward, UP_VECTOR)); @@ -4499,6 +4549,8 @@ void WallClimbMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndP pBot->Button &= ~IN_DUCK; } + + float ZDiff = fabs(pEdict->v.origin.z - RequiredClimbHeight); Vector AdjustedTargetLocation = EndPoint + (UTIL_GetVectorNormal2D(EndPoint - StartPoint) * 1000.0f); Vector DirectAheadView = pBot->CurrentEyePosition + (UTIL_GetVectorNormal2D(AdjustedTargetLocation - pBot->CurrentEyePosition) * 100.0f); @@ -8014,18 +8066,30 @@ void UTIL_UpdateDoorTriggers(nav_door* Door) } } - float BaseTriggerDelay = (it->ToggleEnt) ? it->ToggleEnt->m_flDelay : 0.0f; - float DoorDelay = Door->DoorEntity->GetDelay(); - it->ActivationDelay = BaseTriggerDelay + DoorDelay + 1.0f; + float BaseTriggerDelay = 0.0f; + float BaseTriggerResetTime = 0.0f; + + bool bButtonIsToggle = FBitSet(it->Edict->v.spawnflags, SF_DOOR_NO_AUTO_RETURN); + + if (it->ToggleEnt) + { + BaseTriggerDelay = it->ToggleEnt->m_flDelay; + BaseTriggerResetTime = (bButtonIsToggle) ? 1.0f : it->ToggleEnt->GetDelay(); + } + + float DoorDelay = (FBitSet(Door->DoorEdict->v.spawnflags, SF_DOOR_NO_AUTO_RETURN)) ? 0.0f : Door->DoorEntity->GetDelay(); + it->ActivationDelay = fmaxf(BaseTriggerDelay, BaseTriggerResetTime) + DoorDelay + 1.0f; if (it->ToggleEnt && it->ToggleEnt->GetToggleState() != it->LastToggleState) { - if (it->LastToggleState != TS_GOING_UP && it->LastToggleState != TS_GOING_DOWN) + TOGGLE_STATE NewState = (TOGGLE_STATE)it->ToggleEnt->GetToggleState(); + + if (it->LastToggleState == TS_AT_BOTTOM || (bButtonIsToggle && it->LastToggleState == TS_AT_TOP)) { - it->NextActivationTime = gpGlobals->time + fmaxf(it->ActivationDelay + 1.0f, 1.0f); + it->NextActivationTime = gpGlobals->time + fmaxf(it->ActivationDelay, 1.0f); } - it->LastToggleState = (TOGGLE_STATE)it->ToggleEnt->GetToggleState(); + it->LastToggleState = NewState; } it++; diff --git a/main/source/mod/AvHAIPlayer.cpp b/main/source/mod/AvHAIPlayer.cpp index 8632c743..d594fdd0 100644 --- a/main/source/mod/AvHAIPlayer.cpp +++ b/main/source/mod/AvHAIPlayer.cpp @@ -4655,6 +4655,7 @@ void AIPlayerCOAlienThink(AvHAIPlayer* pBot) void AIPlayerSetPrimaryCOMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { + AvHTeamNumber BotTeam = pBot->Player->GetTeam(); AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam); @@ -4666,10 +4667,11 @@ void AIPlayerSetPrimaryCOMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) AvHAIBuildableStructure EnemyStructure = AITAC_FindClosestDeployableToLocation(pBot->Edict->v.origin, &EnemyStuffFilter); + edict_t* StructureToAttack = nullptr; + if (EnemyStructure.IsValid()) { - AITASK_SetAttackTask(pBot, Task, EnemyStructure.edict, false); - return; + StructureToAttack = EnemyStructure.edict; } else { @@ -4679,40 +4681,73 @@ void AIPlayerSetPrimaryCOMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) if (EnemyHive) { - AITASK_SetAttackTask(pBot, Task, EnemyHive->HiveEdict, false); - return; + StructureToAttack = EnemyHive->HiveEdict; } } } - vector AllEnemyPlayers = AIMGR_GetAllPlayersOnTeam(EnemyTeam); - edict_t* TargetPlayer = nullptr; - - float MinDist = 0.0f; - - for (auto it = AllEnemyPlayers.begin(); it != AllEnemyPlayers.end(); it++) + // Nothing to attack, just hunt down remaining enemy players. Shouldn't happen in vanilla combat mode, but a plugin might change behaviour + if (FNullEnt(StructureToAttack)) { - AvHPlayer* ThisPlayer = (*it); - if (!ThisPlayer) { continue; } + vector AllEnemyPlayers = AIMGR_GetAllPlayersOnTeam(EnemyTeam); + edict_t* TargetPlayer = nullptr; - edict_t* PlayerEdict = ThisPlayer->edict(); + float MinDist = 0.0f; - if (!IsPlayerActiveInGame(PlayerEdict)) { continue; } - - float ThisDist = vDist2DSq(PlayerEdict->v.origin, pBot->Edict->v.origin); - - if (FNullEnt(TargetPlayer) || ThisDist < MinDist) + for (auto it = AllEnemyPlayers.begin(); it != AllEnemyPlayers.end(); it++) { - TargetPlayer = PlayerEdict; - MinDist = ThisDist; + AvHPlayer* ThisPlayer = (*it); + + if (!ThisPlayer) { continue; } + + edict_t* PlayerEdict = ThisPlayer->edict(); + + if (!IsPlayerActiveInGame(PlayerEdict)) { continue; } + + float ThisDist = vDist2DSq(PlayerEdict->v.origin, pBot->Edict->v.origin); + + if (FNullEnt(TargetPlayer) || ThisDist < MinDist) + { + TargetPlayer = PlayerEdict; + MinDist = ThisDist; + } + } + + if (!FNullEnt(TargetPlayer)) + { + MoveTo(pBot, UTIL_GetEntityGroundLocation(TargetPlayer), MOVESTYLE_NORMAL); + } + + return; + } + + // If we're close to the enemy base then just attack. We don't want bots marching through the enemy base and ignoring the hive/comm chair + if (vDist2DSq(pBot->Edict->v.origin, StructureToAttack->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(15.0f))) + { + AITASK_SetAttackTask(pBot, Task, StructureToAttack, false); + return; + } + + // At this point we already know what we want to do, just crack on + if (Task->TaskType != TASK_NONE) { return; } + + // Decide if we're going to attack right away, or take a little detour first. Helps mix things up and prevents all bots just gang-rushing the base endlessly + + if (randbool()) + { + Vector RandomVisitPoint = UTIL_GetRandomPointOnNavmeshInDonut(pBot->BotNavInfo.NavProfile, StructureToAttack->v.origin, UTIL_MetresToGoldSrcUnits(20.0f), UTIL_MetresToGoldSrcUnits(40.0f)); + + if (!vIsZero(RandomVisitPoint)) + { + AITASK_SetMoveTask(pBot, Task, RandomVisitPoint, false); + return; } } - if (!FNullEnt(TargetPlayer)) - { - MoveTo(pBot, UTIL_GetEntityGroundLocation(TargetPlayer), MOVESTYLE_NORMAL); - } + AITASK_SetAttackTask(pBot, Task, StructureToAttack, false); + + } void AIPlayerSetSecondaryCOMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) @@ -4765,6 +4800,51 @@ void AIPlayerSetSecondaryCOMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) if (PlayerHasWeapon(pBot->Player, WEAPON_MARINE_WELDER)) { + vector NearbyPlayers = AITAC_GetAllPlayersOfTeamInArea(BotTeam, pBot->Edict->v.origin, UTIL_MetresToGoldSrcUnits(5.0f), false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER); + AvHPlayer* NearestWeldablePlayer = nullptr; + AvHPlayer* NearestBadlyDamagedPlayer = nullptr; + float MinDist = 0.0f; + float MinBadDist = 0.0f; + + for (auto it = NearbyPlayers.begin(); it != NearbyPlayers.end(); it++) + { + AvHPlayer* ThisPlayer = (*it); + edict_t* PlayerEdict = ThisPlayer->edict(); + + float ArmourPercent = PlayerEdict->v.armorvalue / (float)GetPlayerMaxArmour(PlayerEdict); + + if (ArmourPercent < 1.0f) + { + float ThisDist = vDist2DSq(pBot->Edict->v.origin, PlayerEdict->v.origin); + + if (ArmourPercent < 0.75f) + { + if (!NearestBadlyDamagedPlayer || ThisDist < MinBadDist) + { + NearestBadlyDamagedPlayer = ThisPlayer; + MinBadDist = ThisDist; + } + } + else + { + if (!NearestWeldablePlayer || ThisDist < MinDist) + { + NearestWeldablePlayer = ThisPlayer; + MinDist = ThisDist; + } + } + } + } + + // Basically, we won't prioritise welding players over structures unless they're low on armour, otherwise we prefer structures. This avoids + // situations where the bot constantly keeps topping up nearby players when there are more important weld targets to worry about + if (NearestBadlyDamagedPlayer) + { + AITASK_SetWeldTask(pBot, Task, NearestBadlyDamagedPlayer->edict(), false); + return; + } + + DeployableSearchFilter DamagedStructuresFilter; DamagedStructuresFilter.DeployableTypes = SEARCH_ALL_STRUCTURES; DamagedStructuresFilter.DeployableTeam = BotTeam; @@ -4803,6 +4883,13 @@ void AIPlayerSetSecondaryCOMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) return; } + + if (NearestWeldablePlayer) + { + AITASK_SetWeldTask(pBot, Task, NearestWeldablePlayer->edict(), false); + return; + } + DeployableSearchFilter NearbyArmouryFilter; NearbyArmouryFilter.DeployableTypes = (STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY); NearbyArmouryFilter.DeployableTeam = BotTeam; @@ -4926,40 +5013,84 @@ void AIPlayerSetPrimaryCOAlienTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) AvHAIBuildableStructure EnemyStructure = AITAC_FindClosestDeployableToLocation(pBot->Edict->v.origin, &EnemyStuffFilter); + edict_t* StructureToAttack = nullptr; + if (EnemyStructure.IsValid()) { - AITASK_SetAttackTask(pBot, Task, EnemyStructure.edict, false); - return; + StructureToAttack = EnemyStructure.edict; } - - vector AllEnemyPlayers = AIMGR_GetAllPlayersOnTeam(EnemyTeam); - edict_t* TargetPlayer = nullptr; - - float MinDist = 0.0f; - - for (auto it = AllEnemyPlayers.begin(); it != AllEnemyPlayers.end(); it++) + else { - AvHPlayer* ThisPlayer = (*it); - - if (!ThisPlayer) { continue; } - - edict_t* PlayerEdict = ThisPlayer->edict(); - - if (!IsPlayerActiveInGame(PlayerEdict)) { continue; } - - float ThisDist = vDist2DSq(PlayerEdict->v.origin, pBot->Edict->v.origin); - - if (FNullEnt(TargetPlayer) || ThisDist < MinDist) + if (AIMGR_GetTeamType(EnemyTeam) == AVH_CLASS_TYPE_ALIEN) { - TargetPlayer = PlayerEdict; - MinDist = ThisDist; + const AvHAIHiveDefinition* EnemyHive = AITAC_GetActiveHiveNearestLocation(EnemyTeam, pBot->Edict->v.origin); + + if (EnemyHive) + { + StructureToAttack = EnemyHive->HiveEdict; + } } } - if (!FNullEnt(TargetPlayer)) + // Nothing to attack, just hunt down remaining enemy players. Shouldn't happen in vanilla combat mode, but a plugin might change behaviour + if (FNullEnt(StructureToAttack)) { - MoveTo(pBot, UTIL_GetEntityGroundLocation(TargetPlayer), MOVESTYLE_NORMAL); + vector AllEnemyPlayers = AIMGR_GetAllPlayersOnTeam(EnemyTeam); + edict_t* TargetPlayer = nullptr; + + float MinDist = 0.0f; + + for (auto it = AllEnemyPlayers.begin(); it != AllEnemyPlayers.end(); it++) + { + AvHPlayer* ThisPlayer = (*it); + + if (!ThisPlayer) { continue; } + + edict_t* PlayerEdict = ThisPlayer->edict(); + + if (!IsPlayerActiveInGame(PlayerEdict)) { continue; } + + float ThisDist = vDist2DSq(PlayerEdict->v.origin, pBot->Edict->v.origin); + + if (FNullEnt(TargetPlayer) || ThisDist < MinDist) + { + TargetPlayer = PlayerEdict; + MinDist = ThisDist; + } + } + + if (!FNullEnt(TargetPlayer)) + { + MoveTo(pBot, UTIL_GetEntityGroundLocation(TargetPlayer), MOVESTYLE_NORMAL); + } + + return; } + + // If we're close to the enemy base then just attack. We don't want bots marching through the enemy base and ignoring the hive/comm chair + if (vDist2DSq(pBot->Edict->v.origin, StructureToAttack->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(15.0f))) + { + AITASK_SetAttackTask(pBot, Task, StructureToAttack, false); + return; + } + + // At this point we already know what we want to do, just crack on + if (Task->TaskType != TASK_NONE) { return; } + + // Decide if we're going to attack right away, or take a little detour first. Helps mix things up and prevents all bots just gang-rushing the base endlessly + + if (randbool()) + { + Vector RandomVisitPoint = UTIL_GetRandomPointOnNavmeshInDonut(pBot->BotNavInfo.NavProfile, StructureToAttack->v.origin, UTIL_MetresToGoldSrcUnits(20.0f), UTIL_MetresToGoldSrcUnits(40.0f)); + + if (!vIsZero(RandomVisitPoint)) + { + AITASK_SetMoveTask(pBot, Task, RandomVisitPoint, false); + return; + } + } + + AITASK_SetAttackTask(pBot, Task, StructureToAttack, false); } @@ -5277,6 +5408,12 @@ void BotResumePlay(AvHAIPlayer* pBot) SetBaseNavProfile(pBot); pBot->bIsInactive = false; + + // Keep things nicely randomized in Combat mode + if (GetGameRules()->GetMapMode() == MAP_MODE_CO) + { + AITASK_ClearBotTask(pBot, &pBot->PrimaryBotTask); + } } void UpdateCommanderOrders(AvHAIPlayer* pBot)