Index: binaries/data/mods/public/globalscripts/DamageTypes.js =================================================================== --- /dev/null +++ binaries/data/mods/public/globalscripts/DamageTypes.js @@ -0,0 +1,66 @@ +/** + * This class provides a cache for accessing damage types metadata stored in JSON files. + * Note that damage types need not be defined in JSON files to be handled in-game. + * This class must be initialised before using, as initialising it directly in globalscripts would + * introduce disk I/O every time e.g. a GUI page is loaded. + */ +class DamageTypesMetadata +{ + constructor() + { + this.damageTypeData = {}; + this.tooltipDamageTypes = []; + + for (let filename of Engine.ListDirectoryFiles("simulation/data/template_helpers/damage_types", "*.json", false)) + { + let data = Engine.ReadJSONFile(filename); + if (!data) + continue; + + if (data.code in this.damageTypeData) + { + error("Encountered two damage types with the code " + data.name); + continue; + } + + this.damageTypeData[data.code] = data; + + if (data.showInTooltips) + this.tooltipDamageTypes.push(data.code); + } + + let hasMetadata = (a) => this.damageTypeData[a] ? -1 : 1; + this._sort = (a, b) => { + if (this.damageTypeData[a] && this.damageTypeData[b]) + return this.damageTypeData[a].order - this.damageTypeData[b].order; + return hasMetadata(a) - hasMetadata(b); + }; + this.tooltipDamageTypes.sort(this._sort); + } + + /** + * @returns a copy in sorted order. + */ + sort(damageTypes) + { + let sorted = damageTypes.slice(); + sorted.sort(this._sort); + return sorted; + } + + /** + * @returns the list of damage types to show in tooltips, ordered. + */ + getTooltipDamageTypes() + { + return this.tooltipDamageTypes; + } + + /** + * @returns the name of the @param code damage type, or @code if no metadata exists in JSON files. + */ + getName(code) + { + return this.damageTypeData[code] ? this.damageTypeData[code].name : code; + } +} Index: binaries/data/mods/public/globalscripts/ModificationTemplates.js =================================================================== --- binaries/data/mods/public/globalscripts/ModificationTemplates.js +++ binaries/data/mods/public/globalscripts/ModificationTemplates.js @@ -43,3 +43,156 @@ 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) +{ + 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 = +"" + + "" + + "" + + "tokens" + + "" + + "" + + "" + + "" + + "" + + "tokens" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + +""; + +const ModificationsSchema = +"" + + "" + + "" + + "" + + ModificationSchema + + "" + + "" + +""; + +/** + * 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 = {}; + template.Paths._string.split(/\s/).forEach(path => { + 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] = ret[path].concat(modification[path]); + } + } + return ret; +} Index: binaries/data/mods/public/globalscripts/StatusEffects.js =================================================================== --- /dev/null +++ binaries/data/mods/public/globalscripts/StatusEffects.js @@ -0,0 +1,49 @@ +/** + * This class provides a cache for accessing status effects metadata stored in JSON files. + * Note that status effects need not be defined in JSON files to be handled in-game. + * This class must be initialised before using, as initialising it directly in globalscripts would + * introduce disk I/O every time e.g. a GUI page is loaded. + */ +class StatusEffectsMetadata +{ + constructor() + { + this.statusEffectData = {}; + + for (let filename of Engine.ListDirectoryFiles("simulation/data/template_helpers/status_effects", "*.json", false)) + { + let data = Engine.ReadJSONFile(filename); + if (!data) + continue; + + if (data.code in this.statusEffectData) + { + error("Encountered two status effects with the code " + data.code); + continue; + } + + this.statusEffectData[data.code] = data; + } + + this._sort = (a, b) => this.damageTypeData[a].order - this.damageTypeData[b].order; + } + + /** + * @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. + */ + augment(code, templateData) + { + if (!this.statusEffectData[code]) + return templateData; + else if (!templateData && this.statusEffectData[code]) + return this.statusEffectData[code]; + else if (!this.statusEffectData[code] && !templateData) + { + error("Unknown status effect code " + code + " and no template data given."); + return {}; + } + + return Object.assign(this.statusEffectData[code], templateData); + } +} Index: binaries/data/mods/public/globalscripts/Technologies.js =================================================================== --- binaries/data/mods/public/globalscripts/Technologies.js +++ binaries/data/mods/public/globalscripts/Technologies.js @@ -39,74 +39,6 @@ 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 */ Index: binaries/data/mods/public/globalscripts/Templates.js =================================================================== --- binaries/data/mods/public/globalscripts/Templates.js +++ binaries/data/mods/public/globalscripts/Templates.js @@ -180,7 +180,9 @@ effects.Damage[damageType] = getEntityValue(path + "/Damage/" + damageType); } - // TODO: status effects + if (temp.GiveStatus) + effects.GiveStatus = temp.GiveStatus; + return effects; }; Index: binaries/data/mods/public/globalscripts/tests/test_damageTypes.js =================================================================== --- /dev/null +++ binaries/data/mods/public/globalscripts/tests/test_damageTypes.js @@ -0,0 +1,25 @@ +let damageTypes = { + "test_A": { + "code": "test_a", + "name": "A", + "order": 2 + }, + "test_B": { + "code": "test_b", + "name": "B", + "order": 1, + "showInTooltips": true + } +}; + +Engine.ListDirectoryFiles = () => Object.keys(damageTypes); +Engine.ReadJSONFile = (file) => damageTypes[file]; + +let dtm = new DamageTypesMetadata(); + +TS_ASSERT_EQUALS(dtm.getTooltipDamageTypes().length, 1); +TS_ASSERT_EQUALS(dtm.getName("test_a"), "A"); +TS_ASSERT_EQUALS(dtm.getName("test_b"), "B"); +TS_ASSERT_EQUALS(dtm.getName("test_c"), "test_c"); + +TS_ASSERT_UNEVAL_EQUALS(dtm.sort(["test_c", "test_a", "test_b"]), ["test_b", "test_a", "test_c"]); Index: binaries/data/mods/public/gui/common/tooltips.js =================================================================== --- binaries/data/mods/public/gui/common/tooltips.js +++ binaries/data/mods/public/gui/common/tooltips.js @@ -13,6 +13,9 @@ return g_ResourceData.GetCodes().concat(["population", "populationBonus", "time"]); } +var g_DamageTypesMetadata = new DamageTypesMetadata(); +var g_StatusEffectsMetadata = new StatusEffectsMetadata(); + /** * If true, always shows whether the splash damage deals friendly fire. * Otherwise display the friendly fire tooltip only if it does. @@ -257,10 +260,10 @@ if (!damageTemplate) return ""; - return Object.keys(damageTemplate).filter(dmgType => damageTemplate[dmgType]).map( + return g_DamageTypesMetadata.getTooltipDamageTypes().filter(dmgType => !!damageTemplate[dmgType]).map( dmgType => sprintf(translate("%(damage)s %(damageType)s"), { "damage": (+damageTemplate[dmgType]).toFixed(1), - "damageType": unitFont(translateWithContext("damage type", dmgType)) + "damageType": unitFont(translateWithContext("damage type", g_DamageTypesMetadata.getName(dmgType))) })).join(commaFont(translate(", "))); } @@ -281,7 +284,8 @@ return ""; return sprintf(translate("gives %(name)s"), { - "name": Object.keys(giveStatusTemplate).map(x => unitFont(translateWithContext("status effect", x))).join(', '), + "name": Object.keys(giveStatusTemplate).map(x => + unitFont(translateWithContext("status effect", g_StatusEffectsMetadata.augment(x, giveStatusTemplate[x]).Name))).join(', '), }); } @@ -371,19 +375,36 @@ return tooltips.join("\n"); } -function getStatusEffectsTooltip(name, template) +function getStatusEffectsTooltip(code, template) { + template = g_StatusEffectsMetadata.augment(code, template); + let durationString = ""; if (template.Duration) 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), + let intervalString = ""; + if (template.Interval) + intervalString = sprintf(translate(", %(interval)s"), { + "interval": attackRateDetails(+template.Interval) + }); + + let tooltipString = ""; + if (template.Tooltip) + tooltipString = translate(template.Tooltip); + + let attackEffectsString = ""; + if (template.Damage || template.Capture) + attackEffectsString = attackEffectsDetails(template); + + return sprintf(translate("%(statusName)s: %(tooltip)s%(effects)s%(rate)s%(durationString)s"), { + "statusName": headerFont(translateWithContext("status effect", template.Name)), + "tooltip": tooltipString, + "effects": attackEffectsString, + "rate": intervalString, "durationString": durationString }); } @@ -508,6 +529,9 @@ */ function getEntityCostComponentsTooltipString(template, entity, buildingsCountToTrainFullBatch = 1, fullBatchSize = 1, remainderBatch = 0) { + if (!template.cost) + return; + let totalCosts = multiplyEntityCosts(template, buildingsCountToTrainFullBatch * fullBatchSize + remainderBatch); if (template.cost.time) totalCosts.time = Math.ceil(template.cost.time * (entity ? Engine.GuiInterfaceCall("GetBatchTime", { Index: binaries/data/mods/public/l10n/messages.json =================================================================== --- binaries/data/mods/public/l10n/messages.json +++ binaries/data/mods/public/l10n/messages.json @@ -623,6 +623,32 @@ ] } }, + { + "extractor": "json", + "filemasks": [ + "simulation/data/template_helpers/damage_types/*.json" + ], + "options": { + "keywords": [ + "name", + "description" + ], + "context": "damage type" + } + }, + { + "extractor": "json", + "filemasks": [ + "simulation/data/template_helpers/status_effects/*.json" + ], + "options": { + "keywords": [ + "Name", + "Tooltip" + ], + "context": "status effect" + } + }, { "extractor": "json", "filemasks": [ Index: binaries/data/mods/public/simulation/components/StatusEffectsReceiver.js =================================================================== --- binaries/data/mods/public/simulation/components/StatusEffectsReceiver.js +++ binaries/data/mods/public/simulation/components/StatusEffectsReceiver.js @@ -1,16 +1,33 @@ function StatusEffectsReceiver() {} +/** + * 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. +/** + * 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.GiveStatus = function(effectData, attacker, attackerOwner, bonusMultiplier) { for (let effect in effectData) @@ -21,49 +38,100 @@ return { "inflictedStatuses": Object.keys(effectData) }; }; +/** + * 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) { 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; + + if (status.Modifiers) + { + 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 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 = 1000; + + 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); + 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) { - if (!this.activeStatusEffects[statusName]) + let statusEffect = this.activeStatusEffects[statusName]; + if (!statusEffect) return; - let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); - cmpTimer.CancelTimer(this.activeStatusEffects[statusName].Timer); + 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, INVALID_ENTITY, INVALID_PLAYER); + + 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); }; Index: binaries/data/mods/public/simulation/components/TechnologyManager.js =================================================================== --- binaries/data/mods/public/simulation/components/TechnologyManager.js +++ binaries/data/mods/public/simulation/components/TechnologyManager.js @@ -259,8 +259,19 @@ if (cmpPlayerEntityLimits) cmpPlayerEntityLimits.UpdateLimitsFromTech(tech); - // always send research finished message - Engine.PostMessage(this.entity, MT_ResearchFinished, {"player": playerID, "tech": tech}); + // Always send research finished message. + Engine.PostMessage(this.entity, MT_ResearchFinished, { "player": playerID, "tech": tech }); + + if (tech.startsWith("phase") && !template.autoResearch) + { + let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); + cmpGUIInterface.PushNotification({ + "type": "phase", + "players": [playerID], + "phaseName": tech, + "phaseState": "completed" + }); + } }; /** Index: binaries/data/mods/public/simulation/components/tests/test_Pack.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_Pack.js +++ binaries/data/mods/public/simulation/components/tests/test_Pack.js @@ -11,6 +11,7 @@ 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"); Index: binaries/data/mods/public/simulation/data/template_helpers/damage_types/crush.json =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/data/template_helpers/damage_types/crush.json @@ -0,0 +1,7 @@ +{ + "code": "Crush", + "name": "Crush", + "description": "Damage dealt from crushing, such as when ramming a gate.", + "order": 3, + "showInTooltips": true +} Index: binaries/data/mods/public/simulation/data/template_helpers/damage_types/hack.json =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/data/template_helpers/damage_types/hack.json @@ -0,0 +1,7 @@ +{ + "code": "Hack", + "name": "Hack", + "description": "Damage dealt from hacking, such as when fighting with swords.", + "order": 1, + "showInTooltips": true +} Index: binaries/data/mods/public/simulation/data/template_helpers/damage_types/pierce.json =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/data/template_helpers/damage_types/pierce.json @@ -0,0 +1,7 @@ +{ + "code": "Pierce", + "name": "Pierce", + "description": "Damage dealt from piercing, such as when being hit with an arrow.", + "order": 2, + "showInTooltips": true +} Index: binaries/data/mods/public/simulation/data/template_helpers/status_effects/AnotherBogus.json =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/data/template_helpers/status_effects/AnotherBogus.json @@ -0,0 +1,4 @@ +{ + "code": "AnotherBogus", + "Name": "AnotherBogus" +} Index: binaries/data/mods/public/simulation/helpers/Attacking.js =================================================================== --- binaries/data/mods/public/simulation/helpers/Attacking.js +++ binaries/data/mods/public/simulation/helpers/Attacking.js @@ -3,50 +3,72 @@ */ function Attacking() {} +const DirectEffectsSchema = + "" + + "" + + "" + + "" + + // Armour requires Foundation to not be a damage type. + "Foundation" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""; + +const StatusEffectsSchema = + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + DirectEffectsSchema + + "" + + "" + + "" + + "" + + "" + + ModificationsSchema + + "" + + "" + + "" + + "" + + ""; + /** * 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 + * @return {string} - RelaxNG schema string. */ -const DamageSchema = "" + - "" + - "" + - "" + - // Armour requires Foundation to not be a damage type. - "Foundation" + - "" + - "" + - "" + - ""; - Attacking.prototype.BuildAttackEffectsSchema = function() { return "" + "" + "" + - "" + - DamageSchema + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + DamageSchema + "" + - "" + - "" + - "" + - "" + + DirectEffectsSchema + + StatusEffectsSchema + "" + "" + "" + @@ -250,13 +272,16 @@ Object.assign(targetState, cmpReceiver[receiver.method](attackData[effectType], attacker, attackerOwner, bonusMultiplier)); } + if (targetState.killed) + this.TargetKilled(attacker, target, attackerOwner); + + if (attacker == INVALID_ENTITY) + return; + let cmpPromotion = Engine.QueryInterface(attacker, IID_Promotion); if (cmpPromotion && targetState.xp) cmpPromotion.IncreaseXp(targetState.xp); - if (targetState.killed) - this.TargetKilled(attacker, target, attackerOwner); - Engine.PostMessage(target, MT_Attacked, { "type": attackType, "target": target, Index: binaries/data/mods/public/simulation/helpers/Transform.js =================================================================== --- binaries/data/mods/public/simulation/helpers/Transform.js +++ binaries/data/mods/public/simulation/helpers/Transform.js @@ -109,6 +109,18 @@ } } + 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) + if (activeStatus[status].Duration) + activeStatus[status].Duration -= activeStatus[status]._timeElapsed; + + cmpNewStatusEffectsReceiver.GiveStatus(activeStatus); + } + TransferGarrisonedUnits(oldEnt, newEnt); Engine.PostMessage(oldEnt, MT_EntityRenamed, { "entity": oldEnt, "newentity": newEnt }); Index: binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged_archer.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged_archer.xml +++ binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged_archer.xml @@ -11,6 +11,47 @@ 6.0 0 + + + Bogus + Some bogus bonus tooltip describing the effect of the modifiers. This speeds up the entity but halves the health and resource gathering speed. + 15000 + + + UnitMotion/WalkSpeed + Unit + 20 + + + Health/Max ResourceGatherer/BaseSpeed + Unit Structure + 0.5 + + + + + Bogus-Test2 + 10000 + 1000 + + 1 + + + + Some bogus bonus tooltip describing the effect of the modifiers. This increases the armour permanently. + 1000 + + 1 + + + + Armour/Hack Armour/Pierce Armour/Crush + Unit + 20 + + + + 72.0 0.0 600 @@ -34,6 +75,11 @@ 5 + + + units/athen_ship_fishing + + 1.1