Index: binaries/data/mods/public/maps/scripts/TriggerHelper.js =================================================================== --- binaries/data/mods/public/maps/scripts/TriggerHelper.js +++ binaries/data/mods/public/maps/scripts/TriggerHelper.js @@ -121,7 +121,11 @@ for (let i = 0; i < count; ++i) { - let ent = Engine.AddEntity(template); + const upgradedTemplate = GetUpgradedTemplate(owner, template); + if (upgradedTemplate !== template) + warn(`tried to spawn template '${template}' but upgraded template ${upgradedTemplate} will be spawned instead. You might want to create a template that is not affected by this promotion.`); + + let ent = Engine.AddEntity(upgradedTemplate); let cmpEntPosition = Engine.QueryInterface(ent, IID_Position); if (!cmpEntPosition) { Index: binaries/data/mods/public/simulation/components/EntRenameWatcher.js =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/components/EntRenameWatcher.js @@ -0,0 +1,40 @@ +function EntRenameWatcher() {} + +EntRenameWatcher.prototype.Schema = ""; + +EntRenameWatcher.prototype.Init = function() +{ + this.renames = new Map(); +} + +/** + * Return the current ID (after renames) of the entity ent + * @param ent - original Entity ID + */ +EntRenameWatcher.prototype.GetCurrentId = function(ent) +{ + let cid = this.renames.get(ent); + if (!cid) + return ent; + // Check if this renamed ID is itself renamed, + // if so update the original array (faster check next time) + let nid = this.renames.get(cid); + if (!nid) + return cid; + cid = nid; + while (true) { + let nid = this.renames.get(cid); + if (!nid) + break; + cid = nid; + } + this.renames.set(ent, cid); + return cid; +}; + +EntRenameWatcher.prototype.OnGlobalEntityRenamed = function(msg) +{ + this.renames.set(msg.entity, msg.newentity); +}; + +Engine.RegisterSystemComponentType(IID_EntRenameWatcher, "EntRenameWatcher", EntRenameWatcher); Index: binaries/data/mods/public/simulation/components/Trainer.js =================================================================== --- binaries/data/mods/public/simulation/components/Trainer.js +++ binaries/data/mods/public/simulation/components/Trainer.js @@ -554,7 +554,7 @@ return entMap; } - token = this.GetUpgradedTemplate(token); + token = GetUpgradedTemplate(cmpPlayer.GetPlayerID(), token); entMap.set(rawToken, token); updateAllQueuedTemplate(rawToken, token); return entMap; @@ -563,32 +563,6 @@ this.CalculateTrainCostMultiplier(); }; -/* - * Returns the upgraded template name if necessary. - */ -Trainer.prototype.GetUpgradedTemplate = function(templateName) -{ - const cmpPlayer = QueryOwnerInterface(this.entity); - if (!cmpPlayer) - return templateName; - - const cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); - let template = cmpTemplateManager.GetTemplate(templateName); - while (template && template.Promotion !== undefined) - { - const requiredXp = ApplyValueModificationsToTemplate( - "Promotion/RequiredXp", - +template.Promotion.RequiredXp, - cmpPlayer.GetPlayerID(), - template); - if (requiredXp > 0) - break; - templateName = template.Promotion.Entity; - template = cmpTemplateManager.GetTemplate(templateName); - } - return templateName; -}; - Trainer.prototype.CalculateTrainCostMultiplier = function() { for (const res of Resources.GetCodes().concat(["time"])) Index: binaries/data/mods/public/simulation/components/interfaces/EntRenameWatcher.js =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/components/interfaces/EntRenameWatcher.js @@ -0,0 +1 @@ +Engine.RegisterInterface("EntRenameWatcher"); Index: binaries/data/mods/public/simulation/components/interfaces/Messages.js =================================================================== --- binaries/data/mods/public/simulation/components/interfaces/Messages.js +++ binaries/data/mods/public/simulation/components/interfaces/Messages.js @@ -1,16 +1,3 @@ -/** - * Message of the form { "entity": number, "newentity": number } - * sent when one entity is changed to another: - * - from Foundation component when a building construction is done - * - from Formation component - * - from Health component when an entity died and should remain as a resource - * - from Promotion component when a unit is promoted - * - from Mirage component when a fogged entity is re-discovered - * - from SkirmishReplacer component when a skirmish entity has been replaced - * - from Transform helper when an entity has been upgraded - */ -Engine.RegisterMessageType("EntityRenamed"); - /** * Message of the form {} * sent from InitGame for component map-dependent initialization. Index: binaries/data/mods/public/simulation/components/tests/test_EntRenameWatcher.js =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/components/tests/test_EntRenameWatcher.js @@ -0,0 +1,54 @@ +Engine.LoadComponentScript("interfaces/EntRenameWatcher.js"); +Engine.LoadComponentScript("EntRenameWatcher.js"); + + +// First test, regular order + +let cmpRenameWatcher = ConstructComponent(SYSTEM_ENTITY, "EntRenameWatcher"); + +const entityA = 3476; +const entityB = 857; +const entityC = 7876; +const invalid = INVALID_ENTITY; + +TS_ASSERT_EQUALS(cmpRenameWatcher.GetCurrentId(entityA), entityA); +TS_ASSERT_EQUALS(cmpRenameWatcher.GetCurrentId(invalid), invalid); + +cmpRenameWatcher.OnGlobalEntityRenamed({ + "entity": entityA, + "newentity": entityB, +}); + +TS_ASSERT_EQUALS(cmpRenameWatcher.GetCurrentId(entityA), entityB); +TS_ASSERT_EQUALS(cmpRenameWatcher.GetCurrentId(entityB), entityB); + +cmpRenameWatcher.OnGlobalEntityRenamed({ + "entity": entityB, + "newentity": entityC, +}); + +TS_ASSERT_EQUALS(cmpRenameWatcher.GetCurrentId(entityA), entityC); +TS_ASSERT_EQUALS(cmpRenameWatcher.GetCurrentId(entityB), entityC); + + +// First test, inverse order + +cmpRenameWatcher = ConstructComponent(SYSTEM_ENTITY, "EntRenameWatcher"); + +TS_ASSERT_EQUALS(cmpRenameWatcher.GetCurrentId(entityA), entityA); + +cmpRenameWatcher.OnGlobalEntityRenamed({ + "entity": entityB, + "newentity": entityC, +}); + +TS_ASSERT_EQUALS(cmpRenameWatcher.GetCurrentId(entityA), entityA); +TS_ASSERT_EQUALS(cmpRenameWatcher.GetCurrentId(entityB), entityC); + +cmpRenameWatcher.OnGlobalEntityRenamed({ + "entity": entityA, + "newentity": entityB, +}); + +TS_ASSERT_EQUALS(cmpRenameWatcher.GetCurrentId(entityA), entityC); +TS_ASSERT_EQUALS(cmpRenameWatcher.GetCurrentId(entityB), entityC); Index: binaries/data/mods/public/simulation/components/tests/test_Trainer.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_Trainer.js +++ binaries/data/mods/public/simulation/components/tests/test_Trainer.js @@ -55,6 +55,16 @@ "GetCiv": () => "iber" }); +let GetUpgradedTemplate = (_, template) => template === "units/iber/cavalry_javelineer_b" ? "units/iber/cavalry_javelineer_a" : template; +Engine.RegisterGlobal("GetUpgradedTemplate", GetUpgradedTemplate); +cmpTrainer.CalculateEntitiesMap(); +TS_ASSERT_UNEVAL_EQUALS( + cmpTrainer.GetEntitiesList(), + ["units/iber/cavalry_javelineer_a", "units/iber/infantry_swordsman_b", "units/iber/support_female_citizen"] +); + +GetUpgradedTemplate = (_, template) => template; +Engine.RegisterGlobal("GetUpgradedTemplate", GetUpgradedTemplate); cmpTrainer.CalculateEntitiesMap(); TS_ASSERT_UNEVAL_EQUALS( cmpTrainer.GetEntitiesList(), 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 @@ -294,5 +294,30 @@ } } +/** + * @param {number} playerId - the player id to check technologies for. + * @param {string} templateName - the original template name. + * @returns the upgraded template name if it exists else the original template. + */ +function GetUpgradedTemplate(playerId, templateName) +{ + const cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); + let template = cmpTemplateManager.GetTemplate(templateName); + while (template && template.Promotion !== undefined) + { + const requiredXp = ApplyValueModificationsToTemplate( + "Promotion/RequiredXp", + +template.Promotion.RequiredXp, + playerId, + template); + if (requiredXp > 0) + break; + templateName = template.Promotion.Entity; + template = cmpTemplateManager.GetTemplate(templateName); + } + return templateName; +}; + +Engine.RegisterGlobal("GetUpgradedTemplate", GetUpgradedTemplate); Engine.RegisterGlobal("ChangeEntityTemplate", ChangeEntityTemplate); Engine.RegisterGlobal("ObstructionsBlockingTemplateChange", ObstructionsBlockingTemplateChange); Index: binaries/data/mods/public/simulation/helpers/tests/test_Transform.js =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/helpers/tests/test_Transform.js @@ -0,0 +1,41 @@ +Engine.LoadHelperScript("Transform.js"); + +const template1 = "template_a"; +const template2 = "template_b"; +const template3 = "template_c"; +const template4 = "template_d"; +const affectedClasses = "CitizenSoldier"; +const playerId1 = 1; +const playerId2 = 2; + +AddMock(SYSTEM_ENTITY, IID_TemplateManager, { + "GetTemplate": function (template) { + if (template === template1 || template === template4) + return {}; + + return { + "Identity": { + "Classes": { "_string": template === template3 ? "" : affectedClasses } + }, + "Promotion": { + "Entity": template4, + "RequiredXp": "1000" + } + }; + }, +}); + +let applyValueModificationsToTemplate = function(_, current_value, playerID, template) +{ + return playerID === playerId1 && template.Identity.Classes._string === affectedClasses ? 0 : current_value; +} + +Engine.RegisterGlobal("ApplyValueModificationsToTemplate", applyValueModificationsToTemplate); + +TS_ASSERT_EQUALS(GetUpgradedTemplate(playerId1, template1), template1); +TS_ASSERT_EQUALS(GetUpgradedTemplate(playerId1, template2), template4); +TS_ASSERT_EQUALS(GetUpgradedTemplate(playerId1, template3), template3); + +TS_ASSERT_EQUALS(GetUpgradedTemplate(playerId2, template1), template1); +TS_ASSERT_EQUALS(GetUpgradedTemplate(playerId2, template2), template2); +TS_ASSERT_EQUALS(GetUpgradedTemplate(playerId2, template3), template3);