Allow status effect to apply modifiers and fix 2333b1814e
Status effect can apply modifier to the entity (armour strength, walking speed, ...) This is also hiding status effect icons by default in selection detail. Fixing smaller related bugs. Differential Revision: https://code.wildfiregames.com/D2281 Patch by: @Freagarach Comments by: Stan, wraitii This was SVN commit r23448.
This commit is contained in:
parent
95b13cda13
commit
82a5ab6d19
@ -1,5 +1,5 @@
|
|||||||
// TODO: could be worth putting this in json files someday
|
// TODO: could be worth putting this in json files someday
|
||||||
const g_EffectTypes = ["Damage", "Capture", "GiveStatus"];
|
const g_EffectTypes = ["Damage", "Capture", "ApplyStatus"];
|
||||||
const g_EffectReceiver = {
|
const g_EffectReceiver = {
|
||||||
"Damage": {
|
"Damage": {
|
||||||
"IID": "IID_Health",
|
"IID": "IID_Health",
|
||||||
@ -9,8 +9,8 @@ const g_EffectReceiver = {
|
|||||||
"IID": "IID_Capturable",
|
"IID": "IID_Capturable",
|
||||||
"method": "Capture"
|
"method": "Capture"
|
||||||
},
|
},
|
||||||
"GiveStatus": {
|
"ApplyStatus": {
|
||||||
"IID": "IID_StatusEffectsReceiver",
|
"IID": "IID_StatusEffectsReceiver",
|
||||||
"method": "GiveStatus"
|
"method": "ApplyStatus"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -43,3 +43,160 @@ function LoadModificationTemplates()
|
|||||||
global.AuraTemplates = new ModificationTemplates("simulation/data/auras/");
|
global.AuraTemplates = new ModificationTemplates("simulation/data/auras/");
|
||||||
global.TechnologyTemplates = new ModificationTemplates("simulation/data/technologies/");
|
global.TechnologyTemplates = new ModificationTemplates("simulation/data/technologies/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derives modifications (to be applied to entities) from a given aura/technology.
|
||||||
|
*
|
||||||
|
* @param {Object} techTemplate - The aura/technology template to derive the modifications from.
|
||||||
|
* @return {Object} - An object containing the relevant modifications.
|
||||||
|
*/
|
||||||
|
function DeriveModificationsFromTech(techTemplate)
|
||||||
|
{
|
||||||
|
if (!techTemplate.modifications)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
let techMods = {};
|
||||||
|
let techAffects = [];
|
||||||
|
if (techTemplate.affects && techTemplate.affects.length)
|
||||||
|
techAffects = techTemplate.affects.map(affected => affected.split(/\s+/));
|
||||||
|
else
|
||||||
|
techAffects.push([]);
|
||||||
|
|
||||||
|
for (let mod of techTemplate.modifications)
|
||||||
|
{
|
||||||
|
let affects = techAffects.slice();
|
||||||
|
if (mod.affects)
|
||||||
|
{
|
||||||
|
let specAffects = mod.affects.split(/\s+/);
|
||||||
|
for (let a in affects)
|
||||||
|
affects[a] = affects[a].concat(specAffects);
|
||||||
|
}
|
||||||
|
|
||||||
|
let newModifier = { "affects": affects };
|
||||||
|
for (let idx in mod)
|
||||||
|
if (idx !== "value" && idx !== "affects")
|
||||||
|
newModifier[idx] = mod[idx];
|
||||||
|
|
||||||
|
if (!techMods[mod.value])
|
||||||
|
techMods[mod.value] = [];
|
||||||
|
techMods[mod.value].push(newModifier);
|
||||||
|
}
|
||||||
|
return techMods;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derives modifications (to be applied to entities) from a provided array
|
||||||
|
* of aura/technology template data.
|
||||||
|
*
|
||||||
|
* @param {Object[]} techsDataArray
|
||||||
|
* @return {Object} - The combined relevant modifications of all the technologies.
|
||||||
|
*/
|
||||||
|
function DeriveModificationsFromTechnologies(techsDataArray)
|
||||||
|
{
|
||||||
|
if (!techsDataArray.length)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
let derivedModifiers = {};
|
||||||
|
for (let technology of techsDataArray)
|
||||||
|
{
|
||||||
|
if (!technology.reqs)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
let modifiers = DeriveModificationsFromTech(technology);
|
||||||
|
for (let modPath in modifiers)
|
||||||
|
{
|
||||||
|
if (!derivedModifiers[modPath])
|
||||||
|
derivedModifiers[modPath] = [];
|
||||||
|
derivedModifiers[modPath] = derivedModifiers[modPath].concat(modifiers[modPath]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return derivedModifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common definition of the XML schema for in-template modifications.
|
||||||
|
*/
|
||||||
|
const ModificationSchema =
|
||||||
|
"<interleave>" +
|
||||||
|
"<element name='Paths' a:help='Space separated value paths to modify.'>" +
|
||||||
|
"<attribute name='datatype'>" +
|
||||||
|
"<value>tokens</value>" +
|
||||||
|
"</attribute>" +
|
||||||
|
"<text/>" +
|
||||||
|
"</element>" +
|
||||||
|
"<element name='Affects' a:help='An array of classes to affect.'>" +
|
||||||
|
"<attribute name='datatype'>" +
|
||||||
|
"<value>tokens</value>" +
|
||||||
|
"</attribute>" +
|
||||||
|
"<text/>" +
|
||||||
|
"</element>" +
|
||||||
|
"<choice>" +
|
||||||
|
"<element name='Add'>" +
|
||||||
|
"<data type='decimal' />" +
|
||||||
|
"</element>" +
|
||||||
|
"<element name='Multiply'>" +
|
||||||
|
"<data type='decimal' />" +
|
||||||
|
"</element>" +
|
||||||
|
"<element name='Replace'>" +
|
||||||
|
"<text/>" +
|
||||||
|
"</element>" +
|
||||||
|
"</choice>" +
|
||||||
|
"</interleave>";
|
||||||
|
|
||||||
|
const ModificationsSchema =
|
||||||
|
"<element name='Modifiers' a:help='List of modifiers.'>" +
|
||||||
|
"<oneOrMore>" +
|
||||||
|
"<element>" +
|
||||||
|
"<anyName />" +
|
||||||
|
ModificationSchema +
|
||||||
|
"</element>" +
|
||||||
|
"</oneOrMore>" +
|
||||||
|
"</element>";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derives a single modification (to be applied to entities) from a given XML template.
|
||||||
|
*
|
||||||
|
* @param {Object} techTemplate - The XML template node to derive the modification from.
|
||||||
|
* @return {Object} containing the relevant modification.
|
||||||
|
*/
|
||||||
|
function DeriveModificationFromXMLTemplate(template)
|
||||||
|
{
|
||||||
|
let effect = {};
|
||||||
|
if (template.Add)
|
||||||
|
effect.add = +template.Add;
|
||||||
|
if (template.Multiply)
|
||||||
|
effect.multiply = +template.Multiply;
|
||||||
|
if (template.Replace)
|
||||||
|
effect.replace = template.Replace;
|
||||||
|
effect.affects = template.Affects ? template.Affects._string.split(/\s/) : [];
|
||||||
|
|
||||||
|
let ret = {};
|
||||||
|
for (let path of template.Paths._string.split(/\s/))
|
||||||
|
{
|
||||||
|
ret[path] = [effect];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derives all modifications (to be applied to entities) from a given XML template.
|
||||||
|
*
|
||||||
|
* @param {Object} techTemplate - The XML template node to derive the modifications from.
|
||||||
|
* @return {Object} containing the combined modifications.
|
||||||
|
*/
|
||||||
|
function DeriveModificationsFromXMLTemplate(template)
|
||||||
|
{
|
||||||
|
let ret = {};
|
||||||
|
for (let name in template)
|
||||||
|
{
|
||||||
|
let modification = DeriveModificationFromXMLTemplate(template[name]);
|
||||||
|
for (let path in modification)
|
||||||
|
{
|
||||||
|
if (!ret[path])
|
||||||
|
ret[path] = [];
|
||||||
|
ret[path].push(modification[path][0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
@ -39,74 +39,6 @@ function GetTechModifiedProperty(modifications, classes, originalValue)
|
|||||||
return originalValue;
|
return originalValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Derives modifications (to be applied to entities) from a given technology.
|
|
||||||
*
|
|
||||||
* @param {Object} techTemplate - The technology template to derive the modifications from.
|
|
||||||
* @return {Object} containing the relevant modifications.
|
|
||||||
*/
|
|
||||||
function DeriveModificationsFromTech(techTemplate)
|
|
||||||
{
|
|
||||||
if (!techTemplate.modifications)
|
|
||||||
return {};
|
|
||||||
|
|
||||||
let techMods = {};
|
|
||||||
let techAffects = [];
|
|
||||||
if (techTemplate.affects && techTemplate.affects.length)
|
|
||||||
for (let affected of techTemplate.affects)
|
|
||||||
techAffects.push(affected.split(/\s+/));
|
|
||||||
else
|
|
||||||
techAffects.push([]);
|
|
||||||
|
|
||||||
for (let mod of techTemplate.modifications)
|
|
||||||
{
|
|
||||||
let affects = techAffects.slice();
|
|
||||||
if (mod.affects)
|
|
||||||
{
|
|
||||||
let specAffects = mod.affects.split(/\s+/);
|
|
||||||
for (let a in affects)
|
|
||||||
affects[a] = affects[a].concat(specAffects);
|
|
||||||
}
|
|
||||||
|
|
||||||
let newModifier = { "affects": affects };
|
|
||||||
for (let idx in mod)
|
|
||||||
if (idx !== "value" && idx !== "affects")
|
|
||||||
newModifier[idx] = mod[idx];
|
|
||||||
|
|
||||||
if (!techMods[mod.value])
|
|
||||||
techMods[mod.value] = [];
|
|
||||||
techMods[mod.value].push(newModifier);
|
|
||||||
}
|
|
||||||
return techMods;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derives modifications (to be applied to entities) from a provided array
|
|
||||||
* of technology template data.
|
|
||||||
*
|
|
||||||
* @param {array} techsDataArray
|
|
||||||
* @return {object} containing the combined relevant modifications of all
|
|
||||||
* the technologies.
|
|
||||||
*/
|
|
||||||
function DeriveModificationsFromTechnologies(techsDataArray)
|
|
||||||
{
|
|
||||||
let derivedModifiers = {};
|
|
||||||
for (let technology of techsDataArray)
|
|
||||||
{
|
|
||||||
if (!technology.reqs)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
let modifiers = DeriveModificationsFromTech(technology);
|
|
||||||
for (let modPath in modifiers)
|
|
||||||
{
|
|
||||||
if (!derivedModifiers[modPath])
|
|
||||||
derivedModifiers[modPath] = [];
|
|
||||||
derivedModifiers[modPath] = derivedModifiers[modPath].concat(modifiers[modPath]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return derivedModifiers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the given modification applies to the entity containing the given class list
|
* Returns whether the given modification applies to the entity containing the given class list
|
||||||
*/
|
*/
|
||||||
|
@ -180,7 +180,9 @@ function GetTemplateDataHelper(template, player, auraTemplates, modifiers = {})
|
|||||||
effects.Damage[damageType] = getEntityValue(path + "/Damage/" + damageType);
|
effects.Damage[damageType] = getEntityValue(path + "/Damage/" + damageType);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: status effects
|
if (temp.ApplyStatus)
|
||||||
|
effects.ApplyStatus = temp.ApplyStatus;
|
||||||
|
|
||||||
return effects;
|
return effects;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -275,13 +275,13 @@ function captureDetails(captureTemplate)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function giveStatusDetails(giveStatusTemplate)
|
function applyStatusDetails(applyStatusTemplate)
|
||||||
{
|
{
|
||||||
if (!giveStatusTemplate)
|
if (!applyStatusTemplate)
|
||||||
return "";
|
return "";
|
||||||
|
|
||||||
return sprintf(translate("gives %(name)s"), {
|
return sprintf(translate("gives %(name)s"), {
|
||||||
"name": Object.keys(giveStatusTemplate).map(x => unitFont(translateWithContext("status effect", x))).join(', '),
|
"name": Object.keys(applyStatusTemplate).map(x => unitFont(translateWithContext("status effect", applyStatusTemplate[x].Name))).join(commaFont(translate(", "))),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,7 +293,7 @@ function attackEffectsDetails(attackTypeTemplate)
|
|||||||
let effects = [
|
let effects = [
|
||||||
captureDetails(attackTypeTemplate.Capture || undefined),
|
captureDetails(attackTypeTemplate.Capture || undefined),
|
||||||
damageDetails(attackTypeTemplate.Damage || undefined),
|
damageDetails(attackTypeTemplate.Damage || undefined),
|
||||||
giveStatusDetails(attackTypeTemplate.GiveStatus || undefined)
|
applyStatusDetails(attackTypeTemplate.ApplyStatus || undefined)
|
||||||
];
|
];
|
||||||
return effects.filter(effect => effect).join(commaFont(translate(", ")));
|
return effects.filter(effect => effect).join(commaFont(translate(", ")));
|
||||||
}
|
}
|
||||||
@ -323,9 +323,9 @@ function getAttackTooltip(template)
|
|||||||
|
|
||||||
// Show the effects of status effects below
|
// Show the effects of status effects below
|
||||||
let statusEffectsDetails = [];
|
let statusEffectsDetails = [];
|
||||||
if (attackTypeTemplate.GiveStatus)
|
if (attackTypeTemplate.ApplyStatus)
|
||||||
for (let status in attackTypeTemplate.GiveStatus)
|
for (let status in attackTypeTemplate.ApplyStatus)
|
||||||
statusEffectsDetails.push("\n " + getStatusEffectsTooltip(status, attackTypeTemplate.GiveStatus[status]));
|
statusEffectsDetails.push("\n " + getStatusEffectsTooltip(attackTypeTemplate.ApplyStatus[status]));
|
||||||
statusEffectsDetails = statusEffectsDetails.join("");
|
statusEffectsDetails = statusEffectsDetails.join("");
|
||||||
|
|
||||||
tooltips.push(sprintf(translate("%(attackLabel)s: %(effects)s, %(range)s, %(rate)s%(statusEffects)s"), {
|
tooltips.push(sprintf(translate("%(attackLabel)s: %(effects)s, %(range)s, %(rate)s%(statusEffects)s"), {
|
||||||
@ -371,20 +371,48 @@ function getSplashDamageTooltip(template)
|
|||||||
return tooltips.join("\n");
|
return tooltips.join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStatusEffectsTooltip(name, template)
|
function getStatusEffectsTooltip(template)
|
||||||
{
|
{
|
||||||
|
let tooltipAttributes = [];
|
||||||
|
let tooltipString = "";
|
||||||
|
if (template.Tooltip)
|
||||||
|
{
|
||||||
|
tooltipAttributes.push("%(tooltip)s");
|
||||||
|
tooltipString = translate(template.Tooltip);
|
||||||
|
}
|
||||||
|
|
||||||
|
let attackEffectsString = "";
|
||||||
|
if (template.Damage || template.Capture)
|
||||||
|
{
|
||||||
|
tooltipAttributes.push("%(effects)s");
|
||||||
|
attackEffectsString = attackEffectsDetails(template);
|
||||||
|
}
|
||||||
|
|
||||||
|
let intervalString = "";
|
||||||
|
if (template.Interval)
|
||||||
|
{
|
||||||
|
tooltipAttributes.push("%(rate)s");
|
||||||
|
intervalString = sprintf(translate("%(interval)s"), {
|
||||||
|
"interval": attackRateDetails(+template.Interval)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let durationString = "";
|
let durationString = "";
|
||||||
if (template.Duration)
|
if (template.Duration)
|
||||||
durationString = sprintf(translate(", %(durName)s: %(duration)s"), {
|
{
|
||||||
|
tooltipAttributes.push("%(duration)s");
|
||||||
|
durationString = sprintf(translate("%(durName)s: %(duration)s"), {
|
||||||
"durName": headerFont(translate("Duration")),
|
"durName": headerFont(translate("Duration")),
|
||||||
"duration": getSecondsString((template.TimeElapsed ? +template.Duration - template.TimeElapsed : +template.Duration) / 1000),
|
"duration": getSecondsString((template._timeElapsed ? +template.Duration - template._timeElapsed : +template.Duration) / 1000),
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return sprintf(translate("%(statusName)s: %(effects)s, %(rate)s%(durationString)s"), {
|
return sprintf(translate("%(statusName)s: " + tooltipAttributes.join(translate(commaFont(", ")))), {
|
||||||
"statusName": headerFont(translateWithContext("status effect", name)),
|
"statusName": headerFont(translateWithContext("status effect", template.Name)),
|
||||||
"effects": attackEffectsDetails(template),
|
"tooltip": tooltipString,
|
||||||
"rate": attackRateDetails(+template.Interval),
|
"effects": attackEffectsString,
|
||||||
"durationString": durationString
|
"rate": intervalString,
|
||||||
|
"duration": durationString
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,14 +94,16 @@ function displaySingle(entState)
|
|||||||
|
|
||||||
if (entState.statusEffects)
|
if (entState.statusEffects)
|
||||||
{
|
{
|
||||||
let statusIcons = Engine.GetGUIObjectByName("statusEffectsIcons").children;
|
let statusEffectsSection = Engine.GetGUIObjectByName("statusEffectsIcons");
|
||||||
|
statusEffectsSection.hidden = false;
|
||||||
|
let statusIcons = statusEffectsSection.children;
|
||||||
let i = 0;
|
let i = 0;
|
||||||
for (let effectName in entState.statusEffects)
|
for (let effectName in entState.statusEffects)
|
||||||
{
|
{
|
||||||
let effect = entState.statusEffects[effectName];
|
let effect = entState.statusEffects[effectName];
|
||||||
statusIcons[i].hidden = false;
|
statusIcons[i].hidden = false;
|
||||||
statusIcons[i].sprite = "stretched:session/icons/status_effects/" + (effect.Icon || "default") + ".png";
|
statusIcons[i].sprite = "stretched:session/icons/status_effects/" + (effect.Icon || "default") + ".png";
|
||||||
statusIcons[i].tooltip = getStatusEffectsTooltip(effectName, effect);
|
statusIcons[i].tooltip = getStatusEffectsTooltip(effect);
|
||||||
let size = statusIcons[i].size;
|
let size = statusIcons[i].size;
|
||||||
size.top = i * 18;
|
size.top = i * 18;
|
||||||
size.bottom = i * 18 + 16;
|
size.bottom = i * 18 + 16;
|
||||||
@ -111,6 +113,8 @@ function displaySingle(entState)
|
|||||||
for (; i < statusIcons.length; ++i)
|
for (; i < statusIcons.length; ++i)
|
||||||
statusIcons[i].hidden = true;
|
statusIcons[i].hidden = true;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
Engine.GetGUIObjectByName("statusEffectsIcons").hidden = true;
|
||||||
|
|
||||||
let showHealth = entState.hitpoints;
|
let showHealth = entState.hitpoints;
|
||||||
let showResource = entState.resourceSupply;
|
let showResource = entState.resourceSupply;
|
||||||
|
@ -1,69 +1,145 @@
|
|||||||
function StatusEffectsReceiver() {}
|
function StatusEffectsReceiver() {}
|
||||||
|
|
||||||
|
StatusEffectsReceiver.prototype.DefaultInterval = 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises the status effects.
|
||||||
|
*/
|
||||||
StatusEffectsReceiver.prototype.Init = function()
|
StatusEffectsReceiver.prototype.Init = function()
|
||||||
{
|
{
|
||||||
this.activeStatusEffects = {};
|
this.activeStatusEffects = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which status effects are active on this entity.
|
||||||
|
*
|
||||||
|
* @return {Object} - An object containing the status effects which currently affect the entity.
|
||||||
|
*/
|
||||||
StatusEffectsReceiver.prototype.GetActiveStatuses = function()
|
StatusEffectsReceiver.prototype.GetActiveStatuses = function()
|
||||||
{
|
{
|
||||||
return this.activeStatusEffects;
|
return this.activeStatusEffects;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Called by attacking effects.
|
/**
|
||||||
StatusEffectsReceiver.prototype.GiveStatus = function(effectData, attacker, attackerOwner, bonusMultiplier)
|
* Called by Attacking effects. Adds status effects for each entry in the effectData.
|
||||||
|
*
|
||||||
|
* @param {Object} effectData - An object containing the status effects to give to the entity.
|
||||||
|
* @param {number} attacker - The entity ID of the attacker.
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
StatusEffectsReceiver.prototype.ApplyStatus = function(effectData, attacker, attackerOwner, bonusMultiplier)
|
||||||
{
|
{
|
||||||
|
let attackerData = { "entity": attacker, "owner": attackerOwner };
|
||||||
for (let effect in effectData)
|
for (let effect in effectData)
|
||||||
this.AddStatus(effect, effectData[effect]);
|
this.AddStatus(effect, effectData[effect], attackerData);
|
||||||
|
|
||||||
// TODO: implement loot / resistance.
|
// TODO: implement loot / resistance.
|
||||||
|
|
||||||
return { "inflictedStatuses": Object.keys(effectData) };
|
return { "inflictedStatuses": Object.keys(effectData) };
|
||||||
};
|
};
|
||||||
|
|
||||||
StatusEffectsReceiver.prototype.AddStatus = function(statusName, data)
|
/**
|
||||||
|
* Adds a status effect to the entity.
|
||||||
|
*
|
||||||
|
* @param {string} statusName - The name of the status effect.
|
||||||
|
* @param {object} data - The various effects and timings.
|
||||||
|
*/
|
||||||
|
StatusEffectsReceiver.prototype.AddStatus = function(statusName, data, attackerData)
|
||||||
{
|
{
|
||||||
if (this.activeStatusEffects[statusName])
|
if (this.activeStatusEffects[statusName])
|
||||||
|
{
|
||||||
|
// TODO: implement different behaviour when receiving the same status multiple times.
|
||||||
|
// For now, these are ignored.
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.activeStatusEffects[statusName] = {};
|
this.activeStatusEffects[statusName] = {};
|
||||||
let status = this.activeStatusEffects[statusName];
|
let status = this.activeStatusEffects[statusName];
|
||||||
Object.assign(status, data);
|
Object.assign(status, data);
|
||||||
status.Interval = +data.Interval;
|
|
||||||
status.TimeElapsed = 0;
|
|
||||||
status.FirstTime = true;
|
|
||||||
|
|
||||||
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
if (status.Modifiers)
|
||||||
status.Timer = cmpTimer.SetInterval(this.entity, IID_StatusEffectsReceiver, "ExecuteEffect", 0, +status.Interval, statusName);
|
{
|
||||||
};
|
let modifications = DeriveModificationsFromXMLTemplate(status.Modifiers);
|
||||||
|
let cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager);
|
||||||
|
cmpModifiersManager.AddModifiers(statusName, modifications, this.entity);
|
||||||
|
}
|
||||||
|
|
||||||
StatusEffectsReceiver.prototype.RemoveStatus = function(statusName)
|
// With neither an interval nor a duration, there is no point in starting a timer.
|
||||||
{
|
if (!status.Duration && !status.Interval)
|
||||||
if (!this.activeStatusEffects[statusName])
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// We need this to prevent Status Effects from giving XP
|
||||||
|
// to the entity that applied them.
|
||||||
|
status.StatusEffect = true;
|
||||||
|
|
||||||
|
// We want an interval to update the GUI to show how much time of the status effect
|
||||||
|
// is left even if the status effect itself has no interval.
|
||||||
|
if (!status.Interval)
|
||||||
|
status._interval = this.DefaultInterval;
|
||||||
|
|
||||||
|
status._timeElapsed = 0;
|
||||||
|
status._firstTime = true;
|
||||||
|
status.source = attackerData;
|
||||||
|
|
||||||
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||||
cmpTimer.CancelTimer(this.activeStatusEffects[statusName].Timer);
|
status._timer = cmpTimer.SetInterval(this.entity, IID_StatusEffectsReceiver, "ExecuteEffect", 0, +(status.Interval || status._interval), statusName);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a status effect from the entity.
|
||||||
|
*
|
||||||
|
* @param {string} statusName - The status effect to be removed.
|
||||||
|
*/
|
||||||
|
StatusEffectsReceiver.prototype.RemoveStatus = function(statusName)
|
||||||
|
{
|
||||||
|
let statusEffect = this.activeStatusEffects[statusName];
|
||||||
|
if (!statusEffect)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (statusEffect.Modifiers)
|
||||||
|
{
|
||||||
|
let cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager);
|
||||||
|
cmpModifiersManager.RemoveAllModifiers(statusName, this.entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statusEffect._timer)
|
||||||
|
{
|
||||||
|
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||||
|
cmpTimer.CancelTimer(statusEffect._timer);
|
||||||
|
}
|
||||||
delete this.activeStatusEffects[statusName];
|
delete this.activeStatusEffects[statusName];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the timers. Executes a status effect.
|
||||||
|
*
|
||||||
|
* @param {string} statusName - The name of 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(statusName, lateness)
|
||||||
{
|
{
|
||||||
let status = this.activeStatusEffects[statusName];
|
let status = this.activeStatusEffects[statusName];
|
||||||
if (!status)
|
if (!status)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (status.FirstTime)
|
if (status.Damage || status.Capture)
|
||||||
|
Attacking.HandleAttackEffects(statusName, status, this.entity, status.source.entity, status.source.owner);
|
||||||
|
|
||||||
|
if (!status.Duration)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (status._firstTime)
|
||||||
{
|
{
|
||||||
status.FirstTime = false;
|
status._firstTime = false;
|
||||||
status.TimeElapsed += lateness;
|
status._timeElapsed += lateness;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
status.TimeElapsed += status.Interval + lateness;
|
status._timeElapsed += +(status.Interval || status._interval) + lateness;
|
||||||
|
|
||||||
Attacking.HandleAttackEffects(statusName, status, this.entity, -1, -1);
|
if (status._timeElapsed >= +status.Duration)
|
||||||
|
|
||||||
if (status.Duration && status.TimeElapsed >= +status.Duration)
|
|
||||||
this.RemoveStatus(statusName);
|
this.RemoveStatus(statusName);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4099,6 +4099,9 @@ UnitAI.prototype.OnGlobalEntityRenamed = function(msg)
|
|||||||
|
|
||||||
UnitAI.prototype.OnAttacked = function(msg)
|
UnitAI.prototype.OnAttacked = function(msg)
|
||||||
{
|
{
|
||||||
|
if (msg.fromStatusEffect)
|
||||||
|
return;
|
||||||
|
|
||||||
this.UnitFsm.ProcessMessage(this, {"type": "Attacked", "data": msg});
|
this.UnitFsm.ProcessMessage(this, {"type": "Attacked", "data": msg});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -103,7 +103,16 @@ function Test_Generic()
|
|||||||
|
|
||||||
Engine.PostMessage = function(ent, iid, message)
|
Engine.PostMessage = function(ent, iid, message)
|
||||||
{
|
{
|
||||||
TS_ASSERT_UNEVAL_EQUALS({ "type": type, "target": target, "attacker": attacker, "attackerOwner": attackerOwner, "damage": damage, "capture": 0, "statusEffects": [] }, message);
|
TS_ASSERT_UNEVAL_EQUALS({
|
||||||
|
"type": type,
|
||||||
|
"target": target,
|
||||||
|
"attacker": attacker,
|
||||||
|
"attackerOwner": attackerOwner,
|
||||||
|
"damage": damage,
|
||||||
|
"capture": 0,
|
||||||
|
"statusEffects": [],
|
||||||
|
"fromStatusEffect": false
|
||||||
|
}, message);
|
||||||
};
|
};
|
||||||
|
|
||||||
AddMock(target, IID_Footprint, {
|
AddMock(target, IID_Footprint, {
|
||||||
|
@ -11,6 +11,7 @@ Engine.LoadComponentScript("interfaces/Pack.js");
|
|||||||
Engine.LoadComponentScript("interfaces/Player.js");
|
Engine.LoadComponentScript("interfaces/Player.js");
|
||||||
Engine.LoadComponentScript("interfaces/Promotion.js");
|
Engine.LoadComponentScript("interfaces/Promotion.js");
|
||||||
Engine.LoadComponentScript("interfaces/ResourceGatherer.js");
|
Engine.LoadComponentScript("interfaces/ResourceGatherer.js");
|
||||||
|
Engine.LoadComponentScript("interfaces/StatusEffectsReceiver.js");
|
||||||
Engine.LoadComponentScript("interfaces/Timer.js");
|
Engine.LoadComponentScript("interfaces/Timer.js");
|
||||||
Engine.LoadComponentScript("interfaces/UnitAI.js");
|
Engine.LoadComponentScript("interfaces/UnitAI.js");
|
||||||
Engine.LoadComponentScript("Pack.js");
|
Engine.LoadComponentScript("Pack.js");
|
||||||
|
@ -7,6 +7,8 @@ var target = 42;
|
|||||||
var cmpStatusReceiver;
|
var cmpStatusReceiver;
|
||||||
var cmpTimer;
|
var cmpTimer;
|
||||||
var dealtDamage;
|
var dealtDamage;
|
||||||
|
var enemyEntity = 4;
|
||||||
|
var enemy = 2;
|
||||||
|
|
||||||
function setup()
|
function setup()
|
||||||
{
|
{
|
||||||
@ -31,6 +33,10 @@ function testInflictEffects()
|
|||||||
"Damage": {
|
"Damage": {
|
||||||
[statusName]: 1
|
[statusName]: 1
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": enemyEntity,
|
||||||
|
"owner": enemy,
|
||||||
});
|
});
|
||||||
|
|
||||||
cmpTimer.OnUpdate({ "turnLength": 1 });
|
cmpTimer.OnUpdate({ "turnLength": 1 });
|
||||||
@ -63,7 +69,7 @@ function testMultipleEffects()
|
|||||||
Engine.RegisterGlobal("Attacking", Attacking);
|
Engine.RegisterGlobal("Attacking", Attacking);
|
||||||
|
|
||||||
// damage scheduled: 0, 1, 2, 10 sec
|
// damage scheduled: 0, 1, 2, 10 sec
|
||||||
cmpStatusReceiver.GiveStatus({
|
cmpStatusReceiver.ApplyStatus({
|
||||||
"Burn": {
|
"Burn": {
|
||||||
"Duration": 20000,
|
"Duration": 20000,
|
||||||
"Interval": 10000,
|
"Interval": 10000,
|
||||||
@ -111,6 +117,10 @@ function testRemoveStatus()
|
|||||||
"Damage": {
|
"Damage": {
|
||||||
[statusName]: 1
|
[statusName]: 1
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": enemyEntity,
|
||||||
|
"owner": enemy,
|
||||||
});
|
});
|
||||||
|
|
||||||
cmpTimer.OnUpdate({ "turnLength": 1 });
|
cmpTimer.OnUpdate({ "turnLength": 1 });
|
||||||
|
@ -3,14 +3,8 @@
|
|||||||
*/
|
*/
|
||||||
function Attacking() {}
|
function Attacking() {}
|
||||||
|
|
||||||
/**
|
const DirectEffectsSchema =
|
||||||
* Builds a RelaxRNG schema of possible attack effects.
|
"<element name='Damage'>" +
|
||||||
* See globalscripts/AttackEffects.js for possible elements.
|
|
||||||
* Attacks may also have a "Bonuses" element.
|
|
||||||
*
|
|
||||||
* @return {string} - RelaxNG schema string
|
|
||||||
*/
|
|
||||||
const DamageSchema = "" +
|
|
||||||
"<oneOrMore>" +
|
"<oneOrMore>" +
|
||||||
"<element a:help='One or more elements describing damage types'>" +
|
"<element a:help='One or more elements describing damage types'>" +
|
||||||
"<anyName>" +
|
"<anyName>" +
|
||||||
@ -19,34 +13,60 @@ const DamageSchema = "" +
|
|||||||
"</anyName>" +
|
"</anyName>" +
|
||||||
"<ref name='nonNegativeDecimal' />" +
|
"<ref name='nonNegativeDecimal' />" +
|
||||||
"</element>" +
|
"</element>" +
|
||||||
"</oneOrMore>";
|
"</oneOrMore>" +
|
||||||
|
"</element>" +
|
||||||
|
"<element name='Capture' a:help='Capture points value'>" +
|
||||||
|
"<ref name='nonNegativeDecimal'/>" +
|
||||||
|
"</element>";
|
||||||
|
|
||||||
|
const StatusEffectsSchema =
|
||||||
|
"<element name='ApplyStatus' a:help='Effects like poisoning or burning a unit.'>" +
|
||||||
|
"<oneOrMore>" +
|
||||||
|
"<element>" +
|
||||||
|
"<anyName/>" +
|
||||||
|
"<interleave>" +
|
||||||
|
"<element name='Name'><text/></element>" +
|
||||||
|
"<optional>" +
|
||||||
|
"<element name='Icon' a:help='Icon for the status effect.'><text/></element>" +
|
||||||
|
"</optional>" +
|
||||||
|
"<optional>" +
|
||||||
|
"<element name='Tooltip'><text/></element>" +
|
||||||
|
"</optional>" +
|
||||||
|
"<optional>" +
|
||||||
|
"<element name='Duration' a:help='The duration of the status while the effect occurs.'><ref name='nonNegativeDecimal'/></element>" +
|
||||||
|
"</optional>" +
|
||||||
|
"<optional>" +
|
||||||
|
"<interleave>" +
|
||||||
|
"<element name='Interval' a:help='Interval between the occurances of the effect.'><ref name='nonNegativeDecimal'/></element>" +
|
||||||
|
"<oneOrMore>" +
|
||||||
|
"<choice>" +
|
||||||
|
DirectEffectsSchema +
|
||||||
|
"</choice>" +
|
||||||
|
"</oneOrMore>" +
|
||||||
|
"</interleave>" +
|
||||||
|
"</optional>" +
|
||||||
|
"<optional>" +
|
||||||
|
ModificationsSchema +
|
||||||
|
"</optional>" +
|
||||||
|
"</interleave>" +
|
||||||
|
"</element>" +
|
||||||
|
"</oneOrMore>" +
|
||||||
|
"</element>";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a RelaxRNG schema of possible attack effects.
|
||||||
|
* See globalscripts/AttackEffects.js for possible elements.
|
||||||
|
* Attacks may also have a "Bonuses" element.
|
||||||
|
*
|
||||||
|
* @return {string} - RelaxNG schema string.
|
||||||
|
*/
|
||||||
Attacking.prototype.BuildAttackEffectsSchema = function()
|
Attacking.prototype.BuildAttackEffectsSchema = function()
|
||||||
{
|
{
|
||||||
return "" +
|
return "" +
|
||||||
"<oneOrMore>" +
|
"<oneOrMore>" +
|
||||||
"<choice>" +
|
"<choice>" +
|
||||||
"<element name='Damage'>" +
|
DirectEffectsSchema +
|
||||||
DamageSchema +
|
StatusEffectsSchema +
|
||||||
"</element>" +
|
|
||||||
"<element name='Capture' a:help='Capture points value'>" +
|
|
||||||
"<ref name='nonNegativeDecimal'/>" +
|
|
||||||
"</element>" +
|
|
||||||
"<element name='GiveStatus' a:help='Effects like poisoning or burning a unit.'>" +
|
|
||||||
"<oneOrMore>" +
|
|
||||||
"<element>" +
|
|
||||||
"<anyName/>" +
|
|
||||||
"<interleave>" +
|
|
||||||
"<optional>" +
|
|
||||||
"<element name='Icon' a:help='Icon for the status effect'><text/></element>" +
|
|
||||||
"</optional>" +
|
|
||||||
"<element name='Duration' a:help='The duration of the status while the effect occurs.'><ref name='nonNegativeDecimal'/></element>" +
|
|
||||||
"<element name='Interval' a:help='Interval between the occurances of the effect.'><ref name='nonNegativeDecimal'/></element>" +
|
|
||||||
"<element name='Damage' a:help='Damage caused by the effect.'>" + DamageSchema + "</element>" +
|
|
||||||
"</interleave>" +
|
|
||||||
"</element>" +
|
|
||||||
"</oneOrMore>" +
|
|
||||||
"</element>" +
|
|
||||||
"</choice>" +
|
"</choice>" +
|
||||||
"</oneOrMore>" +
|
"</oneOrMore>" +
|
||||||
"<optional>" +
|
"<optional>" +
|
||||||
@ -85,8 +105,8 @@ Attacking.prototype.GetAttackEffectsData = function(valueModifRoot, template, en
|
|||||||
if (template.Capture)
|
if (template.Capture)
|
||||||
ret.Capture = ApplyValueModificationsToEntity(valueModifRoot + "/Capture", +(template.Capture || 0), entity);
|
ret.Capture = ApplyValueModificationsToEntity(valueModifRoot + "/Capture", +(template.Capture || 0), entity);
|
||||||
|
|
||||||
if (template.GiveStatus)
|
if (template.ApplyStatus)
|
||||||
ret.GiveStatus = template.GiveStatus;
|
ret.ApplyStatus = template.ApplyStatus;
|
||||||
|
|
||||||
if (template.Bonuses)
|
if (template.Bonuses)
|
||||||
ret.Bonuses = template.Bonuses;
|
ret.Bonuses = template.Bonuses;
|
||||||
@ -250,10 +270,6 @@ Attacking.prototype.HandleAttackEffects = function(attackType, attackData, targe
|
|||||||
Object.assign(targetState, cmpReceiver[receiver.method](attackData[effectType], attacker, attackerOwner, bonusMultiplier));
|
Object.assign(targetState, cmpReceiver[receiver.method](attackData[effectType], attacker, attackerOwner, bonusMultiplier));
|
||||||
}
|
}
|
||||||
|
|
||||||
let cmpPromotion = Engine.QueryInterface(attacker, IID_Promotion);
|
|
||||||
if (cmpPromotion && targetState.xp)
|
|
||||||
cmpPromotion.IncreaseXp(targetState.xp);
|
|
||||||
|
|
||||||
if (targetState.killed)
|
if (targetState.killed)
|
||||||
this.TargetKilled(attacker, target, attackerOwner);
|
this.TargetKilled(attacker, target, attackerOwner);
|
||||||
|
|
||||||
@ -265,7 +281,16 @@ Attacking.prototype.HandleAttackEffects = function(attackType, attackData, targe
|
|||||||
"damage": -(targetState.HPchange || 0),
|
"damage": -(targetState.HPchange || 0),
|
||||||
"capture": targetState.captureChange || 0,
|
"capture": targetState.captureChange || 0,
|
||||||
"statusEffects": targetState.inflictedStatuses || [],
|
"statusEffects": targetState.inflictedStatuses || [],
|
||||||
|
"fromStatusEffect": !!attackData.StatusEffect,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// We do not want an entity to get XP from active Status Effects.
|
||||||
|
if (!!attackData.StatusEffect)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let cmpPromotion = Engine.QueryInterface(attacker, IID_Promotion);
|
||||||
|
if (cmpPromotion && targetState.xp)
|
||||||
|
cmpPromotion.IncreaseXp(targetState.xp);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -110,6 +110,20 @@ function ChangeEntityTemplate(oldEnt, newTemplate)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let cmpStatusEffectsReceiver = Engine.QueryInterface(oldEnt, IID_StatusEffectsReceiver);
|
||||||
|
let cmpNewStatusEffectsReceiver = Engine.QueryInterface(newEnt, IID_StatusEffectsReceiver);
|
||||||
|
if (cmpStatusEffectsReceiver && cmpNewStatusEffectsReceiver)
|
||||||
|
{
|
||||||
|
let activeStatus = cmpStatusEffectsReceiver.GetActiveStatuses();
|
||||||
|
for (let status in activeStatus)
|
||||||
|
{
|
||||||
|
let newStatus = activeStatus[status];
|
||||||
|
if (newStatus.Duration)
|
||||||
|
newStatus.Duration -= newStatus._timeElapsed;
|
||||||
|
cmpNewStatusEffectsReceiver.ApplyStatus({ [status]: newStatus }, newStatus.source.entity, newStatus.source.owner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TransferGarrisonedUnits(oldEnt, newEnt);
|
TransferGarrisonedUnits(oldEnt, newEnt);
|
||||||
|
|
||||||
Engine.PostMessage(oldEnt, MT_EntityRenamed, { "entity": oldEnt, "newentity": newEnt });
|
Engine.PostMessage(oldEnt, MT_EntityRenamed, { "entity": oldEnt, "newentity": newEnt });
|
||||||
|
Loading…
Reference in New Issue
Block a user