From b6a618f3f2764b2cea5f53cbc7e4c63d36707b67 Mon Sep 17 00:00:00 2001 From: eun-ice Date: Wed, 16 Sep 2026 04:19:54 -0600 Subject: [PATCH] Fix setNeutral causing attack orders to be skipped and skip crashing aircraft as attack targets (#3348) * Reset attack state after StopAttackingTargetIf and skip crashing aircraft as attack targets Two related problems with Attack queues over aircraft (#3347): 1. CCommandAI::StopAttackingTargetIf (used by CUnit::SetNeutral, which BAR's crashing-aircraft gadget calls for every shot-down plane) erases the order being executed from the queue without FinishCommand. inCommand stays CMD_ATTACK and orderTarget keeps the old target, so the next queued Attack is never initialised: the unit idles with that order at the front of its queue until the target dies or leaves radar. Reset the execution state when the front order is erased so the next order starts normally at the unit's next SlowUpdate. 2. A crashing flyer is not an available attack target (weapons refuse it unless fireAtCrashing), but only CAirCAI noticed, and only one SlowUpdate after the order started; CMobileCAI and CCommandAI kept such an order until the aircraft was deleted. Check the target when the order starts executing in all three CAIs and end an ongoing attack when its target starts crashing, matching the existing AirCAI periodic check. Co-Authored-By: Claude Fable 5.1 * Remove comment Co-authored-by: sprunk * Remove Comment. Co-authored-by: sprunk * Respect fireAtCrashing when selecting and executing attack targets Share the command-AI crashing-target predicate, honor the modrule in fighter target searches, and add the PR changelog snippet. Validation: Podman headless build and 52 regression assertions across both modrule settings passed. AI assistance: OpenAI Codex prepared the changes and automated validation. --------- Co-authored-by: Claude Fable 5.1 Co-authored-by: sprunk --- doc/pr-changelogs/3348.md | 2 ++ rts/Game/GameHelper.cpp | 2 +- rts/Sim/Units/CommandAI/AirCAI.cpp | 8 ++++++-- rts/Sim/Units/CommandAI/CommandAI.cpp | 25 +++++++++++++++++++++++++ rts/Sim/Units/CommandAI/CommandAI.h | 6 ++---- rts/Sim/Units/CommandAI/MobileCAI.cpp | 9 +++++++++ 6 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 doc/pr-changelogs/3348.md diff --git a/doc/pr-changelogs/3348.md b/doc/pr-changelogs/3348.md new file mode 100644 index 00000000000..ac2eb226cb7 --- /dev/null +++ b/doc/pr-changelogs/3348.md @@ -0,0 +1,2 @@ + * fixed attack queues stalling or skipping the next target when the current target is made neutral. + * attack orders now skip crashing aircraft when the `fireAtDead.fireAtCrashing` modrule is disabled. Fighters also respect this modrule when selecting targets automatically. diff --git a/rts/Game/GameHelper.cpp b/rts/Game/GameHelper.cpp index e8c521ea03f..943488558b5 100644 --- a/rts/Game/GameHelper.cpp +++ b/rts/Game/GameHelper.cpp @@ -463,7 +463,7 @@ namespace { { EnemyAircraft(const CUnit* exclUnit, int allyTeam) : Enemy_InLos(exclUnit, allyTeam) {} bool Unit(const CUnit* u) { - return (u->unitDef->canfly && !u->IsCrashing() && Enemy_InLos::Unit(u)); + return (u->unitDef->canfly && (modInfo.fireAtCrashing || !u->IsCrashing()) && Enemy_InLos::Unit(u)); } }; diff --git a/rts/Sim/Units/CommandAI/AirCAI.cpp b/rts/Sim/Units/CommandAI/AirCAI.cpp index 74eb2cc3db5..20e9b266b73 100644 --- a/rts/Sim/Units/CommandAI/AirCAI.cpp +++ b/rts/Sim/Units/CommandAI/AirCAI.cpp @@ -402,7 +402,7 @@ void CAirCAI::ExecuteAttack(Command& c) return; } if (orderTarget != nullptr) { - if (orderTarget->unitDef->canfly && orderTarget->IsCrashing()) { + if (SkipCrashingTarget(orderTarget)) { owner->DropCurrentAttackTarget(); StopMoveAndFinishCommand(); return; @@ -431,6 +431,10 @@ void CAirCAI::ExecuteAttack(Command& c) StopMoveAndFinishCommand(); return; } + if (SkipCrashingTarget(targetUnit)) { + StopMoveAndFinishCommand(); + return; + } SetGoal(targetUnit->pos, owner->pos, cancelDistance); SetOrderTarget(targetUnit); @@ -539,7 +543,7 @@ int CAirCAI::GetDefaultCmd(const CUnit* pointed, const CFeature* feature) bool CAirCAI::IsValidTarget(const CUnit* enemy, CWeapon* weapon) const { if (!CMobileCAI::IsValidTarget(enemy, weapon)) return false; - if (enemy->IsCrashing()) + if (SkipCrashingTarget(enemy)) return false; return (GetStrafeAirMoveType(owner)->isFighter || !enemy->unitDef->canfly); } diff --git a/rts/Sim/Units/CommandAI/CommandAI.cpp b/rts/Sim/Units/CommandAI/CommandAI.cpp index 6cbc5487a70..b4331f5420c 100644 --- a/rts/Sim/Units/CommandAI/CommandAI.cpp +++ b/rts/Sim/Units/CommandAI/CommandAI.cpp @@ -1497,6 +1497,11 @@ void CCommandAI::ExecuteAttack(Command& c) FinishCommand(); return; } + if (SkipCrashingTarget(orderTarget)) { + owner->DropCurrentAttackTarget(); + FinishCommand(); + return; + } if (!(c.GetOpts() & ALT_KEY) && SkipParalyzeTarget(orderTarget)) { FinishCommand(); return; @@ -1517,6 +1522,10 @@ void CCommandAI::ExecuteAttack(Command& c) FinishCommand(); return; } + if (SkipCrashingTarget(targetUnit)) { + FinishCommand(); + return; + } SetOrderTarget(targetUnit); owner->AttackUnit(targetUnit, !c.IsInternalOrder(), c.GetID() == CMD_MANUALFIRE); @@ -1832,6 +1841,11 @@ bool CCommandAI::HasMoreMoveCommands(bool skipFirstCmd) const bool CCommandAI::CanChangeFireState() const { return (owner->unitDef->CanChangeFireState()); } +bool CCommandAI::SkipCrashingTarget(const CUnit* target) const +{ + return (target != nullptr && !modInfo.fireAtCrashing && target->IsCrashing()); +} + bool CCommandAI::SkipParalyzeTarget(const CUnit* target) const { RECOIL_DETAILED_TRACY_ZONE; @@ -1854,7 +1868,18 @@ void CCommandAI::StopAttackingTargetIf(const std::function& const auto hasTarget = [&](const Command& c) { return (c.GetNumParams() == 1 && (c.GetID() == CMD_FIGHT || c.GetID() == CMD_ATTACK)); }; const auto removeCmd = [&](const Command& c) { return (hasTarget(c) && pred(unitHandler.GetUnit(c.GetParam(0)))); }; + const bool frontRemoved = (!commandQue.empty() && removeCmd(commandQue.front())); + commandQue.erase(std::remove_if(commandQue.begin(), commandQue.end(), removeCmd), commandQue.end()); + + // the order being executed was erased without FinishCommand; clear its + // execution state so the next queued order starts fresh instead of being + // judged against the old target (or never initialised at all) + if (frontRemoved) { + inCommand = CMD_STOP; + targetDied = false; + SetOrderTarget(nullptr); + } } void CCommandAI::StopAttackingAllyTeam(int ally) diff --git a/rts/Sim/Units/CommandAI/CommandAI.h b/rts/Sim/Units/CommandAI/CommandAI.h index 5a3ac40771c..ae70944ab12 100644 --- a/rts/Sim/Units/CommandAI/CommandAI.h +++ b/rts/Sim/Units/CommandAI/CommandAI.h @@ -1,7 +1,6 @@ /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ -#ifndef _COMMAND_AI_H -#define _COMMAND_AI_H +#pragma once #include #include @@ -139,6 +138,7 @@ class CCommandAI : public CObject virtual bool SelectNewAreaAttackTargetOrPos(const Command& ac) { return true; } bool IsAttackCapable() const; + bool SkipCrashingTarget(const CUnit* target) const; bool SkipParalyzeTarget(const CUnit* target) const; void GiveAllowedCommand(const Command& c, bool fromSynced = true); @@ -176,5 +176,3 @@ inline void CCommandAI::SetOrderTarget(CUnit* o) { AddDeathDependence(reinterpret_cast(orderTarget), DEPENDENCE_ORDERTARGET); } } - -#endif // _COMMAND_AI_H diff --git a/rts/Sim/Units/CommandAI/MobileCAI.cpp b/rts/Sim/Units/CommandAI/MobileCAI.cpp index ea81d5ae457..f7beb26de9c 100644 --- a/rts/Sim/Units/CommandAI/MobileCAI.cpp +++ b/rts/Sim/Units/CommandAI/MobileCAI.cpp @@ -922,6 +922,10 @@ void CMobileCAI::ExecuteAttack(Command& c) StopMoveAndFinishCommand(); return; } + if (SkipCrashingTarget(targetUnit)) { + StopMoveAndFinishCommand(); + return; + } const float3 tgtErrPos = targetUnit->GetErrorPos(owner->allyteam, false); const float3 tgtPosDir = (tgtErrPos - owner->pos).Normalize(); @@ -953,6 +957,11 @@ void CMobileCAI::ExecuteAttack(Command& c) StopMoveAndFinishCommand(); return; } + if (SkipCrashingTarget(orderTarget)) { + owner->DropCurrentAttackTarget(); + StopMoveAndFinishCommand(); + return; + } // user clicked on enemy unit; either stop and attack or turn/move toward it