Index: binaries/data/mods/public/simulation/components/tests/test_Damage.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_Damage.js +++ binaries/data/mods/public/simulation/components/tests/test_Damage.js @@ -106,6 +106,11 @@ }, }); + AddMock(target, IID_Resistance, { + "GetEffectiveResistanceAgainst": (effectType) => ({ "Damage": { "Crush": 0 } }), + "IsInvulnerable": () => false, + }); + AddMock(SYSTEM_ENTITY, IID_DelayedDamage, { "Hit": () => { damageTaken = true; @@ -232,6 +237,21 @@ "GetPosition2D": () => new Vector2D(5, 2), }); + AddMock(60, IID_Resistance, { + "GetEffectiveResistanceAgainst": (effectType) => ({ "Damage": { "Hack": 0 } }), + "IsInvulnerable": () => false, + }); + + AddMock(61, IID_Resistance, { + "GetEffectiveResistanceAgainst": (effectType) => ({ "Damage": { "Hack": 0 } }), + "IsInvulnerable": () => false, + }); + + AddMock(62, IID_Resistance, { + "GetEffectiveResistanceAgainst": (effectType) => ({ "Damage": { "Hack": 0 } }), + "IsInvulnerable": () => false, + }); + AddMock(60, IID_Health, { "TakeDamage": (amount, __, ___) => { hitEnts.add(60); @@ -345,6 +365,13 @@ "GetPosition2D": () => new Vector2D(23, 4), }); + for (let i = 60; i <= 65; ++i) + AddMock(i, IID_Resistance, { + "GetEffectiveResistanceAgainst": (effectType) => ({ "Damage": { "Hack": 0 } }), + "IsInvulnerable": () => false, + }); + + AddMock(60, IID_Health, { "TakeDamage": (amount, __, ___) => { TS_ASSERT_EQUALS(amount, 100 * fallOff(0)); @@ -464,6 +491,11 @@ "GetShape": () => ({ "type": "circle", "radius": 20 }), }); + AddMock(60, IID_Resistance, { + "GetEffectiveResistanceAgainst": (effectType) => ({ "Damage": { "Pierce": 0 } }), + "IsInvulnerable": () => false, + }); + AddMock(70, IID_Ownership, { "GetOwner": () => 1, }); @@ -510,6 +542,11 @@ "GetShape": () => ({ "type": "circle", "radius": 20 }) }); + AddMock(61, IID_Resistance, { + "GetEffectiveResistanceAgainst": (effectType) => ({ "Damage": { "Pierce": 0 } }), + "IsInvulnerable": () => false, + }); + cmpDelayedDamage.Hit(data, 0); TS_ASSERT(hitEnts.has(61)); hitEnts.clear(); @@ -570,6 +607,11 @@ } }); + AddMock(61, IID_Resistance, { + "GetEffectiveResistanceAgainst": (effectType) => ({ "Damage": { "Pierce": 0, "Crush": 0 } }), + "IsInvulnerable": () => false, + }); + AddMock(62, IID_Position, { "GetPosition": () => new Vector3D(8, 10, 0), "GetPreviousPosition": () => new Vector3D(8, 10, 0), @@ -589,6 +631,11 @@ "GetShape": () => ({ "type": "circle", "radius": 20 }), }); + AddMock(62, IID_Resistance, { + "GetEffectiveResistanceAgainst": (effectType) => ({ "Damage": { "Pierce": 0, "Crush": 0 } }), + "IsInvulnerable": () => false, + }); + cmpDelayedDamage.Hit(data, 0); TS_ASSERT(hitEnts.has(61)); TS_ASSERT_EQUALS(dealtDamage, 100 + 200); @@ -677,6 +724,11 @@ } }); + AddMock(61, IID_Resistance, { + "GetEffectiveResistanceAgainst": (effectType) => ({ "Damage": { "Pierce": 0, "Crush": 0 } }), + "IsInvulnerable": () => false, + }); + AddMock(62, IID_Position, { "GetPosition": () => new Vector3D(8, 10, 0), "GetPreviousPosition": () => new Vector3D(8, 10, 0), @@ -696,6 +748,11 @@ "GetShape": () => ({ "type": "circle", "radius": 20 }), }); + AddMock(62, IID_Resistance, { + "GetEffectiveResistanceAgainst": (effectType) => ({ "Damage": { "Pierce": 0, "Crush": 0 } }), + "IsInvulnerable": () => false, + }); + cmpDelayedDamage.Hit(data, 0); TS_ASSERT(hitEnts.has(61)); TS_ASSERT_EQUALS(dealtDamage, 100 + 200); Index: binaries/data/mods/public/simulation/components/tests/test_Resistance.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_Resistance.js +++ binaries/data/mods/public/simulation/components/tests/test_Resistance.js @@ -58,7 +58,7 @@ TestInvulnerability() { - this.Reset(); + this.Reset({ "Entity": { "Damage": { "Name": 0 } } }); let damage = 5; let attackData = { "Damage": { "Name": damage } }; @@ -90,9 +90,37 @@ TS_ASSERT_EQUALS(spy._called, 1); } + TestInvulnerabilityThroughUnspecifiedResistance() + { + this.Reset({ "Entity": { "Damage": {} } }); + + let damage = 5; + let attackData = { "Damage": { "Name": damage } }; + let attackType = "Test"; + + TS_ASSERT(!this.cmpResistance.IsInvulnerable()); + + let cmpHealth = AddMock(this.EntityID, IID_Health, { + "TakeDamage": (amount, __, ___) => { + TS_ASSERT_EQUALS(amount, 0); + return { "healthChange": -amount }; + } + }); + let spy = new Spy(cmpHealth, "TakeDamage"); + let data = { + "type": attackType, + "attackData": attackData, + "attacker": this.AttackerID, + "attackerOwner": this.EnemyID + }; + + AttackHelper.HandleAttackEffects(this.EntityID, data); + TS_ASSERT_EQUALS(spy._called, 1); + } + TestBonus() { - this.Reset(); + this.Reset({ "Entity": { "Damage": { "Name": 0 } } }); let damage = 5; let bonus = 2; @@ -358,6 +386,7 @@ let captureResistanceValue = 2; this.Reset({ "Entity": { + "Damage": { "Name": 0 }, "Capture": captureResistanceValue } }); Index: binaries/data/mods/public/simulation/helpers/Attack.js =================================================================== --- binaries/data/mods/public/simulation/helpers/Attack.js +++ binaries/data/mods/public/simulation/helpers/Attack.js @@ -169,12 +169,13 @@ let resistanceStrengths = cmpResistance ? cmpResistance.GetEffectiveResistanceAgainst(effectType) : {}; + // Having no damage/capture resistance to a given type specified is assumed to mean invulnerability. if (effectType == "Damage") - for (let type in effectData.Damage) - total += effectData.Damage[type] * Math.pow(0.9, resistanceStrengths.Damage ? resistanceStrengths.Damage[type] || 0 : 0); - else if (effectType == "Capture") + for (let type in resistanceStrengths?.Damage) + total += effectData.Damage[type] * Math.pow(0.9, resistanceStrengths.Damage[type]); + else if (effectType == "Capture" && resistanceStrengths.Capture !== undefined) { - total = effectData.Capture * Math.pow(0.9, resistanceStrengths.Capture || 0); + total = effectData.Capture * Math.pow(0.9, resistanceStrengths.Capture); // If Health is lower we are more susceptible to capture attacks. let cmpHealth = Engine.QueryInterface(target, IID_Health); @@ -184,6 +185,7 @@ if (effectType != "ApplyStatus") return total * bonusMultiplier; + // TODO: treat undefined as invulnerable. if (!resistanceStrengths.ApplyStatus) return effectData[effectType]; Index: binaries/data/mods/public/simulation/helpers/tests/test_Attack.js =================================================================== --- binaries/data/mods/public/simulation/helpers/tests/test_Attack.js +++ binaries/data/mods/public/simulation/helpers/tests/test_Attack.js @@ -36,7 +36,7 @@ this.TESTED_ENTITY_ID = 5; this.attackData = { - "Damage": "1", + "Damage": { "Name": "1" }, "Capture": "2", "ApplyStatus": { "statusName": {} @@ -44,20 +44,40 @@ }; } + setupTarget(id, hasHealth, hasCapture) + { + DeleteMock(id, IID_Health); + DeleteMock(id, IID_Capturable); + + // Resistance is always needed. + AddMock(id, IID_Resistance, { + "GetEffectiveResistanceAgainst": (effectType) => ({ + "Damage": { + "Name": 0, + }, + "Capture": 0 + }), + "IsInvulnerable": () => false, + }); + + if (hasHealth) + AddMock(id, IID_Health, { + "TakeDamage": x => { this.resultString += x; return { "Damage": x }; }, + "GetHitpoints": () => 1, + "GetMaxHitpoints": () => 1, + }); + + if (hasCapture) + AddMock(id, IID_Capturable, { + "Capture": x => { this.resultString += x; return { "Capture": x }; }, + }); + } + /** * This tests that we inflict multiple effect types. */ testMultipleEffects() { - AddMock(this.TESTED_ENTITY_ID, IID_Health, { - "TakeDamage": x => { this.resultString += x; }, - "GetHitpoints": () => 1, - "GetMaxHitpoints": () => 1, - }); - - AddMock(this.TESTED_ENTITY_ID, IID_Capturable, { - "Capture": x => { this.resultString += x; }, - }); - + this.setupTarget(this.TESTED_ENTITY_ID, true, true); AttackHelper.HandleAttackEffects(this.TESTED_ENTITY_ID, { "type": "Test", "attackData": this.attackData, @@ -65,7 +85,7 @@ "attackerOwner": INVALID_PLAYER }); - TS_ASSERT(this.resultString.indexOf(this.attackData.Damage) !== -1); + TS_ASSERT(this.resultString.indexOf(this.attackData.Damage.Name) !== -1); TS_ASSERT(this.resultString.indexOf(this.attackData.Capture) !== -1); } @@ -73,9 +93,7 @@ * This tests that we correctly handle effect types if one is not received. */ testSkippedEffect() { - AddMock(this.TESTED_ENTITY_ID, IID_Capturable, { - "Capture": x => { this.resultString += x; }, - }); + this.setupTarget(this.TESTED_ENTITY_ID, false, true); AttackHelper.HandleAttackEffects(this.TESTED_ENTITY_ID, { "type": "Test", @@ -83,16 +101,11 @@ "attacker": INVALID_ENTITY, "attackerOwner": INVALID_PLAYER }); - TS_ASSERT(this.resultString.indexOf(this.attackData.Damage) === -1); + TS_ASSERT(this.resultString.indexOf(this.attackData.Damage.Name) === -1); TS_ASSERT(this.resultString.indexOf(this.attackData.Capture) !== -1); this.resultString = ""; - DeleteMock(this.TESTED_ENTITY_ID, IID_Capturable); - AddMock(this.TESTED_ENTITY_ID, IID_Health, { - "TakeDamage": x => { this.resultString += x; }, - "GetHitpoints": () => 1, - "GetMaxHitpoints": () => 1, - }); + this.setupTarget(this.TESTED_ENTITY_ID, true, false); AttackHelper.HandleAttackEffects(this.TESTED_ENTITY_ID, { "type": "Test", @@ -100,7 +113,7 @@ "attacker": INVALID_ENTITY, "attackerOwner": INVALID_PLAYER }); - TS_ASSERT(this.resultString.indexOf(this.attackData.Damage) !== -1); + TS_ASSERT(this.resultString.indexOf(this.attackData.Damage.Name) !== -1); TS_ASSERT(this.resultString.indexOf(this.attackData.Capture) === -1); } @@ -109,6 +122,9 @@ */ testAttackedMessage() { Engine.PostMessage = () => TS_ASSERT(false); + + this.setupTarget(this.TESTED_ENTITY_ID, false, false); + AttackHelper.HandleAttackEffects(this.TESTED_ENTITY_ID, { "type": "Test", "attackData": this.attackData, @@ -116,9 +132,8 @@ "attackerOwner": INVALID_PLAYER }); - AddMock(this.TESTED_ENTITY_ID, IID_Capturable, { - "Capture": () => ({ "captureChange": 0 }), - }); + this.setupTarget(this.TESTED_ENTITY_ID, false, true); + let count = 0; Engine.PostMessage = () => count++; AttackHelper.HandleAttackEffects(this.TESTED_ENTITY_ID, { @@ -129,11 +144,8 @@ }); TS_ASSERT_EQUALS(count, 1); - AddMock(this.TESTED_ENTITY_ID, IID_Health, { - "TakeDamage": () => ({ "healthChange": 0 }), - "GetHitpoints": () => 1, - "GetMaxHitpoints": () => 1, - }); + this.setupTarget(this.TESTED_ENTITY_ID, true, true); + count = 0; Engine.PostMessage = () => count++; AttackHelper.HandleAttackEffects(this.TESTED_ENTITY_ID, { @@ -169,9 +181,10 @@ * Regression test that bonus multiplier is handled correctly. */ testBonusMultiplier() { + this.setupTarget(this.TESTED_ENTITY_ID, false, false); AddMock(this.TESTED_ENTITY_ID, IID_Health, { "TakeDamage": (amount, __, ___) => { - TS_ASSERT_EQUALS(amount, this.attackData.Damage * 2); + TS_ASSERT_EQUALS(amount, this.attackData.Damage.Name * 2); }, "GetHitpoints": () => 1, "GetMaxHitpoints": () => 1, Index: binaries/data/mods/public/simulation/templates/template_structure.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_structure.xml +++ binaries/data/mods/public/simulation/templates/template_structure.xml @@ -115,6 +115,7 @@ 1 10 1 + 0 @@ -128,6 +129,7 @@ 1 10 1 + 0 Index: binaries/data/mods/public/simulation/templates/template_unit.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_unit.xml +++ binaries/data/mods/public/simulation/templates/template_unit.xml @@ -82,6 +82,8 @@ 1 1 1 + 0 + 0