Index: ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js +++ ps/trunk/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: ps/trunk/binaries/data/mods/public/simulation/components/Health.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Health.js +++ ps/trunk/binaries/data/mods/public/simulation/components/Health.js @@ -78,6 +78,14 @@ return this.maxHitpoints; }; +/** + * @return {boolean} Whether the units are injured. Dead units are not considered injured. + */ +Health.prototype.IsInjured = function() +{ + return this.hitpoints > 0 && this.hitpoints < this.GetMaxHitpoints(); +}; + Health.prototype.SetHitpoints = function(value) { // If we're already dead, don't allow resurrection @@ -94,7 +102,7 @@ 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 +115,7 @@ Health.prototype.IsUnhealable = function() { return this.template.Unhealable == "true" || - this.GetHitpoints() <= 0 || - this.GetHitpoints() >= this.GetMaxHitpoints(); + this.hitpoints <= 0 || !this.IsInjured(); }; Health.prototype.GetIdleRegenRate = function() @@ -144,8 +151,8 @@ { // check if we need a timer if (this.GetRegenRate() == 0 && this.GetIdleRegenRate() == 0 || - this.GetHitpoints() == this.GetMaxHitpoints() && this.GetRegenRate() >= 0 && this.GetIdleRegenRate() >= 0 || - this.GetHitpoints() == 0) + !this.IsInjured() && this.GetRegenRate() >= 0 && this.GetIdleRegenRate() >= 0 || + this.hitpoints == 0) { // we don't need a timer, disable if one exists if (this.regenTimer) @@ -199,7 +206,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) @@ -256,7 +263,7 @@ if (cmpFogging) cmpFogging.Activate(); - if (this.hitpoints == this.GetMaxHitpoints()) + if (!this.IsInjured()) return { "old": this.hitpoints, "new": this.hitpoints }; // If we're already dead, don't allow resurrection @@ -266,7 +273,7 @@ let old = this.hitpoints; this.hitpoints = Math.min(this.hitpoints + amount, this.GetMaxHitpoints()); - if (this.hitpoints == this.GetMaxHitpoints()) + if (!this.IsInjured()) { let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); if (cmpRangeManager) @@ -350,7 +357,7 @@ { if (!this.template.DamageVariants) return; - let ratio = this.GetHitpoints() / this.GetMaxHitpoints(); + let ratio = this.hitpoints / this.GetMaxHitpoints(); let newDamageVariant = "alive"; if (ratio > 0) { @@ -386,7 +393,7 @@ let newMaxHitpoints = ApplyValueModificationsToEntity("Health/Max", +this.template.Max, this.entity); if (oldMaxHitpoints != newMaxHitpoints) { - let newHitpoints = this.GetHitpoints() * newMaxHitpoints/oldMaxHitpoints; + let newHitpoints = this.hitpoints * newMaxHitpoints/oldMaxHitpoints; this.maxHitpoints = newMaxHitpoints; this.SetHitpoints(newHitpoints); } 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 @@ -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: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Health.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Health.js +++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Health.js @@ -0,0 +1,150 @@ +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 health_template = { + "Max": 50, + "RegenRate": 0, + "IdleRegenRate": 0, + "DeathType": "corpse", + "Unhealable": false +}; + + +var injured_flag = false; +var corpse_entity; + +function setEntityUp() +{ + let cmpHealth = ConstructComponent(entity_id, "Health", 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; +} + +var 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); + +var 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);