Index: binaries/data/mods/public/gui/common/tooltips.js =================================================================== --- binaries/data/mods/public/gui/common/tooltips.js +++ binaries/data/mods/public/gui/common/tooltips.js @@ -333,7 +333,7 @@ for (let status in attackTypeTemplate.ApplyStatus) { let status_template = g_StatusEffectsMetadata.augment(status, attackTypeTemplate.ApplyStatus[status]); - statusEffectsDetails.push("\n " + getStatusEffectsTooltip(status_template)); + statusEffectsDetails.push("\n " + getStatusEffectsTooltip(status_template, true)); } statusEffectsDetails = statusEffectsDetails.join(""); @@ -380,48 +380,67 @@ return tooltips.join("\n"); } -function getStatusEffectsTooltip(template) +/** + * @param applier - if true, return the tooltip for the Applier. If false, Receiver is returned. + */ +function getStatusEffectsTooltip(template, applier) { let tooltipAttributes = []; - let tooltipString = ""; - if (template.StatusTooltip) - { - tooltipAttributes.push("%(tooltip)s"); - tooltipString = translate(template.StatusTooltip); - } + if (applier && template.ApplierTooltip) + tooltipAttributes.push(translate(template.ApplierTooltip)); + else if (!applier && template.ReceiverTooltip) + tooltipAttributes.push(translate(template.ReceiverTooltip)); - let attackEffectsString = ""; if (template.Damage || template.Capture) - { - tooltipAttributes.push("%(effects)s"); - attackEffectsString = attackEffectsDetails(template); - } + tooltipAttributes.push(attackEffectsDetails(template)); - let intervalString = ""; if (template.Interval) - { - tooltipAttributes.push("%(rate)s"); - intervalString = sprintf(translate("%(interval)s"), { - "interval": attackRateDetails(+template.Interval) - }); - } + tooltipAttributes.push(attackRateDetails(+template.Interval)); - let durationString = ""; if (template.Duration) - { - tooltipAttributes.push("%(duration)s"); - durationString = sprintf(translate("%(durName)s: %(duration)s"), { - "durName": headerFont(translate("Duration")), - "duration": getSecondsString((template._timeElapsed ? +template.Duration - template._timeElapsed : +template.Duration) / 1000), + tooltipAttributes.push(getStatusEffectDurationTooltip(template)); + + if (applier) + return sprintf(translate("%(statusName)s: %(statusInfo)s %(stackability)s"), { + "statusName": headerFont(translateWithContext("status effect", template.StatusName)), + "statusInfo": tooltipAttributes.join(commaFont(translate(", "))), + "stackability": getStatusEffectStackabilityTooltip(template) + }); + else + return sprintf(translate("%(statusName)s: %(statusInfo)s"), { + "statusName": headerFont(translateWithContext("status effect", template.StatusName)), + "statusInfo": tooltipAttributes.join(commaFont(translate(", "))) }); - } - - return sprintf(translate("%(statusName)s: " + tooltipAttributes.join(translate(commaFont(", ")))), { - "statusName": headerFont(translateWithContext("status effect", template.StatusName)), - "tooltip": tooltipString, - "effects": attackEffectsString, - "rate": intervalString, - "duration": durationString +} + +function getStatusEffectDurationTooltip(template) +{ + if (!template.Duration) + return ""; + + return sprintf(translate("%(durName)s: %(duration)s"), { + "durName": headerFont(translate("Duration")), + "duration": getSecondsString((template._timeElapsed ? + +template.Duration - template._timeElapsed : + +template.Duration) / 1000) + }); +} + +function getStatusEffectStackabilityTooltip(template) +{ + if (!template.Stackability || template.Stackability == "Ignore") + return ""; + + let stackabilityString = ""; + if (template.Stackability == "Extend") + stackabilityString = translateWithContext("status effect stackability", "(extends)"); + else if (template.Stackability == "Replace") + stackabilityString = translateWithContext("status effect stackability", "(replaces)"); + else if (template.Stackability == "Stack") + stackabilityString = translateWithContext("status effect stackability", "(stacks)"); + + return sprintf(translate("%(stackability)s"), { + "stackability": stackabilityString }); } Index: binaries/data/mods/public/gui/session/selection_details.js =================================================================== --- binaries/data/mods/public/gui/session/selection_details.js +++ binaries/data/mods/public/gui/session/selection_details.js @@ -103,12 +103,14 @@ let effect = entState.statusEffects[effectName]; statusIcons[i].hidden = false; statusIcons[i].sprite = "stretched:session/icons/status_effects/" + (effect.Icon || "default") + ".png"; - statusIcons[i].tooltip = getStatusEffectsTooltip(effect); + statusIcons[i].tooltip = getStatusEffectsTooltip(effect, false); let size = statusIcons[i].size; size.top = i * 18; size.bottom = i * 18 + 16; statusIcons[i].size = size; - i++; + + if (++i >= statusIcons.length) + break; } for (; i < statusIcons.length; ++i) statusIcons[i].hidden = true; Index: binaries/data/mods/public/gui/session/selection_panels_middle/single_details_area.xml =================================================================== --- binaries/data/mods/public/gui/session/selection_panels_middle/single_details_area.xml +++ binaries/data/mods/public/gui/session/selection_panels_middle/single_details_area.xml @@ -83,7 +83,7 @@ Rank - + Index: binaries/data/mods/public/l10n/messages.json =================================================================== --- binaries/data/mods/public/l10n/messages.json +++ binaries/data/mods/public/l10n/messages.json @@ -403,7 +403,10 @@ "StatusName": { "customContext": "status effect" }, - "StatusTooltip": { + "ApplierTooltip": { + "customContext": "status effect" + }, + "ReceiverTooltip": { "customContext": "status effect" }, "GenericName": {}, @@ -441,7 +444,10 @@ "StatusName": { "customContext": "status effect" }, - "StatusTooltip": { + "ApplierTooltip": { + "customContext": "status effect" + }, + "ReceiverTooltip": { "customContext": "status effect" }, "GenericName": {}, @@ -486,7 +492,10 @@ "StatusName": { "customContext": "status effect" }, - "StatusTooltip": { + "ApplierTooltip": { + "customContext": "status effect" + }, + "ReceiverTooltip": { "customContext": "status effect" }, "GenericName": {}, @@ -526,7 +535,8 @@ "options": { "keywords": [ "StatusName", - "StatusTooltip" + "ApplierTooltip", + "ReceiverTooltip" ], "context": "status effect" } Index: binaries/data/mods/public/simulation/components/StatusEffectsReceiver.js =================================================================== --- binaries/data/mods/public/simulation/components/StatusEffectsReceiver.js +++ binaries/data/mods/public/simulation/components/StatusEffectsReceiver.js @@ -46,14 +46,30 @@ * * @param {string} statusName - The name of the status effect. * @param {object} data - The various effects and timings. + * @param {object} attackerData - The attacker and attackerOwner. */ StatusEffectsReceiver.prototype.AddStatus = function(statusName, data, attackerData) { if (this.activeStatusEffects[statusName]) { - // TODO: implement different behaviour when receiving the same status multiple times. - // For now, these are ignored. - return; + if (data.Stackability == "Ignore") + return; + if (data.Stackability == "Extend") + { + this.activeStatusEffects[statusName].Duration += data.Duration; + return; + } + if (data.Stackability == "Replace") + this.RemoveStatus(statusName); + else if (data.Stackability == "Stack") + { + let i = 0; + let temp; + do + temp = statusName + "_" + i++; + while (!!this.activeStatusEffects[temp]); + statusName = temp; + } } this.activeStatusEffects[statusName] = {}; Index: binaries/data/mods/public/simulation/components/tests/test_Attack.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_Attack.js +++ binaries/data/mods/public/simulation/components/tests/test_Attack.js @@ -108,7 +108,31 @@ "Capture": 8, "MaxRange": 10, }, - "Slaughter": {} + "Slaughter": {}, + "StatusEffect": { + "ApplyStatus": { + "StatusInternalName": { + "StatusName": "StatusShownName", + "ApplierTooltip": "ApplierTooltip", + "ReceiverTooltip": "ReceiverTooltip", + "Duration": 5000, + "Stackability": "Stacks", + "Modifiers": { + "SE": { + "Paths": { + "_string": "Health/Max" + }, + "Affects": { + "_string": "Unit" + }, + "Add": 10 + } + } + } + }, + "MinRange": 10, + "MaxRange": 80 + } }); let defender = ++entityID; @@ -175,6 +199,30 @@ } }); + TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackEffectsData("StatusEffect"), { + "ApplyStatus": { + "StatusInternalName": { + "StatusName": "StatusShownName", + "ApplierTooltip": "ApplierTooltip", + "ReceiverTooltip": "ReceiverTooltip", + "Duration": 5000, + "Interval": 0, + "Stackability": "Stacks", + "Modifiers": { + "SE": { + "Paths": { + "_string": "Health/Max" + }, + "Affects": { + "_string": "Unit" + }, + "Add": 10 + } + } + } + } + }); + TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetTimers("Ranged"), { "prepare": 300, "repeat": 500 Index: binaries/data/mods/public/simulation/components/tests/test_StatusEffectsReceiver.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_StatusEffectsReceiver.js +++ binaries/data/mods/public/simulation/components/tests/test_StatusEffectsReceiver.js @@ -1,135 +1,345 @@ +Engine.LoadHelperScript("MultiKeyMap.js"); +Engine.LoadHelperScript("Player.js"); +Engine.LoadHelperScript("ValueModification.js"); +Engine.LoadComponentScript("interfaces/Health.js"); +Engine.LoadComponentScript("interfaces/ModifiersManager.js"); Engine.LoadComponentScript("interfaces/StatusEffectsReceiver.js"); Engine.LoadComponentScript("interfaces/Timer.js"); +Engine.LoadComponentScript("Health.js"); +Engine.LoadComponentScript("ModifiersManager.js"); Engine.LoadComponentScript("StatusEffectsReceiver.js"); Engine.LoadComponentScript("Timer.js"); -var target = 42; -var cmpStatusReceiver; -var cmpTimer; -var dealtDamage; -var enemyEntity = 4; -var enemy = 2; +let target = 42; +let cmpStatusReceiver = ConstructComponent(target, "StatusEffectsReceiver"); +let cmpTimer = ConstructComponent(SYSTEM_ENTITY, "Timer"); +let dealtDamage = 0; +let enemyEntity = 4; +let enemy = 2; +let statusName; -function setup() +let Attacking = { + "HandleAttackEffects": (_, attackData) => { + for (let type in attackData.Damage) + dealtDamage += attackData.Damage[type]; + } +}; +Engine.RegisterGlobal("Attacking", Attacking); + +function reset() { - cmpStatusReceiver = ConstructComponent(target, "StatusEffectsReceiver"); - cmpTimer = ConstructComponent(SYSTEM_ENTITY, "Timer"); + for (let status in cmpStatusReceiver.GetActiveStatuses()) + cmpStatusReceiver.RemoveStatus(status); dealtDamage = 0; } -function testInflictEffects() +// Test adding a single effect. +statusName = "Burn"; + +// Damage scheduled: 0, 10, 20 seconds. +cmpStatusReceiver.AddStatus(statusName, { + "Duration": 20000, + "Interval": 10000, + "Damage": { + [statusName]: 1 + } +}, { - setup(); - let statusName = "Burn"; - let Attacking = { - "HandleAttackEffects": (_, attackData) => { dealtDamage += attackData.Damage[statusName]; } - }; - Engine.RegisterGlobal("Attacking", Attacking); - - // damage scheduled: 0, 10, 20 sec - cmpStatusReceiver.AddStatus(statusName, { + "entity": enemyEntity, + "owner": enemy, +}); + +cmpTimer.OnUpdate({ "turnLength": 1 }); +TS_ASSERT_EQUALS(dealtDamage, 1); // 1 sec + +cmpTimer.OnUpdate({ "turnLength": 8 }); +TS_ASSERT_EQUALS(dealtDamage, 1); // 9 sec + +cmpTimer.OnUpdate({ "turnLength": 1 }); +TS_ASSERT_EQUALS(dealtDamage, 2); // 10 sec + +cmpTimer.OnUpdate({ "turnLength": 10 }); +TS_ASSERT_EQUALS(dealtDamage, 3); // 20 sec + +cmpTimer.OnUpdate({ "turnLength": 10 }); +TS_ASSERT_EQUALS(dealtDamage, 3); // 30 sec + + +// Test adding multiple effects. +reset(); + +// Damage scheduled: 0, 1, 2, 10 seconds. +cmpStatusReceiver.ApplyStatus({ + "Burn": { "Duration": 20000, "Interval": 10000, "Damage": { - [statusName]: 1 + "Burn": 10 } }, - { - "entity": enemyEntity, - "owner": enemy, - }); + "Poison": { + "Duration": 3000, + "Interval": 1000, + "Damage": { + "Poison": 1 + } + } +}); - cmpTimer.OnUpdate({ "turnLength": 1 }); - TS_ASSERT_EQUALS(dealtDamage, 1); // 1 sec +cmpTimer.OnUpdate({ "turnLength": 1 }); +TS_ASSERT_EQUALS(dealtDamage, 12); // 1 sec - cmpTimer.OnUpdate({ "turnLength": 8 }); - TS_ASSERT_EQUALS(dealtDamage, 1); // 9 sec +cmpTimer.OnUpdate({ "turnLength": 1 }); +TS_ASSERT_EQUALS(dealtDamage, 13); // 2 sec - cmpTimer.OnUpdate({ "turnLength": 1 }); - TS_ASSERT_EQUALS(dealtDamage, 2); // 10 sec +cmpTimer.OnUpdate({ "turnLength": 1 }); +TS_ASSERT_EQUALS(dealtDamage, 13); // 3 sec - cmpTimer.OnUpdate({ "turnLength": 10 }); - TS_ASSERT_EQUALS(dealtDamage, 3); // 20 sec +cmpTimer.OnUpdate({ "turnLength": 7 }); +TS_ASSERT_EQUALS(dealtDamage, 23); // 10 sec - cmpTimer.OnUpdate({ "turnLength": 10 }); - TS_ASSERT_EQUALS(dealtDamage, 3); // 30 sec -} -testInflictEffects(); +// Test removing a status removes effects. +reset(); +statusName = "Poison"; -function testMultipleEffects() +// Damage scheduled: 0, 10, 20 seconds. +cmpStatusReceiver.AddStatus(statusName, { + "Duration": 20000, + "Interval": 10000, + "Damage": { + [statusName]: 1 + } +}, +{ + "entity": enemyEntity, + "owner": enemy, +}); + +cmpTimer.OnUpdate({ "turnLength": 1 }); +TS_ASSERT_EQUALS(dealtDamage, 1); // 1 sec + +cmpStatusReceiver.RemoveStatus(statusName); + +cmpTimer.OnUpdate({ "turnLength": 10 }); +TS_ASSERT_EQUALS(dealtDamage, 1); // 11 sec + + +// Test that a status effect with modifications modifies. +reset(); + +AddMock(target, IID_Identity, { + "GetClassesList": () => ["AffectedClass"] +}); +let cmpModifiersManager = ConstructComponent(SYSTEM_ENTITY, "ModifiersManager"); + +let maxHealth = 100; +AddMock(target, IID_Health, { + "GetMaxHitpoints": () => ApplyValueModificationsToEntity("Health/Max", maxHealth, target) +}); + +statusName = "Haste"; +let factor = 0.5; +cmpStatusReceiver.AddStatus(statusName, { + "Duration": 5000, + "Modifiers": { + [statusName]: { + "Paths": { + "_string": "Health/Max" + }, + "Affects": { + "_string": "AffectedClass" + }, + "Multiply": factor + } + } +}, +{ + "entity": enemyEntity, + "owner": enemy, +}); + +let cmpHealth = Engine.QueryInterface(target, IID_Health); +// Test that the modification is applied. +TS_ASSERT_EQUALS(cmpHealth.GetMaxHitpoints(), maxHealth * factor); +cmpTimer.OnUpdate({ "turnLength": 1 }); +TS_ASSERT_EQUALS(cmpHealth.GetMaxHitpoints(), maxHealth * factor); + +// Test that the modification is removed after the appropriate time. +cmpTimer.OnUpdate({ "turnLength": 4 }); +TS_ASSERT_EQUALS(cmpHealth.GetMaxHitpoints(), maxHealth); + + +// Test addition. +let addition = 50; +cmpStatusReceiver.AddStatus(statusName, { + "Duration": 5000, + "Modifiers": { + [statusName]: { + "Paths": { + "_string": "Health/Max" + }, + "Affects": { + "_string": "AffectedClass" + }, + "Add": addition + } + } +}, +{ + "entity": enemyEntity, + "owner": enemy, +}); + +// Test that the addition modification is applied. +TS_ASSERT_EQUALS(cmpHealth.GetMaxHitpoints(), maxHealth + addition); +cmpTimer.OnUpdate({ "turnLength": 1 }); +TS_ASSERT_EQUALS(cmpHealth.GetMaxHitpoints(), maxHealth + addition); + +// Test that the modification is removed after the appropriate time. +cmpTimer.OnUpdate({ "turnLength": 4 }); +TS_ASSERT_EQUALS(cmpHealth.GetMaxHitpoints(), maxHealth); + + +// Test replacement. +let newValue = 50; +cmpStatusReceiver.AddStatus(statusName, { + "Duration": 5000, + "Modifiers": { + [statusName]: { + "Paths": { + "_string": "Health/Max" + }, + "Affects": { + "_string": "AffectedClass" + }, + "Replace": newValue + } + } +}, +{ + "entity": enemyEntity, + "owner": enemy, +}); + +// Test that the replacement modification is applied. +TS_ASSERT_EQUALS(cmpHealth.GetMaxHitpoints(), newValue); +cmpTimer.OnUpdate({ "turnLength": 1 }); +TS_ASSERT_EQUALS(cmpHealth.GetMaxHitpoints(), newValue); + +// Test that the modification is removed after the appropriate time. +cmpTimer.OnUpdate({ "turnLength": 4 }); +TS_ASSERT_EQUALS(cmpHealth.GetMaxHitpoints(), maxHealth); + + +function applyStatus(stackability) { - setup(); - let Attacking = { - "HandleAttackEffects": (_, attackData) => { - if (attackData.Damage.Burn) dealtDamage += attackData.Damage.Burn; - if (attackData.Damage.Poison) dealtDamage += attackData.Damage.Poison; - }, - }; - Engine.RegisterGlobal("Attacking", Attacking); - - // damage scheduled: 0, 1, 2, 10 sec cmpStatusReceiver.ApplyStatus({ - "Burn": { - "Duration": 20000, - "Interval": 10000, - "Damage": { - "Burn": 10 - } - }, - "Poison": { + "randomName": { "Duration": 3000, "Interval": 1000, "Damage": { - "Poison": 1 - } + "randomName": 1 + }, + "Stackability": stackability } }); +} - cmpTimer.OnUpdate({ "turnLength": 1 }); - TS_ASSERT_EQUALS(dealtDamage, 12); // 1 sec - cmpTimer.OnUpdate({ "turnLength": 1 }); - TS_ASSERT_EQUALS(dealtDamage, 13); // 2 sec +// Test different stackabilities. +// First ignoring, i.e. next time the same status is added it is just ignored. +reset(); +applyStatus("Ignore"); - cmpTimer.OnUpdate({ "turnLength": 1 }); - TS_ASSERT_EQUALS(dealtDamage, 13); // 3 sec +// 1 Second: 1 update and lateness. +cmpTimer.OnUpdate({ "turnLength": 1 }); +TS_ASSERT_EQUALS(dealtDamage, 2); - cmpTimer.OnUpdate({ "turnLength": 7 }); - TS_ASSERT_EQUALS(dealtDamage, 23); // 10 sec -} +applyStatus("Ignore"); -testMultipleEffects(); - -function testRemoveStatus() -{ - setup(); - let statusName = "Poison"; - let Attacking = { - "HandleAttackEffects": (_, attackData) => { dealtDamage += attackData.Damage[statusName]; } - }; - Engine.RegisterGlobal("Attacking", Attacking); - - // damage scheduled: 0, 10, 20 sec - cmpStatusReceiver.AddStatus(statusName, { - "Duration": 20000, - "Interval": 10000, - "Damage": { - [statusName]: 1 - } - }, - { - "entity": enemyEntity, - "owner": enemy, - }); +// 2 Seconds. +cmpTimer.OnUpdate({ "turnLength": 1 }); +TS_ASSERT_EQUALS(dealtDamage, 3); - cmpTimer.OnUpdate({ "turnLength": 1 }); - TS_ASSERT_EQUALS(dealtDamage, 1); // 1 sec +// 3 Seconds: finished in previous turn. +cmpTimer.OnUpdate({ "turnLength": 1 }); +TS_ASSERT_EQUALS(dealtDamage, 3); - cmpStatusReceiver.RemoveStatus(statusName); - cmpTimer.OnUpdate({ "turnLength": 10 }); - TS_ASSERT_EQUALS(dealtDamage, 1); // 11 sec -} +// Extending, i.e. next time the same status is applied the times are added. +reset(); +applyStatus("Extend"); + +// 1 Second: 1 update and lateness. +cmpTimer.OnUpdate({ "turnLength": 1 }); +TS_ASSERT_EQUALS(dealtDamage, 2); + +// Add 3 seconds. +applyStatus("Extend"); + +// 2 Seconds. +cmpTimer.OnUpdate({ "turnLength": 1 }); +TS_ASSERT_EQUALS(dealtDamage, 3); + +// 3 Seconds: extended in previous turn. +cmpTimer.OnUpdate({ "turnLength": 1 }); +TS_ASSERT_EQUALS(dealtDamage, 4); + +// 4 Seconds. +cmpTimer.OnUpdate({ "turnLength": 1 }); +TS_ASSERT_EQUALS(dealtDamage, 5); + +// 5 Seconds. +cmpTimer.OnUpdate({ "turnLength": 1 }); +TS_ASSERT_EQUALS(dealtDamage, 6); + +// 6 Seconds: finished in previous turn (3 + 3). +cmpTimer.OnUpdate({ "turnLength": 1 }); +TS_ASSERT_EQUALS(dealtDamage, 6); + + +// Replacing, i.e. the next applied status replaces the former. +reset(); +applyStatus("Replace"); + +// 1 Second: 1 update and lateness. +cmpTimer.OnUpdate({ "turnLength": 1 }); +TS_ASSERT_EQUALS(dealtDamage, 2); + +applyStatus("Replace"); + +// 2 Seconds: 1 update and lateness of the new status. +cmpTimer.OnUpdate({ "turnLength": 1 }); +TS_ASSERT_EQUALS(dealtDamage, 4); + +// 3 Seconds. +cmpTimer.OnUpdate({ "turnLength": 1 }); +TS_ASSERT_EQUALS(dealtDamage, 5); + +// 4 Seconds: finished in previous turn. +cmpTimer.OnUpdate({ "turnLength": 1 }); +TS_ASSERT_EQUALS(dealtDamage, 5); + + +// Stacking, every new status just applies besides the rest. +reset(); +applyStatus("Stack"); + +// 1 Second: 1 update and lateness. +cmpTimer.OnUpdate({ "turnLength": 1 }); +TS_ASSERT_EQUALS(dealtDamage, 2); + +applyStatus("Stack"); + +// 2 Seconds: 1 damage from the previous status + 2 from the new (1 turn + lateness). +cmpTimer.OnUpdate({ "turnLength": 1 }); +TS_ASSERT_EQUALS(dealtDamage, 5); + +// 3 Seconds: first one finished in the previous turn, +1 from the new. +cmpTimer.OnUpdate({ "turnLength": 1 }); +TS_ASSERT_EQUALS(dealtDamage, 6); -testRemoveStatus(); +// 4 Seconds: new status finished in previous turn. +cmpTimer.OnUpdate({ "turnLength": 1 }); +TS_ASSERT_EQUALS(dealtDamage, 6); Index: binaries/data/mods/public/simulation/data/technologies/advanced_unit_bonus.json =================================================================== --- binaries/data/mods/public/simulation/data/technologies/advanced_unit_bonus.json +++ binaries/data/mods/public/simulation/data/technologies/advanced_unit_bonus.json @@ -22,7 +22,9 @@ { "value": "Loot/wood", "multiply": 1.2 }, { "value": "Loot/stone", "multiply": 1.2 }, { "value": "Loot/metal", "multiply": 1.2 }, - { "value": "Loot/xp", "multiply": 1.2 } + { "value": "Loot/xp", "multiply": 1.2 }, + { "value": "Attack/Ranged/ApplyStatus/Bogus/Duration", "multiply": 2 }, + { "value": "Attack/Ranged/ApplyStatus/AnotherBogus/Modifiers/Resiliance/Add", "multiply": 2 } ], "affects": ["Advanced Unit", "Elite Unit"] } Index: binaries/data/mods/public/simulation/helpers/Attacking.js =================================================================== --- binaries/data/mods/public/simulation/helpers/Attacking.js +++ binaries/data/mods/public/simulation/helpers/Attacking.js @@ -32,7 +32,10 @@ "" + "" + "" + - "" + + "" + + "" + + "" + + "" + "" + "" + "" + @@ -50,6 +53,14 @@ "" + ModificationsSchema + "" + + "" + + "" + + "Ignore" + + "Extend" + + "Replace" + + "Stack" + + "" + + "" + "" + "" + "" + @@ -108,7 +119,7 @@ ret.Capture = ApplyValueModificationsToEntity(valueModifRoot + "/Capture", +(template.Capture || 0), entity); if (template.ApplyStatus) - ret.ApplyStatus = template.ApplyStatus; + ret.ApplyStatus = this.GetStatusEffectsData(valueModifRoot, template.ApplyStatus, entity); if (template.Bonuses) ret.Bonuses = template.Bonuses; @@ -116,6 +127,47 @@ return ret; }; +Attacking.prototype.GetStatusEffectsData = function(valueModifRoot, template, entity) +{ + let result = {}; + for (let effect in template) + { + let statusTemplate = template[effect]; + result[effect] = { + "StatusName": statusTemplate.StatusName, + "ApplierTooltip": statusTemplate.ApplierTooltip, + "ReceiverTooltip": statusTemplate.ReceiverTooltip, + "Duration": ApplyValueModificationsToEntity(valueModifRoot + "/ApplyStatus/" + effect + "/Duration", +(statusTemplate.Duration || 0), entity), + "Interval": ApplyValueModificationsToEntity(valueModifRoot + "/ApplyStatus/" + effect + "/Interval", +(statusTemplate.Interval || 0), entity), + "Stackability": statusTemplate.Stackability + }; + Object.assign(result[effect], this.GetAttackEffectsData(valueModifRoot + "/ApplyStatus" + effect, statusTemplate, entity)); + if (statusTemplate.Modifiers) + result[effect].Modifiers = this.GetStatusEffectsModifications(valueModifRoot, statusTemplate.Modifiers, entity, effect); + } + return result; +}; + +Attacking.prototype.GetStatusEffectsModifications = function(valueModifRoot, template, entity, effect) +{ + let modifiers = {}; + for (let modifier in template) + { + let modifierTemplate = template[modifier]; + modifiers[modifier] = { + "Paths": modifierTemplate.Paths, + "Affects": modifierTemplate.Affects + }; + if (modifierTemplate.Add !== undefined) + modifiers[modifier].Add = ApplyValueModificationsToEntity(valueModifRoot + "/ApplyStatus/" + effect + "/Modifiers/" + modifier + "/Add", +modifierTemplate.Add, entity); + if (modifierTemplate.Multiply !== undefined) + modifiers[modifier].Multiply = ApplyValueModificationsToEntity(valueModifRoot + "/ApplyStatus/" + effect + "/Modifiers/" + modifier + "/Multiply", +modifierTemplate.Multiply, entity); + if (modifierTemplate.Replace !== undefined) + modifiers[modifier].Replace = modifierTemplate.Replace; + } + return modifiers; +}; + Attacking.prototype.GetTotalAttackEffects = function(effectData, effectType, cmpResistance) { let total = 0;