diff --git a/doc/pr-changelogs/3348.md b/doc/pr-changelogs/3348.md new file mode 100644 index 0000000000..ac2eb226cb --- /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 e8c521ea03..943488558b 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 74eb2cc3db..20e9b266b7 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 6cbc5487a7..b4331f5420 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 5a3ac40771..ae70944ab1 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 ea81d5ae45..f7beb26de9 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