Index: binaries/data/mods/public/globalscripts/DamageTypes.js =================================================================== --- /dev/null +++ binaries/data/mods/public/globalscripts/DamageTypes.js @@ -0,0 +1,57 @@ +/** + * 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 is intended to simplify modding) + * 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 = {}; + + let files = Engine.ListDirectoryFiles("simulation/data/template_helpers/damage_types", "*.json", false); + for (let filename of files) + { + 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; + } + + 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); + }; + } + + /** + * @param {String[]} damageTypes - The damageTypes to sort. + * @returns {String[]} - The damageTypes in sorted order; first the ones + * where metadata is provided, then the rest. + */ + sort(damageTypes) + { + let sorted = damageTypes.slice(); + sorted.sort(this._sort); + return sorted; + } + + /** + * @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/StatusEffects.js =================================================================== --- /dev/null +++ binaries/data/mods/public/globalscripts/StatusEffects.js @@ -0,0 +1,44 @@ +/** + * 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 = {}; + + let files = Engine.ListDirectoryFiles("simulation/data/template_helpers/status_effects", "*.json", false); + for (let filename of files) + { + 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; + } + } + + /** + * @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 (!templateData && this.statusEffectData[code]) + return this.statusEffectData[code]; + + if (this.statusEffectData[code]) + return Object.assign({}, this.statusEffectData[code], templateData); + + return templateData; + } +} Index: binaries/data/mods/public/globalscripts/tests/test_damageTypes.js =================================================================== --- /dev/null +++ binaries/data/mods/public/globalscripts/tests/test_damageTypes.js @@ -0,0 +1,23 @@ +let damageTypes = { + "test_A": { + "code": "test_a", + "name": "A", + "order": 2 + }, + "test_B": { + "code": "test_b", + "name": "B", + "order": 1 + } +}; + +Engine.ListDirectoryFiles = () => Object.keys(damageTypes); +Engine.ReadJSONFile = (file) => damageTypes[file]; + +let dtm = new DamageTypesMetadata(); + +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/globalscripts/tests/test_statusEffects.js =================================================================== --- /dev/null +++ binaries/data/mods/public/globalscripts/tests/test_statusEffects.js @@ -0,0 +1,23 @@ +let statusEffects = { + "test_A": { + "code": "test_a", + "Name": "A", + "Tooltip": "TTA" + }, + "test_B": { + "code": "test_b", + "Name": "B", + "Tooltip": "TTB" + } +}; + +Engine.ListDirectoryFiles = () => Object.keys(statusEffects); +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", "Name": "A", "Tooltip": "TTA" }); +TS_ASSERT_UNEVAL_EQUALS(sem.augment("test_b"), { "code": "test_b", "Name": "B", "Tooltip": "TTB" }); +TS_ASSERT_UNEVAL_EQUALS(sem.augment("test_a", { "Name": "test" }), { "code": "test_a", "Name": "test", "Tooltip": "TTA" }); +TS_ASSERT_UNEVAL_EQUALS(sem.augment("test_c", { "Name": "test" }), { "Name": "test" }); 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. @@ -169,10 +172,10 @@ return sprintf(translate("%(label)s %(details)s"), { "label": headerFont(translate("Armor:")), "details": - Object.keys(template.armour).map( + g_DamageTypesMetadata.sort(Object.keys(template.armour)).map( dmgType => sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), { "damage": template.armour[dmgType].toFixed(1), - "damageType": unitFont(translateWithContext("damage type", dmgType)), + "damageType": unitFont(translateWithContext("damage type", g_DamageTypesMetadata.getName(dmgType))), "armorPercentage": '[font="sans-10"]' + sprintf(translate("(%(armorPercentage)s)"), { @@ -257,10 +260,10 @@ if (!damageTemplate) return ""; - return Object.keys(damageTemplate).filter(dmgType => damageTemplate[dmgType]).map( + return g_DamageTypesMetadata.sort(Object.keys(damageTemplate).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(", "))); } @@ -325,7 +328,10 @@ let statusEffectsDetails = []; if (attackTypeTemplate.ApplyStatus) for (let status in attackTypeTemplate.ApplyStatus) - statusEffectsDetails.push("\n " + getStatusEffectsTooltip(attackTypeTemplate.ApplyStatus[status])); + { + let status_template = g_StatusEffectsMetadata.augment(status, attackTypeTemplate.ApplyStatus[status]); + statusEffectsDetails.push("\n " + getStatusEffectsTooltip(status_template)); + } statusEffectsDetails = statusEffectsDetails.join(""); tooltips.push(sprintf(translate("%(attackLabel)s: %(effects)s, %(range)s, %(rate)s%(statusEffects)s"), { Index: binaries/data/mods/public/l10n/messages.json =================================================================== --- binaries/data/mods/public/l10n/messages.json +++ binaries/data/mods/public/l10n/messages.json @@ -486,6 +486,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" + } } ] }, 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,6 @@ +{ + "code": "Crush", + "name": "Crush", + "description": "Damage caused by sheer force, like with a club or by trampling.", + "order": 3 +} 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,6 @@ +{ + "code": "Hack", + "name": "Hack", + "description": "Damage caused by sharp objects cutting or chopping, like with a sword or an axe.", + "order": 1 +} 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,6 @@ +{ + "code": "Pierce", + "name": "Pierce", + "description": "Damage caused by sharp pointy objects, like arrows or spears.", + "order": 2 +} Index: binaries/data/mods/public/simulation/data/template_helpers/status_effects/fire.json =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/data/template_helpers/status_effects/fire.json @@ -0,0 +1,5 @@ +{ + "code": "Fire", + "Name": "Fire", + "Icon": "default.png" +} Index: binaries/data/mods/public/simulation/templates/units/theb_siege_fireraiser.xml =================================================================== --- binaries/data/mods/public/simulation/templates/units/theb_siege_fireraiser.xml +++ binaries/data/mods/public/simulation/templates/units/theb_siege_fireraiser.xml @@ -3,10 +3,19 @@ - 50.0 + 45.0 0.0 - 50.0 + 45.0 + + + 10000 + 1000 + + 10.0 + + + 12 8.0 2000