1
0
forked from mirrors/0ad

Handle stacking status effects, modifiers, some related fixes.

Continuation of work on status effects, following 82a5ab6d19.

Allow techs to modify properties.
Enable different behaviour when status effects would stack, such as
extending the duration or stacking the effects.
Fix GUI issue with more than 5 status effects.
Improve tooltips.
Let status effects show a different tooltip for the attacker and the
receiver.

Patch by: Freagarach
Reviewed By: wraitii, Angen (on an older differential).
Differential Revision: https://code.wildfiregames.com/D2296
This was SVN commit r23757.
This commit is contained in:
wraitii
2020-06-09 11:47:16 +00:00
parent 5176f848b8
commit ea67b79667
8 changed files with 498 additions and 142 deletions
@@ -333,7 +333,7 @@ function getAttackTooltip(template)
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,66 @@ function getSplashDamageTooltip(template)
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));
return sprintf(translate("%(statusName)s: " + tooltipAttributes.join(translate(commaFont(", ")))), {
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)
});
return sprintf(translate("%(statusName)s: %(statusInfo)s"), {
"statusName": headerFont(translateWithContext("status effect", template.StatusName)),
"tooltip": tooltipString,
"effects": attackEffectsString,
"rate": intervalString,
"duration": durationString
"statusInfo": tooltipAttributes.join(commaFont(translate(", ")))
});
}
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
});
}
@@ -103,12 +103,14 @@ function displaySingle(entState)
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;
@@ -83,7 +83,7 @@
<translatableAttribute id="tooltip">Rank</translatableAttribute>
</object>
<!-- Status Effecst icons -->
<!-- Status Effects icons -->
<object name="statusEffectsIcons" size="100%-20 4 100%-4 100%">
<repeat count="5">
<object type="image" size="0 0 16 16" z="200" tooltip_style="sessionToolTip"/>
+14 -4
View File
@@ -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"
}
@@ -46,14 +46,30 @@ StatusEffectsReceiver.prototype.ApplyStatus = function(effectData, attacker, att
*
* @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] = {};
@@ -108,7 +108,31 @@ function attackComponentTest(defenderClass, isEnemy, test_function)
"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 @@ attackComponentTest(undefined, true, (attacker, cmpAttack, defender) => {
}
});
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
@@ -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()
{
setup();
let statusName = "Burn";
let Attacking = {
"HandleAttackEffects": (_, attackData) => { dealtDamage += attackData.Damage[statusName]; }
};
Engine.RegisterGlobal("Attacking", Attacking);
// Test adding a single effect.
statusName = "Burn";
// damage scheduled: 0, 10, 20 sec
cmpStatusReceiver.AddStatus(statusName, {
// 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
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
}
},
{
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);
"entity": enemyEntity,
"owner": enemy,
});
// damage scheduled: 0, 1, 2, 10 sec
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)
{
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
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(dealtDamage, 13); // 3 sec
cmpTimer.OnUpdate({ "turnLength": 7 });
TS_ASSERT_EQUALS(dealtDamage, 23); // 10 sec
}
testMultipleEffects();
function testRemoveStatus()
{
setup();
let statusName = "Poison";
let Attacking = {
"HandleAttackEffects": (_, attackData) => { dealtDamage += attackData.Damage[statusName]; }
};
Engine.RegisterGlobal("Attacking", Attacking);
// Test different stackabilities.
// First ignoring, i.e. next time the same status is added it is just ignored.
reset();
applyStatus("Ignore");
// damage scheduled: 0, 10, 20 sec
cmpStatusReceiver.AddStatus(statusName, {
"Duration": 20000,
"Interval": 10000,
"Damage": {
[statusName]: 1
}
},
{
"entity": enemyEntity,
"owner": enemy,
});
// 1 Second: 1 update and lateness.
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(dealtDamage, 2);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(dealtDamage, 1); // 1 sec
applyStatus("Ignore");
cmpStatusReceiver.RemoveStatus(statusName);
// 2 Seconds.
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(dealtDamage, 3);
cmpTimer.OnUpdate({ "turnLength": 10 });
TS_ASSERT_EQUALS(dealtDamage, 1); // 11 sec
}
// 3 Seconds: finished in previous turn.
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(dealtDamage, 3);
testRemoveStatus();
// 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);
// 4 Seconds: new status finished in previous turn.
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(dealtDamage, 6);
@@ -32,7 +32,10 @@ const StatusEffectsSchema =
"<element name='Icon' a:help='Icon for the status effect.'><text/></element>" +
"</optional>" +
"<optional>" +
"<element name='StatusTooltip'><text/></element>" +
"<element name='ApplierTooltip' a:help='The tooltip shown on the entity giving the effect, e.g. the attacker.'><text/></element>" +
"</optional>" +
"<optional>" +
"<element name='ReceiverTooltip' a:help='The tooltip shown on the affected entity while the effect occurs.'><text/></element>" +
"</optional>" +
"<optional>" +
"<element name='Duration' a:help='The duration of the status while the effect occurs.'><ref name='nonNegativeDecimal'/></element>" +
@@ -50,6 +53,14 @@ const StatusEffectsSchema =
"<optional>" +
ModificationsSchema +
"</optional>" +
"<element name='Stackability' a:help='Defines how this status effect stacks, i.e. how subsequent status effects of the same kind are handled. Choices are: “Ignore”, which means a new one is ignored, “Extend”, which means the duration of a new one is added to the already active status effect, “Replace”, which means the currently active status effect is removed and the new one is put in place and “Stack”, which means that the status effect can be added multiple times.'>" +
"<choice>" +
"<value>Ignore</value>" +
"<value>Extend</value>" +
"<value>Replace</value>" +
"<value>Stack</value>" +
"</choice>" +
"</element>" +
"</interleave>" +
"</element>" +
"</oneOrMore>" +
@@ -108,7 +119,7 @@ Attacking.prototype.GetAttackEffectsData = function(valueModifRoot, template, en
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 @@ Attacking.prototype.GetAttackEffectsData = function(valueModifRoot, template, en
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;