mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-20 15:23:56 -07:00
Allow resistances to StatusEffects.
Differential Revision: D2908 Reviewed by: @wraitii Comments by: @bb. This was SVN commit r24162.
This commit is contained in:
parent
b5df81af76
commit
876b035336
10 changed files with 306 additions and 100 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"), "");
|
||||
|
|
|
|||
|
|
@ -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(", ")))
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in a new issue