Index: binaries/data/mods/public/simulation/components/Attack.js =================================================================== --- binaries/data/mods/public/simulation/components/Attack.js +++ binaries/data/mods/public/simulation/components/Attack.js @@ -553,10 +553,18 @@ if (!this.target) return; - // We check the range after the attack to facilitate chasing. + // NB: range checks are performed after the attack to facilitate chasing, + // as units aren't clever enough to offset the prepare time idleness. + + // We'll spend this turn attacking. If we immediately stop on OutOfRange, + // the engine will stop the attacking animation earlier than expected, + // sounds will not play and the unit will start moving instantly. + // To avoid this, stop on the next turn - it will let the animation go through. if (!this.IsTargetInRange(this.target, type)) { - this.StopAttacking("OutOfRange"); + // Reuse the attack timer for this. + cmpTimer.CancelTimer(this.timer); + this.timer = cmpTimer.SetTimeout(this.entity, IID_Attack, "StopAttacking", 1, "OutOfRange"); return; } @@ -573,6 +581,17 @@ } }; +/** + * Called by CmpResistance when our target dies. + */ +Attack.prototype.TargetDeath = function() +{ + // React next turn, same reasoning as OutOfRange in Attack + const cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + cmpTimer.CancelTimer(this.timer); + this.timer = cmpTimer.SetTimeout(this.entity, IID_Attack, "StopAttacking", 1, "TargetInvalidated"); +}; + /** * Attack the target entity. This should only be called after a successful range check, * and should only be called after GetTimers().repeat msec has passed since the last Index: binaries/data/mods/public/simulation/components/Resistance.js =================================================================== --- binaries/data/mods/public/simulation/components/Resistance.js +++ binaries/data/mods/public/simulation/components/Resistance.js @@ -178,7 +178,7 @@ { if (msg.to === INVALID_PLAYER) for (let attacker of this.attackers) - Engine.QueryInterface(attacker, IID_Attack)?.StopAttacking("TargetInvalidated"); + Engine.QueryInterface(attacker, IID_Attack)?.TargetDeath(); };