Index: binaries/data/mods/public/globalscripts/Technologies.js =================================================================== --- binaries/data/mods/public/globalscripts/Technologies.js +++ binaries/data/mods/public/globalscripts/Technologies.js @@ -12,7 +12,7 @@ * @param currentTechModifications array of modificiations * @param classes Array containing the class list of the template. * @param originalValue Number storing the original value. Can also be - * non-numberic, but then only "replace" techs can be supported. + * non-numberic, but then only "replace" and "tokens" techs can be supported. */ function GetTechModifiedProperty(modifications, classes, originalValue) { @@ -25,6 +25,8 @@ continue; if (modification.replace !== undefined) return modification.replace; + if (modification.tokens !== undefined) + return HandleTokens(originalValue, modification.tokens); if (modification.multiply) multiply *= modification.multiply; else if (modification.add) @@ -47,6 +49,37 @@ return MatchesClassList(classes, modification.affects); } +/** + * Returns a modified list of tokens. + * Supports "A>B" to replace A by B, "-A" to remove A, and the rest will add tokens. + */ +function HandleTokens(originalValue, modification) +{ + let tokens = originalValue.split(/\s+/); + let newTokens = modification.split(/\s+/); + + for (let token of newTokens) + { + if (token.indexOf(">") !== -1) + { + let [oldToken, newToken] = token.split(">"); + let index = tokens.indexOf(oldToken); + if (index !== -1) + tokens[index] = newToken; + } + else if (token[0] == "-") + { + let index = tokens.indexOf(token.substr(1)); + if (index !== -1) + tokens.splice(index, 1); + } + else + tokens.push(token); + } + + return tokens.join(" "); +} + /** * Derives the technology requirements from a given technology template. * Takes into account the `supersedes` attribute. Index: binaries/data/mods/public/gui/session/messages.js =================================================================== --- binaries/data/mods/public/gui/session/messages.js +++ binaries/data/mods/public/gui/session/messages.js @@ -214,12 +214,6 @@ if (player == Engine.GetPlayerID()) openDialog(notification.dialogName, notification.data, player); }, - "resetselectionpannel": function(notification, player) - { - if (player != Engine.GetPlayerID()) - return; - g_Selection.rebuildSelection({}); - }, "playercommand": function(notification, player) { // For observers, focus the camera on units commanded by the selected player Index: binaries/data/mods/public/gui/session/session.js =================================================================== --- binaries/data/mods/public/gui/session/session.js +++ binaries/data/mods/public/gui/session/session.js @@ -620,6 +620,13 @@ } g_SimState = undefined; + // Some changes may require re-rendering the selection. + if (Engine.GuiInterfaceCall("IsSelectionDirty")) + { + g_Selection.onChange(); + Engine.GuiInterfaceCall("ResetSelectionDirty"); + } + if (!GetSimState()) return; Index: binaries/data/mods/public/simulation/ai/common-api/entity.js =================================================================== --- binaries/data/mods/public/simulation/ai/common-api/entity.js +++ binaries/data/mods/public/simulation/ai/common-api/entity.js @@ -15,8 +15,7 @@ this._tpCache = new Map(); }, - // helper function to return a template value, optionally adjusting for tech. - // TODO: there's no support for "_string" values here. + // Helper function to return a template value, adjusting for tech. "get": function(string) { let value = this._template; Index: binaries/data/mods/public/simulation/components/AIInterface.js =================================================================== --- binaries/data/mods/public/simulation/components/AIInterface.js +++ binaries/data/mods/public/simulation/components/AIInterface.js @@ -251,7 +251,7 @@ if (!ended) continue; // item now contains the template value for this. - let oldValue = +item; + let oldValue = +item == item ? +item : item; let newValue = ApplyValueModificationsToTemplate(valName, oldValue, msg.player, template); // Apply the same roundings as in the components if (valName === "Player/MaxPopulation" || valName === "Cost/Population" || @@ -299,7 +299,7 @@ if (!ended) continue; // "item" now contains the unmodified template value for this. - let oldValue = +item; + let oldValue = +item == item ? +item : item; let newValue = ApplyValueModificationsToEntity(valName, oldValue, ent); // Apply the same roundings as in the components if (valName === "Player/MaxPopulation" || valName === "Cost/Population" || Index: binaries/data/mods/public/simulation/components/Builder.js =================================================================== --- binaries/data/mods/public/simulation/components/Builder.js +++ binaries/data/mods/public/simulation/components/Builder.js @@ -30,6 +30,8 @@ if (!string) return []; + string = ApplyValueModificationsToEntity("Builder/Entities/_string", string, this.entity); + let cmpPlayer = QueryOwnerInterface(this.entity); if (!cmpPlayer) return []; @@ -85,4 +87,15 @@ } }; +Builder.prototype.OnValueModification = function(msg) +{ + if (msg.component != "Builder" || !msg.valueNames.some(name => name.endsWith('_string'))) + return; + + // Token changes may require selection updates. + let cmpPlayer = QueryOwnerInterface(this.entity, IID_Player); + if (cmpPlayer) + Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).SetSelectionDirty(cmpPlayer.GetPlayerID()); +}; + Engine.RegisterComponentType(IID_Builder, "Builder", Builder); Index: binaries/data/mods/public/simulation/components/GuiInterface.js =================================================================== --- binaries/data/mods/public/simulation/components/GuiInterface.js +++ binaries/data/mods/public/simulation/components/GuiInterface.js @@ -35,6 +35,7 @@ this.entsWithAuraAndStatusBars = new Set(); this.enabledVisualRangeOverlayTypes = {}; this.templateModified = {}; + this.selectionDirty = {}; this.obstructionSnap = new ObstructionSnap(); }; @@ -659,6 +660,7 @@ GuiInterface.prototype.OnTemplateModification = function(msg) { this.templateModified[msg.player] = true; + this.selectionDirty[msg.player] = true; }; GuiInterface.prototype.IsTemplateModified = function(player) @@ -671,6 +673,35 @@ this.templateModified = {}; }; +/** + * Some changes may require an update to the selection panel, + * which is cached for efficiency. Inform the GUI it needs reloading. + */ +GuiInterface.prototype.OnDisabledTemplatesChanged = function(msg) +{ + this.selectionDirty[msg.player] = true; +}; + +GuiInterface.prototype.OnDisabledTechnologiesChanged = function(msg) +{ + this.selectionDirty[msg.player] = true; +}; + +GuiInterface.prototype.SetSelectionDirty = function(player) +{ + this.selectionDirty[player] = true; +}; + +GuiInterface.prototype.IsSelectionDirty = function(player) +{ + return this.selectionDirty[player] || false; +}; + +GuiInterface.prototype.ResetSelectionDirty = function() +{ + this.selectionDirty = {}; +}; + /** * Add a timed notification. * Warning: timed notifacations are serialised @@ -1991,7 +2022,9 @@ "GetTraderNumber": 1, "GetTradingGoods": 1, "IsTemplateModified": 1, - "ResetTemplateModified": 1 + "ResetTemplateModified": 1, + "IsSelectionDirty": 1, + "ResetSelectionDirty": 1 }; GuiInterface.prototype.ScriptCall = function(player, name, args) Index: binaries/data/mods/public/simulation/components/Player.js =================================================================== --- binaries/data/mods/public/simulation/components/Player.js +++ binaries/data/mods/public/simulation/components/Player.js @@ -863,23 +863,12 @@ { this.disabledTemplates[template] = true; Engine.BroadcastMessage(MT_DisabledTemplatesChanged, {}); - var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); - cmpGuiInterface.PushNotification({ - "type": "resetselectionpannel", - "players": [this.GetPlayerID()] - }); }; Player.prototype.RemoveDisabledTemplate = function(template) { this.disabledTemplates[template] = false; Engine.BroadcastMessage(MT_DisabledTemplatesChanged, {}); - - var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); - cmpGuiInterface.PushNotification({ - "type": "resetselectionpannel", - "players": [this.GetPlayerID()] - }); }; Player.prototype.SetDisabledTemplates = function(templates) @@ -888,12 +877,6 @@ for (let template of templates) this.disabledTemplates[template] = true; Engine.BroadcastMessage(MT_DisabledTemplatesChanged, {}); - - var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); - cmpGuiInterface.PushNotification({ - "type": "resetselectionpannel", - "players": [this.GetPlayerID()] - }); }; Player.prototype.GetDisabledTemplates = function() Index: binaries/data/mods/public/simulation/components/ProductionQueue.js =================================================================== --- binaries/data/mods/public/simulation/components/ProductionQueue.js +++ binaries/data/mods/public/simulation/components/ProductionQueue.js @@ -88,6 +88,8 @@ if (!string) return; + string = ApplyValueModificationsToEntity("ProductionQueue/Entities/_string", string, this.entity); + // Replace the "{civ}" and "{native}" codes with the owner's civ ID and entity's civ ID. let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); let cmpPlayer = QueryOwnerInterface(this.entity); @@ -140,6 +142,8 @@ return []; let string = this.template.Technologies._string; + string = ApplyValueModificationsToEntity("ProductionQueue/Technologies/_string", string, this.entity); + if (!string) return []; @@ -866,8 +870,17 @@ // If the promotion requirements of units is changed, // update the entities list so that automatically promoted units are shown // appropriately in the list. - if (msg.component == "Promotion") - this.CalculateEntitiesList(); + if (msg.component != "Promotion" && msg.component != "ProductionQueue") + return; + + this.CalculateEntitiesList(); + // Token changes may require selection updates. + if (!msg.valueNames.some(name => name.endsWith('_string'))) + return; + + let cmpPlayer = QueryOwnerInterface(this.entity, IID_Player); + if (cmpPlayer) + Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).SetSelectionDirty(cmpPlayer.GetPlayerID()); }; ProductionQueue.prototype.OnDisabledTemplatesChanged = function(msg) Index: binaries/data/mods/public/simulation/data/auras/units/heroes/brit_hero_cunobelin.json =================================================================== --- binaries/data/mods/public/simulation/data/auras/units/heroes/brit_hero_cunobelin.json +++ binaries/data/mods/public/simulation/data/auras/units/heroes/brit_hero_cunobelin.json @@ -3,7 +3,11 @@ "radius": 30, "affects": ["Human"], "modifications": [ - { "value": "Health/RegenRate", "add": 0.8 } + { "value": "Health/RegenRate", "add": 0.8 }, + { + "value": "Builder/Entities/_string", + "tokens": "structures/stonehenge" + } ], "auraName": "Britannorum Rex", "auraDescription": "Humans +0.8 health regeneration rate.", Index: binaries/data/mods/public/simulation/data/technologies/successors/upgrade_mace_silvershields.json =================================================================== --- binaries/data/mods/public/simulation/data/technologies/successors/upgrade_mace_silvershields.json +++ binaries/data/mods/public/simulation/data/technologies/successors/upgrade_mace_silvershields.json @@ -8,8 +8,11 @@ "researchTime": 40, "tooltip": "Upgrade Shield Bearer Champion Infantry to Silver Shields, with greater health and armor.", "modifications": [ - { "value": "Promotion/RequiredXp", "replace": 0 } + { + "value": "ProductionQueue/Entities/_string", + "tokens": "units/{civ}_champion_infantry_a_barracks>units/{civ}_champion_infantry_e_barracks units/{civ}_champion_infantry_a>units/{civ}_champion_infantry_e" + } ], - "affects": ["Champion Infantry"], + "affects": ["Structure"], "soundComplete": "interface/alarm/alarm_upgradearmory.xml" } Index: binaries/data/mods/public/simulation/data/technologies/tech_demo.json =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/data/technologies/tech_demo.json @@ -0,0 +1,25 @@ +{ + "genericName": "Tech Demo", + "description": "", + "cost": { "food": 0, "wood": 0, "stone": 0, "metal": 0 }, + "icon": "farming_training.png", + "researchTime": 0, + "tooltip": "", + "modifications": [ + { + "value": "ProductionQueue/Entities/_string", + "tokens": "units/{civ}_support_female_citizen>units/{civ}_infantry_spearman_b" + }, + { + "value": "ProductionQueue/Technologies/_string", + "tokens": "tower_armour" + }, + { + "value": "Builder/Entities/_string", + "tokens": "structures/stonehenge -structures/{civ}_house" + } + + ], + "affects": [], + "soundComplete": "interface/alarm/alarm_upgradearmory.xml" +} Index: binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml +++ binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml @@ -110,6 +110,7 @@ phase_city_{civ} unlock_spies spy_counter + tech_demo