Index: binaries/data/mods/public/simulation/components/GuiInterface.js =================================================================== --- binaries/data/mods/public/simulation/components/GuiInterface.js +++ binaries/data/mods/public/simulation/components/GuiInterface.js @@ -254,7 +254,7 @@ { ret.hitpoints = cmpHealth.GetHitpoints(); ret.maxHitpoints = cmpHealth.GetMaxHitpoints(); - ret.needsRepair = cmpHealth.IsRepairable() && cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints(); + ret.needsRepair = cmpHealth.IsRepairable() && cmpHealth.IsInjured(); ret.needsHeal = !cmpHealth.IsUnhealable(); } Index: binaries/data/mods/public/simulation/components/Health.js =================================================================== --- binaries/data/mods/public/simulation/components/Health.js +++ binaries/data/mods/public/simulation/components/Health.js @@ -78,10 +78,16 @@ return this.maxHitpoints; }; +// Dead units are not considered injured. +Health.prototype.IsInjured = function() +{ + return this.GetHitpoints() > 0 && this.GetHitpoints() < this.GetMaxHitpoints(); +}; + Health.prototype.SetHitpoints = function(value) { // If we're already dead, don't allow resurrection - if (this.hitpoints == 0) + if (this.GetHitpoints() == 0) return; // Before changing the value, activate Fogging if necessary to hide changes @@ -89,12 +95,12 @@ if (cmpFogging) cmpFogging.Activate(); - let old = this.hitpoints; + let old = this.GetHitpoints(); this.hitpoints = Math.max(1, Math.min(this.GetMaxHitpoints(), value)); let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); if (cmpRangeManager) - cmpRangeManager.SetEntityFlag(this.entity, "injured", this.hitpoints < this.GetMaxHitpoints()); + cmpRangeManager.SetEntityFlag(this.entity, "injured", this.IsInjured()); this.RegisterHealthChanged(old); }; @@ -107,8 +113,7 @@ Health.prototype.IsUnhealable = function() { return this.template.Unhealable == "true" || - this.GetHitpoints() <= 0 || - this.GetHitpoints() >= this.GetMaxHitpoints(); + this.GetHitpoints() <= 0 || !this.IsInjured(); }; Health.prototype.GetIdleRegenRate = function() @@ -144,7 +149,7 @@ { // check if we need a timer if (this.GetRegenRate() == 0 && this.GetIdleRegenRate() == 0 || - this.GetHitpoints() == this.GetMaxHitpoints() && this.GetRegenRate() >= 0 && this.GetIdleRegenRate() >= 0 || + !this.IsInjured() && this.GetRegenRate() >= 0 && this.GetIdleRegenRate() >= 0 || this.GetHitpoints() == 0) { // we don't need a timer, disable if one exists @@ -167,7 +172,7 @@ Health.prototype.Kill = function() { - this.Reduce(this.hitpoints); + this.Reduce(this.GetHitpoints()); }; /** @@ -180,7 +185,7 @@ // (The entity will exist a little while after calling DestroyEntity so this // might get called multiple times) // Likewise if the amount is 0. - if (!amount || !this.hitpoints) + if (!amount || !this.GetHitpoints()) return {"killed": false, "change": 0}; // Before changing the value, activate Fogging if necessary to hide changes @@ -188,9 +193,9 @@ if (cmpFogging) cmpFogging.Activate(); - let oldHitpoints = this.hitpoints; + let oldHitpoints = this.GetHitpoints(); // If we reached 0, then die. - if (amount >= this.hitpoints) + if (amount >= this.GetHitpoints()) { this.hitpoints = 0; this.RegisterHealthChanged(oldHitpoints); @@ -199,7 +204,7 @@ } // If we are not marked as injured, do it now - if (this.hitpoints == this.GetMaxHitpoints()) + if (!this.IsInjured()) { let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); if (cmpRangeManager) @@ -208,7 +213,7 @@ this.hitpoints -= amount; this.RegisterHealthChanged(oldHitpoints); - return {"killed": false, "change": (this.hitpoints - oldHitpoints)}; + return {"killed": false, "change": (this.GetHitpoints() - oldHitpoints)}; }; /** @@ -256,17 +261,17 @@ if (cmpFogging) cmpFogging.Activate(); - if (this.hitpoints == this.GetMaxHitpoints()) - return { "old": this.hitpoints, "new": this.hitpoints }; + if (!this.IsInjured()) + return { "old": this.GetHitpoints(), "new": this.GetHitpoints() }; // If we're already dead, don't allow resurrection - if (this.hitpoints == 0) + if (this.GetHitpoints() == 0) return undefined; - let old = this.hitpoints; - this.hitpoints = Math.min(this.hitpoints + amount, this.GetMaxHitpoints()); + let old = this.GetHitpoints(); + this.hitpoints = Math.min(this.GetHitpoints() + amount, this.GetMaxHitpoints()); - if (this.hitpoints == this.GetMaxHitpoints()) + if (!this.IsInjured()) { let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); if (cmpRangeManager) @@ -275,7 +280,7 @@ this.RegisterHealthChanged(old); - return { "old": old, "new": this.hitpoints }; + return { "old": old, "new": this.GetHitpoints() }; }; Health.prototype.CreateCorpse = function(leaveResources) @@ -405,7 +410,7 @@ { this.CheckRegenTimer(); this.UpdateActor(); - Engine.PostMessage(this.entity, MT_HealthChanged, { "from": from, "to": this.hitpoints }); + Engine.PostMessage(this.entity, MT_HealthChanged, { "from": from, "to": this.GetHitpoints() }); }; Engine.RegisterComponentType(IID_Health, "Health", Health); Index: binaries/data/mods/public/simulation/components/UnitAI.js =================================================================== --- binaries/data/mods/public/simulation/components/UnitAI.js +++ binaries/data/mods/public/simulation/components/UnitAI.js @@ -1426,7 +1426,7 @@ var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity); var cmpHealth = Engine.QueryInterface(this.isGuardOf, IID_Health); if (cmpIdentity && cmpIdentity.HasClass("Support") && - cmpHealth && cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints()) + cmpHealth && cmpHealth.IsInjured()) { if (this.CanHeal(this.isGuardOf)) this.PushOrderFront("Heal", { "target": this.isGuardOf, "force": false }); @@ -1716,7 +1716,7 @@ { // if nothing better to do, check if the guarded needs to be healed or repaired var cmpHealth = Engine.QueryInterface(this.isGuardOf, IID_Health); - if (cmpHealth && (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints())) + if (cmpHealth && cmpHealth.IsInjured()) { if (this.CanHeal(this.isGuardOf)) this.PushOrderFront("Heal", { "target": this.isGuardOf, "force": false }); Index: binaries/data/mods/public/simulation/components/tests/test_Health.js =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/components/tests/test_Health.js @@ -0,0 +1,151 @@ +Engine.LoadComponentScript("interfaces/TechnologyManager.js"); +Engine.LoadComponentScript("interfaces/AuraManager.js"); +Engine.LoadHelperScript("Player.js"); +Engine.LoadHelperScript("ValueModification.js"); + +Engine.LoadHelperScript("Sound.js"); +Engine.LoadComponentScript("interfaces/DeathDamage.js"); + +Engine.LoadComponentScript("interfaces/Health.js"); +Engine.LoadComponentScript("Health.js"); + +const entity_id = 5; +const corpse_id = entity_id + 1; + +const template = { + "Max": 50, + "RegenRate": 0, + "IdleRegenRate": 0, + "DeathType": "corpse", + "Unhealable": false +}; + +function setEntityUp() { + let cmpHealth = ConstructComponent(entity_id, "Health", template); + + AddMock(entity_id, IID_DeathDamage, { + "CauseDeathDamage": () => {} + }); + AddMock(entity_id, IID_Position, { + "IsInWorld": () => true, + "GetPosition": () => ({ "x" : 0, "z": 0 }), + "GetRotation": () => ({ "x" : 0, "y": 0, "z": 0 }) + }); + AddMock(entity_id, IID_Ownership, { + "GetOwner": () => 1 + }); + AddMock(entity_id, IID_Visual, { + "GetActorSeed": () => 1 + }); + AddMock(SYSTEM_ENTITY, IID_TemplateManager, { + "GetCurrentTemplateName": () => "test" + }); + + AddMock(SYSTEM_ENTITY, IID_RangeManager, { + "SetEntityFlag": (ent, flag, value) => injured_flag = value + }); + + return cmpHealth; +}; + +let injured_flag = false; +let corpse_entity; + +let cmpHealth = setEntityUp(); + +//////////////////////////////////////////// +//////////////////////////////////////////// + +TS_ASSERT_EQUALS(cmpHealth.GetHitpoints(), 50); +TS_ASSERT_EQUALS(cmpHealth.GetMaxHitpoints(), 50); +TS_ASSERT_EQUALS(cmpHealth.IsInjured(), false); +TS_ASSERT_EQUALS(cmpHealth.IsUnhealable(), true); + +let change = cmpHealth.Reduce(25); +TS_ASSERT_EQUALS(injured_flag, true); + +TS_ASSERT_EQUALS(change.killed, false); +TS_ASSERT_EQUALS(change.change, -25); +TS_ASSERT_EQUALS(cmpHealth.GetHitpoints(), 25); +TS_ASSERT_EQUALS(cmpHealth.GetMaxHitpoints(), 50); +TS_ASSERT_EQUALS(cmpHealth.IsInjured(), true); +TS_ASSERT_EQUALS(cmpHealth.IsUnhealable(), false); + +change = cmpHealth.Increase(25); +TS_ASSERT_EQUALS(injured_flag, false); + +TS_ASSERT_EQUALS(change.new, 50); +TS_ASSERT_EQUALS(cmpHealth.GetHitpoints(), 50); +TS_ASSERT_EQUALS(cmpHealth.GetMaxHitpoints(), 50); +TS_ASSERT_EQUALS(cmpHealth.IsInjured(), false); +TS_ASSERT_EQUALS(cmpHealth.IsUnhealable(), true); + +// Check death. +Engine.AddLocalEntity = function(template) { + corpse_entity = template; + + AddMock(corpse_id, IID_Position, { + "JumpTo": () => {}, + "SetYRotation": () => {}, + "SetXZRotation": () => {}, + }); + AddMock(corpse_id, IID_Ownership, { + "SetOwner": () => {}, + }); + AddMock(corpse_id, IID_Visual, { + "SetActorSeed": () => {}, + "SelectAnimation": () => {}, + }); + return corpse_id; +}; + +change = cmpHealth.Reduce(50); + +// Assert we create a corpse with the proper template. +TS_ASSERT_EQUALS(corpse_entity, "corpse|test"); + +// Check that we are not marked as injured. +TS_ASSERT_EQUALS(injured_flag, false); + +TS_ASSERT_EQUALS(change.killed, true); +TS_ASSERT_EQUALS(change.change, -50); +TS_ASSERT_EQUALS(cmpHealth.GetHitpoints(), 0); +TS_ASSERT_EQUALS(cmpHealth.GetMaxHitpoints(), 50); +TS_ASSERT_EQUALS(cmpHealth.IsInjured(), false); + +// Check that we can't be revived once dead. +change = cmpHealth.Increase(25); +TS_ASSERT_EQUALS(change.new, 0); +TS_ASSERT_EQUALS(cmpHealth.GetHitpoints(), 0); +TS_ASSERT_EQUALS(cmpHealth.GetMaxHitpoints(), 50); +TS_ASSERT_EQUALS(cmpHealth.IsInjured(), false); + +// Check that we can't die twice. +change = cmpHealth.Reduce(50); +TS_ASSERT_EQUALS(change.killed, false); +TS_ASSERT_EQUALS(change.change, 0); +TS_ASSERT_EQUALS(cmpHealth.GetHitpoints(), 0); +TS_ASSERT_EQUALS(cmpHealth.GetMaxHitpoints(), 50); +TS_ASSERT_EQUALS(cmpHealth.IsInjured(), false); + +cmpHealth = setEntityUp(); + +// Check that we still die with > Max HP of damage. +change = cmpHealth.Reduce(60); +TS_ASSERT_EQUALS(change.killed, true); +TS_ASSERT_EQUALS(change.change, -50); +TS_ASSERT_EQUALS(cmpHealth.GetHitpoints(), 0); +TS_ASSERT_EQUALS(cmpHealth.GetMaxHitpoints(), 50); +TS_ASSERT_EQUALS(cmpHealth.IsInjured(), false); + +cmpHealth = setEntityUp(); + +// Check that increasing by more than required puts us at the max HP +change = cmpHealth.Reduce(30); +change = cmpHealth.Increase(30); +TS_ASSERT_EQUALS(injured_flag, false); +TS_ASSERT_EQUALS(change.new, 50); +TS_ASSERT_EQUALS(cmpHealth.GetHitpoints(), 50); +TS_ASSERT_EQUALS(cmpHealth.GetMaxHitpoints(), 50); +TS_ASSERT_EQUALS(cmpHealth.IsInjured(), false); +TS_ASSERT_EQUALS(cmpHealth.IsUnhealable(), true);