Allow resistances to StatusEffects.

Differential Revision: D2908
Reviewed by: @wraitii
Comments by: @bb.
This was SVN commit r24162.
This commit is contained in:
Freagarach 2020-11-11 20:07:30 +00:00
parent b5df81af76
commit 876b035336
10 changed files with 306 additions and 100 deletions

View file

@ -10,7 +10,7 @@ class StatusEffectsMetadata
{
this.statusEffectData = {};
let files = Engine.ListDirectoryFiles("simulation/data/template_helpers/status_effects", "*.json", false);
let files = Engine.ListDirectoryFiles("simulation/data/status_effects", "*.json", false);
for (let filename of files)
{
let data = Engine.ReadJSONFile(filename);
@ -23,22 +23,46 @@ class StatusEffectsMetadata
continue;
}
this.statusEffectData[data.code] = data;
this.statusEffectData[data.code] = {
"applierTooltip": data.applierTooltip || "",
"code": data.code,
"icon": data.icon || "default",
"statusName": data.statusName || "data.code",
"receiverTooltip": data.receiverTooltip || ""
};
}
}
/**
* @returns the default data for @param code status effects, augmented with the given template data,
* or simply @param templateData if the code is not found in JSON files.
* @param {string} code - The code of the Status Effect.
* @return {Object} - The JSON data corresponding to the code.
*/
augment(code, templateData)
getData(code)
{
if (!templateData && this.statusEffectData[code])
if (this.statusEffectData[code])
return this.statusEffectData[code];
if (this.statusEffectData[code])
return Object.assign({}, this.statusEffectData[code], templateData);
warn("No status effects data found for: " + code + ".");
return {};
}
return templateData;
getApplierTooltip(code)
{
return this.getData(code).applierTooltip;
}
getIcon(code)
{
return this.getData(code).icon;
}
getName(code)
{
return this.getData(code).statusName;
}
getReceiverTooltip(code)
{
return this.getData(code).receiverTooltip;
}
}

View file

@ -174,8 +174,15 @@ function GetTemplateDataHelper(template, player, auraTemplates, modifiers = {})
}
if (template.Resistance.Entity.Capture)
ret.resistance.Capture = getEntityValue("Resistance/Entity/Capture");
// ToDo: Resistance against StatusEffects.
if (template.Resistance.Entity.ApplyStatus)
{
ret.resistance.ApplyStatus = {};
for (let statusEffect in template.Resistance.Entity.ApplyStatus)
ret.resistance.ApplyStatus[statusEffect] = {
"blockChance": getEntityValue("Resistance/Entity/ApplyStatus/" + statusEffect + "/BlockChance"),
"duration": getEntityValue("Resistance/Entity/ApplyStatus/" + statusEffect + "/Duration")
};
}
}
}

View file

@ -1,13 +1,13 @@
let statusEffects = {
"test_A": {
"code": "test_a",
"StatusName": "A",
"StatusTooltip": "TTA"
"statusName": "A",
"applierTooltip": "TTA"
},
"test_B": {
"code": "test_b",
"StatusName": "B",
"StatusTooltip": "TTB"
"statusName": "B",
"applierTooltip": "TTB"
}
};
@ -16,16 +16,21 @@ Engine.ReadJSONFile = (file) => statusEffects[file];
let sem = new StatusEffectsMetadata();
// Template data takes precedence over generic data.
TS_ASSERT_UNEVAL_EQUALS(sem.augment("test_a"), {
"code": "test_a", "StatusName": "A", "StatusTooltip": "TTA"
TS_ASSERT_UNEVAL_EQUALS(sem.getData("test_a"), {
"applierTooltip": "TTA",
"code": "test_a",
"icon": "default",
"statusName": "A",
"receiverTooltip": ""
});
TS_ASSERT_UNEVAL_EQUALS(sem.augment("test_b"), {
"code": "test_b", "StatusName": "B", "StatusTooltip": "TTB"
});
TS_ASSERT_UNEVAL_EQUALS(sem.augment("test_a", { "StatusName": "test" }), {
"code": "test_a", "StatusName": "test", "StatusTooltip": "TTA"
});
TS_ASSERT_UNEVAL_EQUALS(sem.augment("test_c", { "StatusName": "test" }), {
"StatusName": "test"
TS_ASSERT_UNEVAL_EQUALS(sem.getData("test_b"), {
"applierTooltip": "TTB",
"code": "test_b",
"icon": "default",
"statusName": "B",
"receiverTooltip": ""
});
TS_ASSERT_UNEVAL_EQUALS(sem.getApplierTooltip("test_a"), "TTA");
TS_ASSERT_UNEVAL_EQUALS(sem.getIcon("test_b"), "default");
TS_ASSERT_UNEVAL_EQUALS(sem.getName("test_a"), "A");
TS_ASSERT_UNEVAL_EQUALS(sem.getReceiverTooltip("test_b"), "");

View file

@ -181,7 +181,8 @@ function getResistanceTooltip(template)
if (template.resistance.Capture)
details.push(getCaptureResistanceTooltip(template.resistance.Capture));
// TODO: Status effects resistance.
if (template.resistance.ApplyStatus)
details.push(getStatusEffectsResistanceTooltip(template.resistance.ApplyStatus));
return sprintf(translate("%(label)s\n%(details)s"), {
"label": headerFont(translate("Resistance:")),
@ -230,6 +231,48 @@ function getCaptureResistanceTooltip(resistanceTypeTemplate)
});
}
function getStatusEffectsResistanceTooltip(resistanceTypeTemplate)
{
if (!resistanceTypeTemplate)
return "";
return sprintf(translate("%(label)s %(details)s"), {
"label": headerFont(translate("Status Effects:")),
"details":
Object.keys(resistanceTypeTemplate).map(
statusEffect => {
if (resistanceTypeTemplate[statusEffect].blockChance == 1)
return sprintf(translate("Blocks %(name)s"), {
"name": unitFont(translateWithContext("status effect", g_StatusEffectsMetadata.getName(statusEffect)))
});
if (resistanceTypeTemplate[statusEffect].blockChance == 0)
return sprintf(translate("%(name)s %(details)s"), {
"name": unitFont(translateWithContext("status effect", g_StatusEffectsMetadata.getName(statusEffect))),
"details": sprintf(translate("Duration reduction: %(durationReduction)s%%"), {
"durationReduction": (100 - resistanceTypeTemplate[statusEffect].duration * 100)
})
});
if (resistanceTypeTemplate[statusEffect].duration == 1)
return sprintf(translate("%(name)s %(details)s"), {
"name": unitFont(translateWithContext("status effect", g_StatusEffectsMetadata.getName(statusEffect))),
"details": sprintf(translate("Blocks: %(blockPercentage)s%%"), {
"blockPercentage": resistanceTypeTemplate[statusEffect].blockChance * 100
})
});
return sprintf(translate("%(name)s %(details)s"), {
"name": unitFont(translateWithContext("status effect", g_StatusEffectsMetadata.getName(statusEffect))),
"details": sprintf(translate("Blocks: %(blockPercentage)s%%, Duration reduction: %(durationReduction)s%%"), {
"blockPercentage": resistanceTypeTemplate[statusEffect].blockChance * 100,
"durationReduction": (100 - resistanceTypeTemplate[statusEffect].duration * 100)
})
});
}
).join(commaFont(translate(", ")))
});
}
function attackRateDetails(interval, projectiles)
{
if (!interval)
@ -346,10 +389,9 @@ function applyStatusDetails(applyStatusTemplate)
return "";
return sprintf(translate("gives %(name)s"), {
"name": Object.keys(applyStatusTemplate).map(x => {
let template = g_StatusEffectsMetadata.augment(x, applyStatusTemplate[x]);
return unitFont(translateWithContext("status effect", template.StatusName));
}).join(commaFont(translate(", "))),
"name": Object.keys(applyStatusTemplate).map(x =>
unitFont(translateWithContext("status effect", g_StatusEffectsMetadata.getName(x)))
).join(commaFont(translate(", "))),
});
}
@ -395,10 +437,7 @@ function getAttackTooltip(template)
let statusEffectsDetails = [];
if (attackTypeTemplate.ApplyStatus)
for (let status in attackTypeTemplate.ApplyStatus)
{
let status_template = g_StatusEffectsMetadata.augment(status, attackTypeTemplate.ApplyStatus[status]);
statusEffectsDetails.push("\n" + g_Indent + g_Indent + getStatusEffectsTooltip(status_template, true));
}
statusEffectsDetails.push("\n" + g_Indent + g_Indent + getStatusEffectsTooltip(status, attackTypeTemplate.ApplyStatus[status], true));
statusEffectsDetails = statusEffectsDetails.join("");
tooltips.push(sprintf(translate("%(attackLabel)s: %(effects)s, %(range)s, %(rate)s%(statusEffects)s%(splash)s"), {
@ -420,14 +459,10 @@ function getAttackTooltip(template)
/**
* @param applier - if true, return the tooltip for the Applier. If false, Receiver is returned.
*/
function getStatusEffectsTooltip(template, applier)
function getStatusEffectsTooltip(statusCode, template, applier)
{
let tooltipAttributes = [];
if (applier && template.ApplierTooltip)
tooltipAttributes.push(translate(template.ApplierTooltip));
else if (!applier && template.ReceiverTooltip)
tooltipAttributes.push(translate(template.ReceiverTooltip));
let statusData = g_StatusEffectsMetadata.getData(statusCode);
if (template.Damage || template.Capture)
tooltipAttributes.push(attackEffectsDetails(template));
@ -437,14 +472,19 @@ function getStatusEffectsTooltip(template, applier)
if (template.Duration)
tooltipAttributes.push(getStatusEffectDurationTooltip(template));
if (applier && statusData.applierTooltip)
tooltipAttributes.push(translate(statusData.applierTooltip));
else if (!applier && statusData.receiverTooltip)
tooltipAttributes.push(translate(statusData.receiverTooltip));
if (applier)
return sprintf(translate("%(statusName)s: %(statusInfo)s %(stackability)s"), {
"statusName": headerFont(translateWithContext("status effect", template.StatusName)),
"statusName": headerFont(translateWithContext("status effect", statusData.statusName)),
"statusInfo": tooltipAttributes.join(commaFont(translate(", "))),
"stackability": getStatusEffectStackabilityTooltip(template)
});
return sprintf(translate("%(statusName)s: %(statusInfo)s"), {
"statusName": headerFont(translateWithContext("status effect", template.StatusName)),
"statusName": headerFont(translateWithContext("status effect", statusData.statusName)),
"statusInfo": tooltipAttributes.join(commaFont(translate(", ")))
});
}

View file

@ -98,12 +98,12 @@ function displaySingle(entState)
statusEffectsSection.hidden = false;
let statusIcons = statusEffectsSection.children;
let i = 0;
for (let effectName in entState.statusEffects)
for (let effectCode in entState.statusEffects)
{
let effect = entState.statusEffects[effectName];
let effect = entState.statusEffects[effectCode];
statusIcons[i].hidden = false;
statusIcons[i].sprite = "stretched:session/icons/status_effects/" + (effect.Icon || "default") + ".png";
statusIcons[i].tooltip = getStatusEffectsTooltip(effect, false);
statusIcons[i].sprite = "stretched:session/icons/status_effects/" + g_StatusEffectsMetadata.getIcon(effect.baseCode) + ".png";
statusIcons[i].tooltip = getStatusEffectsTooltip(effect.baseCode, effect, false);
let size = statusIcons[i].size;
size.top = i * 18;
size.bottom = i * 18 + 16;

View file

@ -489,15 +489,6 @@
},
"options": {
"keywords": {
"StatusName": {
"customContext": "status effect"
},
"ApplierTooltip": {
"customContext": "status effect"
},
"ReceiverTooltip": {
"customContext": "status effect"
},
"GenericName": {},
"SpecificName": {},
"History": {},
@ -530,13 +521,13 @@
{
"extractor": "json",
"filemasks": [
"simulation/data/template_helpers/status_effects/*.json"
"simulation/data/status_effects/*.json"
],
"options": {
"keywords": [
"StatusName",
"ApplierTooltip",
"ReceiverTooltip"
"statusName",
"applierTooltip",
"receiverTooltip"
],
"context": "status effect"
}

View file

@ -2,7 +2,6 @@ function Resistance() {}
/**
* Builds a RelaxRNG schema of possible attack effects.
* ToDo: Resistance to StatusEffects.
*
* @return {string} - RelaxNG schema string.
*/
@ -22,6 +21,23 @@ Resistance.prototype.BuildResistanceSchema = function()
"<element name='Capture' a:help='Resistance against Capture attacks.'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"<element name='ApplyStatus' a:help='Resistance against StatusEffects.'>" +
"<oneOrMore>" +
"<element a:help='Resistance against any number of status effects.'>" +
"<anyName/>" +
"<interleave>" +
"<optional>" +
"<element name='Duration' a:help='The reduction in duration of the status. The normal duration time is multiplied by this factor.'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"</optional>" +
"<optional>" +
"<element name='BlockChance' a:help='The chance of blocking the status. In the interval [0,1].'><ref name='nonNegativeDecimal'/></element>" +
"</optional>" +
"</interleave>" +
"</element>" +
"</oneOrMore>" +
"</element>" +
"</choice>" +
"</oneOrMore>";
};
@ -122,6 +138,16 @@ Resistance.prototype.GetResistanceOfForm = function(entityForm)
if (template.Capture)
ret.Capture = ApplyValueModificationsToEntity("Resistance/" + entityForm + "/Capture", +this.template[entityForm].Capture, this.entity);
if (template.ApplyStatus)
{
ret.ApplyStatus = {};
for (let effect in template.ApplyStatus)
ret.ApplyStatus[effect] = {
"duration": ApplyValueModificationsToEntity("Resistance/" + entityForm + "/ApplyStatus/" + effect + "/Duration", +(template.ApplyStatus[effect].Duration || 1), this.entity),
"blockChance": ApplyValueModificationsToEntity("Resistance/" + entityForm + "/ApplyStatus/" + effect + "/BlockChance", +(template.ApplyStatus[effect].BlockChance || 0), this.entity)
};
}
return ret;
};

View file

@ -28,7 +28,7 @@ StatusEffectsReceiver.prototype.GetActiveStatuses = function()
* @param {number} attackerOwner - The player ID of the attacker.
* @param {number} bonusMultiplier - A value to multiply the damage with (not implemented yet for SE).
*
* @return {Object} - The names of the status effects which were processed.
* @return {Object} - The codes of the status effects which were processed.
*/
StatusEffectsReceiver.prototype.ApplyStatus = function(effectData, attacker, attackerOwner)
{
@ -36,7 +36,7 @@ StatusEffectsReceiver.prototype.ApplyStatus = function(effectData, attacker, att
for (let effect in effectData)
this.AddStatus(effect, effectData[effect], attackerData);
// TODO: implement loot / resistance.
// TODO: implement loot?
return { "inflictedStatuses": Object.keys(effectData) };
};
@ -44,43 +44,46 @@ StatusEffectsReceiver.prototype.ApplyStatus = function(effectData, attacker, att
/**
* Adds a status effect to the entity.
*
* @param {string} statusName - The name of the status effect.
* @param {string} statusCode - The code 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)
StatusEffectsReceiver.prototype.AddStatus = function(baseCode, data, attackerData)
{
if (this.activeStatusEffects[statusName])
let statusCode = baseCode;
if (this.activeStatusEffects[statusCode])
{
if (data.Stackability == "Ignore")
return;
if (data.Stackability == "Extend")
{
this.activeStatusEffects[statusName].Duration += data.Duration;
this.activeStatusEffects[statusCode].Duration += data.Duration;
return;
}
if (data.Stackability == "Replace")
this.RemoveStatus(statusName);
this.RemoveStatus(statusCode);
else if (data.Stackability == "Stack")
{
let i = 0;
let temp;
do
temp = statusName + "_" + i++;
temp = statusCode + "_" + i++;
while (!!this.activeStatusEffects[temp]);
statusName = temp;
statusCode = temp;
}
}
this.activeStatusEffects[statusName] = {};
let status = this.activeStatusEffects[statusName];
this.activeStatusEffects[statusCode] = {
"baseCode": baseCode
};
let status = this.activeStatusEffects[statusCode];
Object.assign(status, data);
if (status.Modifiers)
{
let modifications = DeriveModificationsFromXMLTemplate(status.Modifiers);
let cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager);
cmpModifiersManager.AddModifiers(statusName, modifications, this.entity);
cmpModifiersManager.AddModifiers(statusCode, modifications, this.entity);
}
// With neither an interval nor a duration, there is no point in starting a timer.
@ -101,24 +104,24 @@ StatusEffectsReceiver.prototype.AddStatus = function(statusName, data, attackerD
status.source = attackerData;
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
status._timer = cmpTimer.SetInterval(this.entity, IID_StatusEffectsReceiver, "ExecuteEffect", 0, +(status.Interval || status._interval), statusName);
status._timer = cmpTimer.SetInterval(this.entity, IID_StatusEffectsReceiver, "ExecuteEffect", 0, +(status.Interval || status._interval), statusCode);
};
/**
* Removes a status effect from the entity.
*
* @param {string} statusName - The status effect to be removed.
* @param {string} statusCode - The status effect to be removed.
*/
StatusEffectsReceiver.prototype.RemoveStatus = function(statusName)
StatusEffectsReceiver.prototype.RemoveStatus = function(statusCode)
{
let statusEffect = this.activeStatusEffects[statusName];
let statusEffect = this.activeStatusEffects[statusCode];
if (!statusEffect)
return;
if (statusEffect.Modifiers)
{
let cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager);
cmpModifiersManager.RemoveAllModifiers(statusName, this.entity);
cmpModifiersManager.RemoveAllModifiers(statusCode, this.entity);
}
if (statusEffect._timer)
@ -126,23 +129,23 @@ StatusEffectsReceiver.prototype.RemoveStatus = function(statusName)
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
cmpTimer.CancelTimer(statusEffect._timer);
}
delete this.activeStatusEffects[statusName];
delete this.activeStatusEffects[statusCode];
};
/**
* Called by the timers. Executes a status effect.
*
* @param {string} statusName - The name of the status effect to be executed.
* @param {string} statusCode - The status effect to be executed.
* @param {number} lateness - The delay between the calling of the function and the actual execution (turn time?).
*/
StatusEffectsReceiver.prototype.ExecuteEffect = function(statusName, lateness)
StatusEffectsReceiver.prototype.ExecuteEffect = function(statusCode, lateness)
{
let status = this.activeStatusEffects[statusName];
let status = this.activeStatusEffects[statusCode];
if (!status)
return;
if (status.Damage || status.Capture)
Attacking.HandleAttackEffects(this.entity, statusName, status, status.source.entity, status.source.owner);
Attacking.HandleAttackEffects(this.entity, statusCode, status, status.source.entity, status.source.owner);
if (!status.Duration)
return;
@ -156,7 +159,7 @@ StatusEffectsReceiver.prototype.ExecuteEffect = function(statusName, lateness)
status._timeElapsed += +(status.Interval || status._interval) + lateness;
if (status._timeElapsed >= +status.Duration)
this.RemoveStatus(statusName);
this.RemoveStatus(statusCode);
};
Engine.RegisterComponentType(IID_StatusEffectsReceiver, "StatusEffectsReceiver", StatusEffectsReceiver);

View file

@ -10,6 +10,7 @@ Engine.LoadComponentScript("interfaces/PlayerManager.js");
Engine.LoadComponentScript("interfaces/Promotion.js");
Engine.LoadComponentScript("interfaces/Resistance.js");
Engine.LoadComponentScript("interfaces/StatisticsTracker.js");
Engine.LoadComponentScript("interfaces/StatusEffectsReceiver.js");
Engine.LoadComponentScript("Resistance.js");
class testResistance
@ -26,9 +27,10 @@ class testResistance
Reset(schema = {})
{
this.cmpResistance = ConstructComponent(this.EntityID, "Resistance", schema);
DeleteMock(this.EntityID, IID_Health);
DeleteMock(this.EntityID, IID_Capturable);
DeleteMock(this.EntityID, IID_Health);
DeleteMock(this.EntityID, IID_Identity);
DeleteMock(this.EntityID, IID_StatusEffectsReceiver);
}
TestInvulnerability()
@ -148,6 +150,103 @@ class testResistance
TS_ASSERT_EQUALS(spy._called, 1);
}
TestStatusEffectsResistancesApplies()
{
// Test duration reduction.
let durationFactor = 0.5;
let statusName = "statusName";
this.Reset({
"Entity": {
"ApplyStatus": {
[statusName]: {
"Duration": durationFactor
}
}
}
});
let duration = 10;
let attackData = {
"ApplyStatus": {
[statusName]: {
"Duration": duration
}
}
};
let cmpStatusEffectsReceiver = AddMock(this.EntityID, IID_StatusEffectsReceiver, {
"ApplyStatus": (effectData, __, ___) => {
TS_ASSERT_EQUALS(effectData[statusName].Duration, duration * durationFactor);
return { "inflictedStatuses": Object.keys(effectData) };
}
});
let spy = new Spy(cmpStatusEffectsReceiver, "ApplyStatus");
Attacking.HandleAttackEffects(this.EntityID, "Test", attackData, this.AttackerID, this.EnemyID);
TS_ASSERT_EQUALS(spy._called, 1);
// Test blocking.
this.Reset({
"Entity": {
"ApplyStatus": {
[statusName]: {
"BlockChance": "1"
}
}
}
});
cmpStatusEffectsReceiver = AddMock(this.EntityID, IID_StatusEffectsReceiver, {
"ApplyStatus": (effectData, __, ___) => {
TS_ASSERT_UNEVAL_EQUALS(effectData, {});
return { "inflictedStatuses": Object.keys(effectData) };
}
});
spy = new Spy(cmpStatusEffectsReceiver, "ApplyStatus");
Attacking.HandleAttackEffects(this.EntityID, "Test", attackData, this.AttackerID, this.EnemyID);
TS_ASSERT_EQUALS(spy._called, 1);
// Test multiple resistances.
let reducedStatusName = "reducedStatus";
let blockedStatusName = "blockedStatus";
this.Reset({
"Entity": {
"ApplyStatus": {
[reducedStatusName]: {
"Duration": durationFactor
},
[blockedStatusName]: {
"BlockChance": "1"
}
}
}
});
attackData = {
"ApplyStatus": {
[reducedStatusName]: {
"Duration": duration
},
[blockedStatusName]: {
"Duration": duration
}
}
};
cmpStatusEffectsReceiver = AddMock(this.EntityID, IID_StatusEffectsReceiver, {
"ApplyStatus": (effectData, __, ___) => {
TS_ASSERT_EQUALS(effectData[reducedStatusName].Duration, duration * durationFactor);
TS_ASSERT_UNEVAL_EQUALS(Object.keys(effectData), [reducedStatusName]);
return { "inflictedStatuses": Object.keys(effectData) };
}
});
spy = new Spy(cmpStatusEffectsReceiver, "ApplyStatus");
Attacking.HandleAttackEffects(this.EntityID, "Test", attackData, this.AttackerID, this.EnemyID);
TS_ASSERT_EQUALS(spy._called, 1);
}
TestResistanceAndBonus()
{
let resistanceValue = 2;
@ -246,5 +345,6 @@ cmp.TestInvulnerability();
cmp.TestBonus();
cmp.TestDamageResistanceApplies();
cmp.TestCaptureResistanceApplies();
cmp.TestStatusEffectsResistancesApplies();
cmp.TestResistanceAndBonus();
cmp.TestMultipleEffects();

View file

@ -20,20 +20,8 @@ const StatusEffectsSchema =
"<element name='ApplyStatus' a:help='Effects like poisoning or burning a unit.'>" +
"<oneOrMore>" +
"<element>" +
"<anyName/>" +
"<anyName a:help='The name must have a matching JSON file in data/status_effects.'/>" +
"<interleave>" +
"<optional>" +
"<element name='StatusName'><text/></element>" +
"</optional>" +
"<optional>" +
"<element name='Icon' a:help='Icon for the status effect.'><text/></element>" +
"</optional>" +
"<optional>" +
"<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>" +
"</optional>" +
@ -196,10 +184,32 @@ Attacking.prototype.GetTotalAttackEffects = function(target, effectData, effectT
if (cmpHealth)
total /= 0.1 + 0.9 * cmpHealth.GetHitpoints() / cmpHealth.GetMaxHitpoints();
}
else if (effectType == "ApplyStatus")
if (effectType != "ApplyStatus")
return total * bonusMultiplier;
if (!resistanceStrengths.ApplyStatus)
return effectData[effectType];
return total * bonusMultiplier;
let result = {};
for (let statusEffect in effectData[effectType])
{
if (!resistanceStrengths.ApplyStatus[statusEffect])
{
result[statusEffect] = effectData[effectType][statusEffect];
continue;
}
if (randBool(resistanceStrengths.ApplyStatus[statusEffect].blockChance))
continue;
result[statusEffect] = effectData[effectType][statusEffect];
if (effectData[effectType][statusEffect].Duration)
result[statusEffect].Duration = effectData[effectType][statusEffect].Duration *
resistanceStrengths.ApplyStatus[statusEffect].duration;
}
return result;
};
/**