1
0
forked from 0ad/0ad

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:
Angen 2020-01-27 16:51:25 +00:00
parent 95b13cda13
commit 82a5ab6d19
13 changed files with 412 additions and 151 deletions

View File

@ -1,5 +1,5 @@
// 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 = {
"Damage": {
"IID": "IID_Health",
@ -9,8 +9,8 @@ const g_EffectReceiver = {
"IID": "IID_Capturable",
"method": "Capture"
},
"GiveStatus": {
"ApplyStatus": {
"IID": "IID_StatusEffectsReceiver",
"method": "GiveStatus"
"method": "ApplyStatus"
}
};

View File

@ -43,3 +43,160 @@ function LoadModificationTemplates()
global.AuraTemplates = new ModificationTemplates("simulation/data/auras/");
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;
}

View File

@ -39,74 +39,6 @@ function GetTechModifiedProperty(modifications, classes, 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
*/

View File

@ -180,7 +180,9 @@ function GetTemplateDataHelper(template, player, auraTemplates, modifiers = {})
effects.Damage[damageType] = getEntityValue(path + "/Damage/" + damageType);
}
// TODO: status effects
if (temp.ApplyStatus)
effects.ApplyStatus = temp.ApplyStatus;
return effects;
};

View File

@ -275,13 +275,13 @@ function captureDetails(captureTemplate)
});
}
function giveStatusDetails(giveStatusTemplate)
function applyStatusDetails(applyStatusTemplate)
{
if (!giveStatusTemplate)
if (!applyStatusTemplate)
return "";
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 = [
captureDetails(attackTypeTemplate.Capture || undefined),
damageDetails(attackTypeTemplate.Damage || undefined),
giveStatusDetails(attackTypeTemplate.GiveStatus || undefined)
applyStatusDetails(attackTypeTemplate.ApplyStatus || undefined)
];
return effects.filter(effect => effect).join(commaFont(translate(", ")));
}
@ -323,9 +323,9 @@ function getAttackTooltip(template)
// Show the effects of status effects below
let statusEffectsDetails = [];
if (attackTypeTemplate.GiveStatus)
for (let status in attackTypeTemplate.GiveStatus)
statusEffectsDetails.push("\n " + getStatusEffectsTooltip(status, attackTypeTemplate.GiveStatus[status]));
if (attackTypeTemplate.ApplyStatus)
for (let status in attackTypeTemplate.ApplyStatus)
statusEffectsDetails.push("\n " + getStatusEffectsTooltip(attackTypeTemplate.ApplyStatus[status]));
statusEffectsDetails = statusEffectsDetails.join("");
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");
}
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 = "";
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")),
"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"), {
"statusName": headerFont(translateWithContext("status effect", name)),
"effects": attackEffectsDetails(template),
"rate": attackRateDetails(+template.Interval),
"durationString": durationString
return sprintf(translate("%(statusName)s: " + tooltipAttributes.join(translate(commaFont(", ")))), {
"statusName": headerFont(translateWithContext("status effect", template.Name)),
"tooltip": tooltipString,
"effects": attackEffectsString,
"rate": intervalString,
"duration": durationString
});
}

View File

@ -94,14 +94,16 @@ function displaySingle(entState)
if (entState.statusEffects)
{
let statusIcons = Engine.GetGUIObjectByName("statusEffectsIcons").children;
let statusEffectsSection = Engine.GetGUIObjectByName("statusEffectsIcons");
statusEffectsSection.hidden = false;
let statusIcons = statusEffectsSection.children;
let i = 0;
for (let effectName in entState.statusEffects)
{
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(effectName, effect);
statusIcons[i].tooltip = getStatusEffectsTooltip(effect);
let size = statusIcons[i].size;
size.top = i * 18;
size.bottom = i * 18 + 16;
@ -111,6 +113,8 @@ function displaySingle(entState)
for (; i < statusIcons.length; ++i)
statusIcons[i].hidden = true;
}
else
Engine.GetGUIObjectByName("statusEffectsIcons").hidden = true;
let showHealth = entState.hitpoints;
let showResource = entState.resourceSupply;

View File

@ -1,69 +1,145 @@
function StatusEffectsReceiver() {}
StatusEffectsReceiver.prototype.DefaultInterval = 1000;
/**
* Initialises the status effects.
*/
StatusEffectsReceiver.prototype.Init = function()
{
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()
{
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)
this.AddStatus(effect, effectData[effect]);
this.AddStatus(effect, effectData[effect], attackerData);
// TODO: implement loot / resistance.
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])
{
// TODO: implement different behaviour when receiving the same status multiple times.
// For now, these are ignored.
return;
}
this.activeStatusEffects[statusName] = {};
let status = this.activeStatusEffects[statusName];
Object.assign(status, data);
status.Interval = +data.Interval;
status.TimeElapsed = 0;
status.FirstTime = true;
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
status.Timer = cmpTimer.SetInterval(this.entity, IID_StatusEffectsReceiver, "ExecuteEffect", 0, +status.Interval, statusName);
};
StatusEffectsReceiver.prototype.RemoveStatus = function(statusName)
if (status.Modifiers)
{
if (!this.activeStatusEffects[statusName])
let modifications = DeriveModificationsFromXMLTemplate(status.Modifiers);
let cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager);
cmpModifiersManager.AddModifiers(statusName, modifications, this.entity);
}
// With neither an interval nor a duration, there is no point in starting a timer.
if (!status.Duration && !status.Interval)
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);
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];
};
/**
* 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)
{
let status = this.activeStatusEffects[statusName];
if (!status)
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.TimeElapsed += lateness;
status._firstTime = false;
status._timeElapsed += lateness;
}
else
status.TimeElapsed += status.Interval + lateness;
status._timeElapsed += +(status.Interval || status._interval) + lateness;
Attacking.HandleAttackEffects(statusName, status, this.entity, -1, -1);
if (status.Duration && status.TimeElapsed >= +status.Duration)
if (status._timeElapsed >= +status.Duration)
this.RemoveStatus(statusName);
};

View File

@ -4099,6 +4099,9 @@ UnitAI.prototype.OnGlobalEntityRenamed = function(msg)
UnitAI.prototype.OnAttacked = function(msg)
{
if (msg.fromStatusEffect)
return;
this.UnitFsm.ProcessMessage(this, {"type": "Attacked", "data": msg});
};

View File

@ -103,7 +103,16 @@ function Test_Generic()
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, {

View File

@ -11,6 +11,7 @@ Engine.LoadComponentScript("interfaces/Pack.js");
Engine.LoadComponentScript("interfaces/Player.js");
Engine.LoadComponentScript("interfaces/Promotion.js");
Engine.LoadComponentScript("interfaces/ResourceGatherer.js");
Engine.LoadComponentScript("interfaces/StatusEffectsReceiver.js");
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("interfaces/UnitAI.js");
Engine.LoadComponentScript("Pack.js");

View File

@ -7,6 +7,8 @@ var target = 42;
var cmpStatusReceiver;
var cmpTimer;
var dealtDamage;
var enemyEntity = 4;
var enemy = 2;
function setup()
{
@ -31,6 +33,10 @@ function testInflictEffects()
"Damage": {
[statusName]: 1
}
},
{
"entity": enemyEntity,
"owner": enemy,
});
cmpTimer.OnUpdate({ "turnLength": 1 });
@ -63,7 +69,7 @@ function testMultipleEffects()
Engine.RegisterGlobal("Attacking", Attacking);
// damage scheduled: 0, 1, 2, 10 sec
cmpStatusReceiver.GiveStatus({
cmpStatusReceiver.ApplyStatus({
"Burn": {
"Duration": 20000,
"Interval": 10000,
@ -111,6 +117,10 @@ function testRemoveStatus()
"Damage": {
[statusName]: 1
}
},
{
"entity": enemyEntity,
"owner": enemy,
});
cmpTimer.OnUpdate({ "turnLength": 1 });

View File

@ -3,14 +3,8 @@
*/
function Attacking() {}
/**
* 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
*/
const DamageSchema = "" +
const DirectEffectsSchema =
"<element name='Damage'>" +
"<oneOrMore>" +
"<element a:help='One or more elements describing damage types'>" +
"<anyName>" +
@ -19,34 +13,60 @@ const DamageSchema = "" +
"</anyName>" +
"<ref name='nonNegativeDecimal' />" +
"</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()
{
return "" +
"<oneOrMore>" +
"<choice>" +
"<element name='Damage'>" +
DamageSchema +
"</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>" +
DirectEffectsSchema +
StatusEffectsSchema +
"</choice>" +
"</oneOrMore>" +
"<optional>" +
@ -85,8 +105,8 @@ Attacking.prototype.GetAttackEffectsData = function(valueModifRoot, template, en
if (template.Capture)
ret.Capture = ApplyValueModificationsToEntity(valueModifRoot + "/Capture", +(template.Capture || 0), entity);
if (template.GiveStatus)
ret.GiveStatus = template.GiveStatus;
if (template.ApplyStatus)
ret.ApplyStatus = template.ApplyStatus;
if (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));
}
let cmpPromotion = Engine.QueryInterface(attacker, IID_Promotion);
if (cmpPromotion && targetState.xp)
cmpPromotion.IncreaseXp(targetState.xp);
if (targetState.killed)
this.TargetKilled(attacker, target, attackerOwner);
@ -265,7 +281,16 @@ Attacking.prototype.HandleAttackEffects = function(attackType, attackData, targe
"damage": -(targetState.HPchange || 0),
"capture": targetState.captureChange || 0,
"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);
};
/**

View File

@ -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);
Engine.PostMessage(oldEnt, MT_EntityRenamed, { "entity": oldEnt, "newentity": newEnt });