From d87bb3d6008ddea7f52e228bc6edac382bfd753e Mon Sep 17 00:00:00 2001 From: RGreenlees Date: Fri, 5 Apr 2024 21:34:50 +0100 Subject: [PATCH] Further bot enhancements * Improved jump and blink movement * Hopefully fixed bug with bots trying to walk between phase gates * Fixed issue with bots constantly switching weapons when trying to reload * Added a max AI time for a match, with bots throwing the game if there are no humans and it goes on too long (configurable in nsbots.ini, default is 90 minutes) --- main/source/mod/AvHAICommander.cpp | 100 ++++++++++++++++++++++-- main/source/mod/AvHAIConfig.cpp | 20 +++++ main/source/mod/AvHAIConfig.h | 2 + main/source/mod/AvHAINavigation.cpp | 102 +++++++++++++++++-------- main/source/mod/AvHAIPlayer.cpp | 18 +++-- main/source/mod/AvHAIPlayerManager.cpp | 11 ++- main/source/mod/AvHAITask.cpp | 7 +- main/source/mod/AvHMarineEquipment.cpp | 6 ++ 8 files changed, 217 insertions(+), 49 deletions(-) diff --git a/main/source/mod/AvHAICommander.cpp b/main/source/mod/AvHAICommander.cpp index 26b6af67..bb62a02d 100644 --- a/main/source/mod/AvHAICommander.cpp +++ b/main/source/mod/AvHAICommander.cpp @@ -960,7 +960,11 @@ bool AICOMM_IsRequestValid(ai_commander_request* Request) case BUILD_TURRET_FACTORY: return !AITAC_IsStructureOfTypeNearLocation(RequestorTeam, (STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(5.0f)); case BUILD_ARMORY: - return !AITAC_IsStructureOfTypeNearLocation(RequestorTeam, (STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(5.0f)); + return !AITAC_IsStructureOfTypeNearLocation(RequestorTeam, (STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(5.0f)); + case BUILD_COMMANDSTATION: + return !AITAC_IsStructureOfTypeNearLocation(RequestorTeam, STRUCTURE_MARINE_COMMCHAIR, Requestor->v.origin, UTIL_MetresToGoldSrcUnits(10.0f)); + case BUILD_SCAN: + return !AITAC_ItemExistsInLocation(Requestor->v.origin, DEPLOYABLE_ITEM_SCAN, RequestorTeam, AI_REACHABILITY_NONE, 0.0f, UTIL_MetresToGoldSrcUnits(5.0f), false); default: return true; } @@ -2843,6 +2847,86 @@ bool AICOMM_CheckForNextSupportAction(AvHAIPlayer* pBot) return true; } + if (NextRequest->RequestType == BUILD_SCAN) + { + DeployableSearchFilter ObsFilter; + ObsFilter.DeployableTeam = CommanderTeam; + ObsFilter.DeployableTypes = STRUCTURE_MARINE_OBSERVATORY; + ObsFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + ObsFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + + AvHAIBuildableStructure NearestObservatory = AITAC_FindClosestDeployableToLocation(Requestor->v.origin, &ObsFilter); + + if (!NearestObservatory.IsValid()) + { + char msg[128]; + sprintf(msg, "We don't have an observatory yet %s, ask again later.", STRING(Requestor->v.netname)); + BotSay(pBot, true, 0.5f, msg); + NextRequest->bResponded = true; + + return false; + } + + Vector IdealDeployLocation = Requestor->v.origin + (UTIL_GetForwardVector2D(Requestor->v.angles) * 75.0f); + Vector ProjectedDeployLocation = AdjustPointForPathfinding(IdealDeployLocation, GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE)); + + if (!vIsZero(ProjectedDeployLocation)) + { + bool bSuccess = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_PHASEGATE, ProjectedDeployLocation, STRUCTURE_PURPOSE_NONE); + + if (bSuccess) + { + NextRequest->bResponded = true; + return true; + } + } + + Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(1.0f)); + + if (!vIsZero(DeployLocation)) + { + bool bSuccess = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_PHASEGATE, DeployLocation, STRUCTURE_PURPOSE_NONE); + + if (bSuccess) + { + NextRequest->bResponded = true; + return true; + } + } + + DeployLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(5.0f)); + + if (!vIsZero(DeployLocation)) + { + bool bSuccess = AICOMM_DeployItem(pBot, DEPLOYABLE_ITEM_SCAN, DeployLocation); + + if (bSuccess) + { + NextRequest->bResponded = true; + return true; + } + else + { + char msg[128]; + sprintf(msg, "I can't find a good scan spot, %s. Try again elsewhere.", STRING(Requestor->v.netname)); + BotSay(pBot, true, 0.5f, msg); + NextRequest->bResponded = true; + return false; + } + } + else + { + char msg[128]; + sprintf(msg, "I can't find a good scan spot, %s. Try again elsewhere.", STRING(Requestor->v.netname)); + BotSay(pBot, true, 0.5f, msg); + NextRequest->bResponded = true; + return false; + } + + return false; + + } + if (NextRequest->RequestType == BUILD_PHASEGATE) { if (!AITAC_ResearchIsComplete(CommanderTeam, TECH_RESEARCH_PHASETECH)) @@ -2927,9 +3011,11 @@ bool AICOMM_CheckForNextSupportAction(AvHAIPlayer* pBot) } - if (NextRequest->RequestType == BUILD_ARMORY) + if (NextRequest->RequestType == BUILD_ARMORY || NextRequest->RequestType == BUILD_COMMANDSTATION) { - if (pBot->Player->GetResources() < BALANCE_VAR(kArmoryCost)) + float RequiredRes = (NextRequest->RequestType == BUILD_ARMORY) ? BALANCE_VAR(kArmoryCost) : BALANCE_VAR(kCommandStationCost); + + if (pBot->Player->GetResources() < RequiredRes) { if (!NextRequest->bAcknowledged) { @@ -2945,9 +3031,11 @@ bool AICOMM_CheckForNextSupportAction(AvHAIPlayer* pBot) Vector IdealDeployLocation = Requestor->v.origin + (UTIL_GetForwardVector2D(Requestor->v.angles) * 75.0f); Vector ProjectedDeployLocation = AdjustPointForPathfinding(IdealDeployLocation, GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE)); + AvHAIDeployableStructureType StructureToDeploy = (NextRequest->RequestType == BUILD_ARMORY) ? STRUCTURE_MARINE_ARMOURY : STRUCTURE_MARINE_COMMCHAIR; + if (!vIsZero(ProjectedDeployLocation)) { - bool bSuccess = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_ARMOURY, ProjectedDeployLocation, STRUCTURE_PURPOSE_NONE); + bool bSuccess = AICOMM_DeployStructure(pBot, StructureToDeploy, ProjectedDeployLocation, STRUCTURE_PURPOSE_NONE); if (bSuccess) { @@ -2960,7 +3048,7 @@ bool AICOMM_CheckForNextSupportAction(AvHAIPlayer* pBot) if (!vIsZero(DeployLocation)) { - bool bSuccess = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_ARMOURY, DeployLocation, STRUCTURE_PURPOSE_NONE); + bool bSuccess = AICOMM_DeployStructure(pBot, StructureToDeploy, DeployLocation, STRUCTURE_PURPOSE_NONE); if (bSuccess) { @@ -2973,7 +3061,7 @@ bool AICOMM_CheckForNextSupportAction(AvHAIPlayer* pBot) if (!vIsZero(DeployLocation)) { - bool bSuccess = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_ARMOURY, DeployLocation, STRUCTURE_PURPOSE_NONE); + bool bSuccess = AICOMM_DeployStructure(pBot, StructureToDeploy, DeployLocation, STRUCTURE_PURPOSE_NONE); if (bSuccess) { diff --git a/main/source/mod/AvHAIConfig.cpp b/main/source/mod/AvHAIConfig.cpp index 9ed56736..f597290b 100644 --- a/main/source/mod/AvHAIConfig.cpp +++ b/main/source/mod/AvHAIConfig.cpp @@ -8,6 +8,8 @@ BotFillTiming CurrentBotFillTiming = FILLTIMING_ALLHUMANS; +float MaxAIMatchTimeMinutes = 90.0f; + std::unordered_map TeamSizeMap; bot_skill BotSkillLevels[4]; @@ -54,6 +56,11 @@ float CONFIG_GetMaxStuckTime() return avh_botmaxstucktime.value; } +float CONFIG_GetMaxAIMatchTimeMinutes() +{ + return MaxAIMatchTimeMinutes; +} + string CONFIG_GetBotPrefix() { return string(BotPrefix); @@ -208,6 +215,14 @@ void CONFIG_ParseConfigFile() continue; } + if (!stricmp(keyChar, "MaxAIMatchTime")) + { + float MaxMinutes = std::stof(value.c_str()); + MaxAIMatchTimeMinutes = MaxMinutes; + + continue; + } + if (!stricmp(keyChar, "BotFillTiming")) { int FillSetting = atoi(value.c_str()); @@ -550,6 +565,11 @@ void CONFIG_RegenerateIniFile() fprintf(NewConfigFile, "# What prefix to put in front of a bot's name (can leave blank)\n"); fprintf(NewConfigFile, "Prefix=[BOT]\n\n"); + fprintf(NewConfigFile, "# After this many minutes into a match, the bots will leave the game if there are no humans playing\n"); + fprintf(NewConfigFile, "# Helps prevent stalemates and bugs that happen after extremely long matches\n"); + fprintf(NewConfigFile, "# Default = 90 minutes\n"); + fprintf(NewConfigFile, "MaxAIMatchTime=90\n\n"); + fprintf(NewConfigFile, "# When should the server start adding bots? Note: bots will always be added after round start regardless\n"); fprintf(NewConfigFile, "# 0 = On map load (after 5 second grace period)\n"); fprintf(NewConfigFile, "# 1 = When all humans have joined a team (i.e. no more humans left in ready room)\n"); diff --git a/main/source/mod/AvHAIConfig.h b/main/source/mod/AvHAIConfig.h index 73afa3d7..719e3ac0 100644 --- a/main/source/mod/AvHAIConfig.h +++ b/main/source/mod/AvHAIConfig.h @@ -46,6 +46,8 @@ bool CONFIG_IsOnosAllowed(); // Returns the max time a bot is allowed to be stuck before suiciding (0 means forever) float CONFIG_GetMaxStuckTime(); +float CONFIG_GetMaxAIMatchTimeMinutes(); + // Returns the desired marine team size for the given map, indexes into TeamSizeMap int CONFIG_GetTeamASizeForMap(const char* MapName); // Returns the desired alien team size for the given map, indexes into TeamSizeMap diff --git a/main/source/mod/AvHAINavigation.cpp b/main/source/mod/AvHAINavigation.cpp index ead60478..63a51ee5 100644 --- a/main/source/mod/AvHAINavigation.cpp +++ b/main/source/mod/AvHAINavigation.cpp @@ -3530,6 +3530,25 @@ void BlockedMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoi pBot->desiredMovementDir = vForward; + Vector CurrVelocity = UTIL_GetVectorNormal2D(pBot->Edict->v.velocity); + + float Dot = UTIL_GetDotProduct2D(vForward, CurrVelocity); + + Vector FaceDir = UTIL_GetForwardVector2D(pBot->Edict->v.angles); + + float FaceDot = UTIL_GetDotProduct2D(FaceDir, vForward); + + // Yes this is cheating, but is it not cheating for humans to have millions of years of evolution + // driving their ability to judge a jump, while the bots have a single year of coding from a moron? + if (FaceDot < 0.95f) + { + float MoveSpeed = vSize2D(pBot->Edict->v.velocity); + Vector NewVelocity = vForward * MoveSpeed; + NewVelocity.z = pBot->Edict->v.velocity.z; + + pBot->Edict->v.velocity = NewVelocity; + } + BotJump(pBot); } @@ -3544,6 +3563,25 @@ void JumpMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint) pBot->desiredMovementDir = vForward; + Vector CurrVelocity = UTIL_GetVectorNormal2D(pBot->Edict->v.velocity); + + float Dot = UTIL_GetDotProduct2D(vForward, CurrVelocity); + + Vector FaceDir = UTIL_GetForwardVector2D(pBot->Edict->v.angles); + + float FaceDot = UTIL_GetDotProduct2D(FaceDir, vForward); + + // Yes this is cheating, but is it not cheating for humans to have millions of years of evolution + // driving their ability to judge a jump, while the bots have a single year of coding from a moron? + if (FaceDot < 0.95f) + { + float MoveSpeed = vSize2D(pBot->Edict->v.velocity); + Vector NewVelocity = vForward * MoveSpeed; + NewVelocity.z = pBot->Edict->v.velocity.z; + + pBot->Edict->v.velocity = NewVelocity; + } + BotJump(pBot); bool bCanDuck = (IsPlayerMarine(pBot->Edict) || IsPlayerFade(pBot->Edict) || IsPlayerOnos(pBot->Edict)); @@ -4315,10 +4353,10 @@ bool IsBotOffPath(const AvHAIPlayer* pBot) case SAMPLE_POLYFLAGS_LIFT: return IsBotOffLiftNode(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag); default: - return IsBotOffWalkNode(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag); + return IsBotOffFallNode(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag); } - return IsBotOffWalkNode(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag); + return IsBotOffFallNode(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag); } @@ -4341,6 +4379,9 @@ bool IsBotOffWalkNode(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, { if (!pBot->BotNavInfo.IsOnGround) { return false; } + // This shouldn't happen... but does occasionally. Walk moves should always be directly reachable from start to end + if (!UTIL_PointIsDirectlyReachable(MoveStart, MoveEnd)) { return true; } + Vector NearestPointOnLine = vClosestPointOnLine2D(MoveStart, MoveEnd, pBot->Edict->v.origin); if (vDist2DSq(pBot->Edict->v.origin, NearestPointOnLine) > sqrf(GetPlayerRadius(pBot->Edict) * 3.0f)) { return true; } @@ -4464,18 +4505,10 @@ void BlinkClimbMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector End if (GetPlayerCurrentWeapon(pBot->Player) != WEAPON_FADE_BLINK) { return; } // Only blink if we're below the target climb height - if (pEdict->v.origin.z < RequiredClimbHeight) + if (pEdict->v.origin.z < RequiredClimbHeight + 32.0f) { float HeightToClimb = (fabsf(pEdict->v.origin.z - RequiredClimbHeight)); - if (HeightToClimb > (GetPlayerHeight(pBot->Edict, false) * 2.0f)) - { - if (GetPlayerEnergy(pBot->Edict) < 0.15f && pBot->BotNavInfo.IsOnGround) - { - return; - } - } - Vector CurrVelocity = UTIL_GetVectorNormal2D(pBot->Edict->v.velocity); float Dot = UTIL_GetDotProduct2D(MoveDir, CurrVelocity); @@ -4484,30 +4517,37 @@ void BlinkClimbMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector End float FaceDot = UTIL_GetDotProduct2D(FaceDir, MoveDir); - // Don't start blinking unless we're already in the air, or we're moving in the correct direction. Stops fade shooting off sideways when approaching a climb point from the side - if (FaceDot > 0.9f) + // Yes this is cheating, but the fades were struggling with zipping off-target when trying to blink + // Better this than fades getting constantly chewed up by marines because they can't escape properly + if (FaceDot < 0.95f) { - float ZDiff = fabs(pEdict->v.origin.z - RequiredClimbHeight); + float MoveSpeed = vSize2D(pBot->Edict->v.velocity); + Vector NewVelocity = MoveDir * MoveSpeed; + NewVelocity.z = pBot->Edict->v.velocity.z; - // We don't want to blast off like a rocket, so only apply enough blink until our upwards velocity is enough to carry us to the desired height - float DesiredZVelocity = sqrtf(2.0f * GOLDSRC_GRAVITY * (ZDiff + 10.0f)); + pBot->Edict->v.velocity = NewVelocity; + } - if (pBot->Edict->v.velocity.z < DesiredZVelocity || pBot->Edict->v.velocity.z < 300.0f) + float ZDiff = fabs(pEdict->v.origin.z - (RequiredClimbHeight + 72.0f)); + + // We don't want to blast off like a rocket, so only apply enough blink until our upwards velocity is enough to carry us to the desired height + float DesiredZVelocity = sqrtf(2.0f * GOLDSRC_GRAVITY * (ZDiff + 10.0f)); + + if (pBot->Edict->v.velocity.z < DesiredZVelocity || pBot->Edict->v.velocity.z < 300.0f) + { + // We're going to cheat and give the bot the necessary energy to make the move. Better the fade cheats a bit than gets stuck somewhere + if (GetPlayerEnergy(pBot->Edict) < 0.1f) { - // We're going to cheat and give the bot the necessary energy to make the move. Better the fade cheats a bit than gets stuck somewhere - if (GetPlayerEnergy(pBot->Edict) < 0.1f) - { - pBot->Player->Energize(0.1f); - } - BotMoveLookAt(pBot, EndPoint + Vector(0.0f, 0.0f, 100.0f)); - pBot->Button |= IN_ATTACK2; - } - else - { - Vector LookAtTarget = EndPoint; - LookAtTarget.z = pBot->CurrentEyePosition.z; - BotMoveLookAt(pBot, LookAtTarget); + pBot->Player->Energize(0.1f); } + BotMoveLookAt(pBot, EndPoint + Vector(0.0f, 0.0f, 100.0f)); + pBot->Button |= IN_ATTACK2; + } + else + { + Vector LookAtTarget = EndPoint; + LookAtTarget.z = pBot->CurrentEyePosition.z; + BotMoveLookAt(pBot, LookAtTarget); } } } @@ -8770,7 +8810,7 @@ void NAV_ProgressMovementTask(AvHAIPlayer* pBot) } BotMoveLookAt(pBot, AimLocation); - pBot->DesiredCombatWeapon = WEAPON_MARINE_WELDER; + pBot->DesiredMoveWeapon = WEAPON_MARINE_WELDER; if (GetPlayerCurrentWeapon(pBot->Player) != WEAPON_MARINE_WELDER) { diff --git a/main/source/mod/AvHAIPlayer.cpp b/main/source/mod/AvHAIPlayer.cpp index 2d2d7301..5b0832cd 100644 --- a/main/source/mod/AvHAIPlayer.cpp +++ b/main/source/mod/AvHAIPlayer.cpp @@ -379,7 +379,13 @@ void BotSay(AvHAIPlayer* pBot, bool bTeamSay, float Delay, char* textToSay) bool BotReloadWeapons(AvHAIPlayer* pBot) { // Aliens and commander don't reload - if (!IsPlayerMarine(pBot->Edict) || !IsPlayerActiveInGame(pBot->Edict) || IsPlayerReloading(pBot->Player)) { return false; } + if (!IsPlayerMarine(pBot->Edict) || !IsPlayerActiveInGame(pBot->Edict)) { return false; } + + if (IsPlayerReloading(pBot->Player)) + { + pBot->DesiredCombatWeapon = GetPlayerCurrentWeapon(pBot->Player); + } + AvHAIWeapon PrimaryWeapon = UTIL_GetPlayerPrimaryWeapon(pBot->Player); AvHAIWeapon SecondaryWeapon = GetBotMarineSecondaryWeapon(pBot); @@ -791,12 +797,14 @@ void BotShootTarget(AvHAIPlayer* pBot, AvHAIWeapon AttackWeapon, edict_t* Target // We can be less accurate with spores and umbra since they have AoE effects float MinAcceptableAccuracy = 0.9f; + Vector GetPosition = pBot->Player->GetGunPosition(); + bWillHit = (AimDot >= MinAcceptableAccuracy); if (!bWillHit && IsHitscanWeapon(CurrentWeapon)) { - edict_t* HitEntity = UTIL_TraceEntity(pBot->Edict, pBot->CurrentEyePosition, pBot->CurrentEyePosition + (AimDir * GetMaxIdealWeaponRange(CurrentWeapon))); + edict_t* HitEntity = UTIL_TraceEntity(pBot->Edict, pBot->Player->GetGunPosition(), pBot->Player->GetGunPosition() + (AimDir * GetMaxIdealWeaponRange(CurrentWeapon))); bWillHit = (HitEntity == Target); } @@ -871,7 +879,7 @@ void BotShootLocation(AvHAIPlayer* pBot, AvHAIWeapon AttackWeapon, const Vector return; } - if (CurrentWeapon == WEAPON_NONE) { return; } + if (CurrentWeapon == WEAPON_INVALID) { return; } if (CurrentWeapon == WEAPON_SKULK_XENOCIDE) { @@ -2820,7 +2828,7 @@ void AIPlayerNSMarineThink(AvHAIPlayer* pBot) BotProgressTask(pBot, pBot->CurrentTask); } - if (pBot->DesiredCombatWeapon == WEAPON_NONE) + if (pBot->DesiredCombatWeapon == WEAPON_INVALID) { pBot->DesiredCombatWeapon = BotMarineChooseBestWeapon(pBot, nullptr); } @@ -4245,7 +4253,7 @@ void AIPlayerNSAlienThink(AvHAIPlayer* pBot) BotProgressTask(pBot, pBot->CurrentTask); } - if (pBot->DesiredCombatWeapon == WEAPON_NONE) + if (pBot->DesiredCombatWeapon == WEAPON_INVALID) { pBot->DesiredCombatWeapon = UTIL_GetPlayerPrimaryWeapon(pBot->Player); } diff --git a/main/source/mod/AvHAIPlayerManager.cpp b/main/source/mod/AvHAIPlayerManager.cpp index bccb6298..8bf98037 100644 --- a/main/source/mod/AvHAIPlayerManager.cpp +++ b/main/source/mod/AvHAIPlayerManager.cpp @@ -12,8 +12,6 @@ #include "../dlls/client.h" #include -float MAX_MATCH_TIME = 7200.0f; - double last_think_time = 0.0; vector ActiveAIPlayers; @@ -129,9 +127,14 @@ void AIMGR_UpdateAIPlayerCounts() LastAIPlayerCountUpdate = gpGlobals->time; - bool bMatchExceededMaxLength = (gpGlobals->time - AIStartedTime) > MAX_MATCH_TIME; + float MaxMinutes = CONFIG_GetMaxAIMatchTimeMinutes(); + float MaxSeconds = MaxMinutes * 60.0f; - // If bots are disabled, ensure we've removed all bots from the game + bool bMatchExceededMaxLength = (GetGameRules()->GetGameTime() > MaxSeconds); + + // If bots are disabled or we've exceeded max AI time and no humans are playing, ensure we've removed all bots from the game + // Max AI time is configurable in nsbots.ini, and helps prevent infinite stalemates + // Default time is 90 minutes before bots start leaving to let the map cycle if (!AIMGR_IsBotEnabled() || (bMatchExceededMaxLength && AIMGR_GetNumActiveHumanPlayers() == 0)) { if (AIMGR_GetNumAIPlayers() > 0) diff --git a/main/source/mod/AvHAITask.cpp b/main/source/mod/AvHAITask.cpp index b2d58100..0e0661b1 100644 --- a/main/source/mod/AvHAITask.cpp +++ b/main/source/mod/AvHAITask.cpp @@ -563,9 +563,10 @@ bool AITASK_IsResupplyTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) if (!UTIL_StructureIsFullyBuilt(Task->TaskTarget) || UTIL_StructureIsRecycling(Task->TaskTarget)) { return false; } - return ((pBot->Edict->v.health < pBot->Edict->v.max_health) - || (UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) < UTIL_GetPlayerPrimaryMaxAmmoReserve(pBot->Player)) - || (BotGetSecondaryWeaponAmmoReserve(pBot) < BotGetSecondaryWeaponMaxAmmoReserve(pBot)) + return ( + (pBot->Edict->v.health < pBot->Edict->v.max_health) + || (UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) < UTIL_GetPlayerPrimaryMaxAmmoReserve(pBot->Player)) + || (UTIL_GetPlayerSecondaryAmmoReserve(pBot->Player) < UTIL_GetPlayerSecondaryMaxAmmoReserve(pBot->Player)) ); } diff --git a/main/source/mod/AvHMarineEquipment.cpp b/main/source/mod/AvHMarineEquipment.cpp index a6697014..bbb89a2e 100644 --- a/main/source/mod/AvHMarineEquipment.cpp +++ b/main/source/mod/AvHMarineEquipment.cpp @@ -1977,6 +1977,12 @@ void AvHCommandStation::CommandUse( CBaseEntity* pActivator, CBaseEntity* pCalle this->mTimeToPlayOnlineSound = this->GetTimeAnimationDone(); GetGameRules()->MarkDramaticEvent(kCCNewCommanderPriority, thePlayer, this); + + // A human used the comm chair, let the AI know to keep away + if (!(thePlayer->pev->flags & FL_FAKECLIENT)) + { + AIMGR_SetCommanderAllowedTime(theStationTeamNumber, gpGlobals->time + 20.0f); + } } else {