Index: ps/trunk/binaries/data/mods/public/simulation/components/Attack.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Attack.js +++ ps/trunk/binaries/data/mods/public/simulation/components/Attack.js @@ -455,6 +455,10 @@ if (!this.CanAttack(target, [type])) return false; + let cmpResistance = Engine.QueryInterface(target, IID_Resistance); + if (!cmpResistance || !cmpResistance.AddAttacker(this.entity)) + return false; + let timings = this.GetTimers(type); let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); @@ -496,6 +500,10 @@ cmpTimer.CancelTimer(this.timer); delete this.timer; + let cmpResistance = Engine.QueryInterface(this.target, IID_Resistance); + if (cmpResistance) + cmpResistance.RemoveAttacker(this.entity); + delete this.target; let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); @@ -539,10 +547,7 @@ this.PerformAttack(type, this.target); if (!this.target) - { - this.StopAttacking("TargetInvalidated"); return; - } // We check the range after the attack to facilitate chasing. if (!this.IsTargetInRange(this.target, type)) Index: ps/trunk/binaries/data/mods/public/simulation/components/Resistance.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Resistance.js +++ ps/trunk/binaries/data/mods/public/simulation/components/Resistance.js @@ -73,6 +73,7 @@ Resistance.prototype.Init = function() { this.invulnerable = false; + this.attackers = new Set(); }; Resistance.prototype.IsInvulnerable = function() @@ -80,6 +81,28 @@ return this.invulnerable; }; +/** + * @param {number} attacker - The entity ID of the attacker to add. + * @return {boolean} - Whether the attacker was added sucessfully. + */ +Resistance.prototype.AddAttacker = function(attacker) +{ + if (this.attackers.has(attacker)) + return false; + + this.attackers.add(attacker); + return true; +}; + +/** + * @param {number} attacker - The entity ID of the attacker to remove. + * @return {boolean} - Whether the attacker was attacking us previously. + */ +Resistance.prototype.RemoveAttacker = function(attacker) +{ + return this.attackers.delete(attacker); +}; + Resistance.prototype.SetInvulnerability = function(invulnerability) { this.invulnerable = invulnerability; @@ -151,4 +174,11 @@ return ret; }; +Resistance.prototype.OnOwnershipChanged = function(msg) +{ + if (msg.to === INVALID_PLAYER) + for (let attacker of this.attackers) + Engine.QueryInterface(attacker, IID_Attack)?.StopAttacking("TargetInvalidated"); +}; + Engine.RegisterComponentType(IID_Resistance, "Resistance", Resistance); Index: ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js +++ ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js @@ -2156,9 +2156,6 @@ this.SetNextState("FINDINGNEWTARGET"); }, - // TODO: respond to target deaths immediately, rather than waiting - // until the next Timer event - "Attacked": function(msg) { if (this.order.data.attackType == "Capture" && (this.GetStance().targetAttackersAlways || !this.order.data.force) && this.order.data.target != msg.data.attacker && this.GetBestAttackAgainst(msg.data.attacker, true) != "Capture") Index: ps/trunk/binaries/data/mods/public/simulation/templates/special/filter/mirage.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/special/filter/mirage.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/special/filter/mirage.xml @@ -24,6 +24,7 @@ +