Index: ps/trunk/binaries/data/mods/public/globalscripts/DamageTypes.js =================================================================== --- ps/trunk/binaries/data/mods/public/globalscripts/DamageTypes.js +++ ps/trunk/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: ps/trunk/binaries/data/mods/public/globalscripts/StatusEffects.js =================================================================== --- ps/trunk/binaries/data/mods/public/globalscripts/StatusEffects.js +++ ps/trunk/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: ps/trunk/binaries/data/mods/public/globalscripts/tests/test_damageTypes.js =================================================================== --- ps/trunk/binaries/data/mods/public/globalscripts/tests/test_damageTypes.js +++ ps/trunk/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: ps/trunk/binaries/data/mods/public/globalscripts/tests/test_statusEffects.js =================================================================== --- ps/trunk/binaries/data/mods/public/globalscripts/tests/test_statusEffects.js +++ ps/trunk/binaries/data/mods/public/globalscripts/tests/test_statusEffects.js @@ -0,0 +1,31 @@ +let statusEffects = { + "test_A": { + "code": "test_a", + "StatusName": "A", + "StatusTooltip": "TTA" + }, + "test_B": { + "code": "test_b", + "StatusName": "B", + "StatusTooltip": "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", "StatusName": "A", "StatusTooltip": "TTA" +}); +TS_ASSERT_UNEVAL_EQUALS(sem.augment("test_b"), { + "code": "test_b", "StatusName": "B", "StatusTooltip": "TTB" +}); +TS_ASSERT_UNEVAL_EQUALS(sem.augment("test_a", { "StatusName": "test" }), { + "code": "test_a", "StatusName": "test", "StatusTooltip": "TTA" +}); +TS_ASSERT_UNEVAL_EQUALS(sem.augment("test_c", { "StatusName": "test" }), { + "StatusName": "test" +}); Index: ps/trunk/binaries/data/mods/public/gui/common/tooltips.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/common/tooltips.js +++ ps/trunk/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(", "))); } @@ -281,7 +284,10 @@ return ""; return sprintf(translate("gives %(name)s"), { - "name": Object.keys(applyStatusTemplate).map(x => unitFont(translateWithContext("status effect", applyStatusTemplate[x].Name))).join(commaFont(translate(", "))), + "name": Object.keys(applyStatusTemplate).map(x => { + let template = g_StatusEffectsMetadata.augment(x, applyStatusTemplate[x]); + return unitFont(translateWithContext("status effect", template.StatusName)); + }).join(commaFont(translate(", "))), }); } @@ -325,7 +331,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"), { @@ -375,10 +384,10 @@ { let tooltipAttributes = []; let tooltipString = ""; - if (template.Tooltip) + if (template.StatusTooltip) { tooltipAttributes.push("%(tooltip)s"); - tooltipString = translate(template.Tooltip); + tooltipString = translate(template.StatusTooltip); } let attackEffectsString = ""; @@ -408,7 +417,7 @@ } return sprintf(translate("%(statusName)s: " + tooltipAttributes.join(translate(commaFont(", ")))), { - "statusName": headerFont(translateWithContext("status effect", template.Name)), + "statusName": headerFont(translateWithContext("status effect", template.StatusName)), "tooltip": tooltipString, "effects": attackEffectsString, "rate": intervalString, Index: ps/trunk/binaries/data/mods/public/l10n/messages.json =================================================================== --- ps/trunk/binaries/data/mods/public/l10n/messages.json +++ ps/trunk/binaries/data/mods/public/l10n/messages.json @@ -400,6 +400,12 @@ ], "options": { "keywords": { + "StatusName": { + "customContext": "status effect" + }, + "StatusTooltip": { + "customContext": "status effect" + }, "GenericName": {}, "SpecificName": {}, "History": {}, @@ -432,6 +438,12 @@ ], "options": { "keywords": { + "StatusName": { + "customContext": "status effect" + }, + "StatusTooltip": { + "customContext": "status effect" + }, "GenericName": {}, "SpecificName": {}, "History": {}, @@ -471,6 +483,12 @@ }, "options": { "keywords": { + "StatusName": { + "customContext": "status effect" + }, + "StatusTooltip": { + "customContext": "status effect" + }, "GenericName": {}, "SpecificName": {}, "History": {}, @@ -486,6 +504,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": [ + "StatusName", + "StatusTooltip" + ], + "context": "status effect" + } } ] }, Index: ps/trunk/binaries/data/mods/public/simulation/data/template_helpers/damage_types/crush.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/template_helpers/damage_types/crush.json +++ ps/trunk/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: ps/trunk/binaries/data/mods/public/simulation/data/template_helpers/damage_types/hack.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/template_helpers/damage_types/hack.json +++ ps/trunk/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: ps/trunk/binaries/data/mods/public/simulation/data/template_helpers/damage_types/pierce.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/template_helpers/damage_types/pierce.json +++ ps/trunk/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: ps/trunk/binaries/data/mods/public/simulation/helpers/Attacking.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/helpers/Attacking.js +++ ps/trunk/binaries/data/mods/public/simulation/helpers/Attacking.js @@ -25,12 +25,14 @@ "" + "" + "" + - "" + + "" + + "" + + "" + "" + "" + "" + "" + - "" + + "" + "" + "" + "" + Index: ps/trunk/source/tools/i18n/extractors/extractors.py =================================================================== --- ps/trunk/source/tools/i18n/extractors/extractors.py +++ ps/trunk/source/tools/i18n/extractors/extractors.py @@ -428,10 +428,12 @@ if "locationAttributes" in self.keywords[keyword]: attributes = [element.get(attribute) for attribute in self.keywords[keyword]["locationAttributes"] if attribute in element.attrib] breadcrumb = "({attributes})".format(attributes=", ".join(attributes)) - if "tagAsContext" in self.keywords[keyword]: - context = keyword if "context" in element.attrib: context = unicode(element.get("context")) + elif "tagAsContext" in self.keywords[keyword]: + context = keyword + elif "customContext" in self.keywords[keyword]: + context = self.keywords[keyword]["customContext"] if "comment" in element.attrib: comment = element.get("comment") comment = u" ".join(comment.split()) # Remove tabs, line breaks and unecessary spaces.