Index: ps/trunk/binaries/data/mods/public/maps/scripts/TriggerHelper.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/scripts/TriggerHelper.js +++ ps/trunk/binaries/data/mods/public/maps/scripts/TriggerHelper.js @@ -96,6 +96,21 @@ }; /** + * Creates a new unit taking into account possible 0 experience promotion. + * @param {number} owner Player id of the owner of the new units. + * @param {string} template Name of the template. + * @returns the created entity id. + */ +TriggerHelper.AddUpgradeTemplate = function(owner, 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.`); + + return Engine.AddEntity(upgradedTemplate); +}; + +/** * Can be used to "force" a building/unit to spawn a group of entities. * * @param source Entity id of the point where they will be spawned from @@ -121,7 +136,8 @@ for (let i = 0; i < count; ++i) { - let ent = Engine.AddEntity(template); + const ent = this.AddUpgradeTemplate(owner, template); + let cmpEntPosition = Engine.QueryInterface(ent, IID_Position); if (!cmpEntPosition) { @@ -178,7 +194,7 @@ for (let i = 0; i < count; ++i) { - let ent = Engine.AddEntity(template); + const ent = this.AddUpgradeTemplate(owner, template); let cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); if (cmpOwnership) @@ -219,7 +235,7 @@ for (let i = 0; i < count; ++i) { - let ent = Engine.AddEntity(template); + const ent = this.AddUpgradeTemplate(owner, template); let cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); if (cmpOwnership) Index: ps/trunk/binaries/data/mods/public/simulation/components/Trainer.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Trainer.js +++ ps/trunk/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: ps/trunk/binaries/data/mods/public/simulation/components/TurretHolder.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/TurretHolder.js +++ ps/trunk/binaries/data/mods/public/simulation/components/TurretHolder.js @@ -35,22 +35,21 @@ */ CreateSubunit(turretPointName) { - let turretPoint = this.TurretPointByName(turretPointName); + const turretPoint = this.TurretPointByName(turretPointName); if (!turretPoint || turretPoint.entity || this.initTurrets?.has(turretPointName) || this.reservedTurrets?.has(turretPointName)) return false; - let ent = Engine.AddEntity(turretPoint.template); + const cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); - let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); - if (cmpOwnership) - { - let cmpEntOwnership = Engine.QueryInterface(ent, IID_Ownership); - cmpEntOwnership?.SetOwner(cmpOwnership.GetOwner()); - } + const upgradedTemplate = GetUpgradedTemplate(cmpOwnership.GetOwner(), turretPoint.template); + const ent = Engine.AddEntity(upgradedTemplate); - let cmpTurretable = Engine.QueryInterface(ent, IID_Turretable); + const cmpEntOwnership = Engine.QueryInterface(ent, IID_Ownership); + cmpEntOwnership?.SetOwner(cmpOwnership.GetOwner()); + + const cmpTurretable = Engine.QueryInterface(ent, IID_Turretable); return cmpTurretable?.OccupyTurret(this.entity, turretPoint.name, turretPoint.ejectable) || Engine.DestroyEntity(ent); } @@ -389,6 +388,7 @@ this.EjectOrKill(this.GetEntities()); return; } + for (let point of this.turretPoints) { // If we were created, create any subunits now. Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Trainer.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Trainer.js +++ ps/trunk/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: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_TurretHolder.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_TurretHolder.js +++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_TurretHolder.js @@ -1,7 +1,9 @@ Engine.LoadHelperScript("Player.js"); Engine.LoadComponentScript("interfaces/TurretHolder.js"); +Engine.LoadComponentScript("interfaces/Turretable.js"); Engine.LoadComponentScript("interfaces/UnitAI.js"); Engine.LoadComponentScript("TurretHolder.js"); +Engine.LoadComponentScript("Turretable.js"); AddMock(SYSTEM_ENTITY, IID_PlayerManager, { "GetPlayerByID": id => id @@ -122,3 +124,100 @@ TS_ASSERT(cmpTurretHolder.LeaveTurretPoint(archerID)); TS_ASSERT(!cmpTurretHolder.LeaveTurretPoint(cavID, false, cmpTurretHolder.turretPoints[1])); TS_ASSERT(cmpTurretHolder.LeaveTurretPoint(cavID, false, cmpTurretHolder.turretPoints[2])); + +// Incremental Turret creation. +cmpTurretHolder = ConstructComponent(turretHolderID, "TurretHolder", { + "TurretPoints": { + "Turret": { + "X": "15.0", + "Y": "5.0", + "Z": "6.0", + "Template": "units/iber/cavalry_javelineer_c" + } + } +}); + +let spawned = 100; +Engine.AddEntity = function() { + ++spawned; + if(spawned > 101) + { + ConstructComponent(spawned, "Turretable", {}); + } + if(spawned > 102) + { + AddMock(spawned, IID_Ownership, { + "GetOwner": () => player, + "SetOwner": () => {} + }); + } + if(spawned > 103) + { + AddMock(spawned, IID_Position, { + "GetPosition": () => new Vector3D(4, 3, 25), + "GetRotation": () => new Vector3D(4, 0, 6), + "SetTurretParent": () => {}, + "IsInWorld": () => true + }); + } + return spawned; +} + +const GetUpgradedTemplate = (_, template) => template === "units/iber/cavalry_javelineer_b" ? "units/iber/cavalry_javelineer_a" : template; +Engine.RegisterGlobal("GetUpgradedTemplate", GetUpgradedTemplate); +cmpTurretHolder.OnOwnershipChanged({ + "to": 1, + "from": INVALID_PLAYER +}); +TS_ASSERT(!cmpTurretHolder.OccupiesTurretPoint(spawned)); +cmpTurretHolder.OnOwnershipChanged({ + "to": 1, + "from": INVALID_PLAYER +}); +TS_ASSERT(!cmpTurretHolder.OccupiesTurretPoint(spawned)); +cmpTurretHolder.OnOwnershipChanged({ + "to": 1, + "from": INVALID_PLAYER +}); +TS_ASSERT(!cmpTurretHolder.OccupiesTurretPoint(spawned)); +cmpTurretHolder.OnOwnershipChanged({ + "to": 1, + "from": INVALID_PLAYER +}); +TS_ASSERT(cmpTurretHolder.OccupiesTurretPoint(spawned)); + +// Normal turret creation. +Engine.AddEntity = function(t) { + ++spawned; + // Check that we're using the upgraded template. + TS_ASSERT(t, "units/iber/cavalry_javelineer_a"); + ConstructComponent(spawned, "Turretable", {}); + AddMock(spawned, IID_Ownership, { + "GetOwner": () => player, + "SetOwner": () => {} + }); + AddMock(spawned, IID_Position, { + "GetPosition": () => new Vector3D(4, 3, 25), + "GetRotation": () => new Vector3D(4, 0, 6), + "SetTurretParent": () => {}, + "IsInWorld": () => true + }); + return spawned; +} + +cmpTurretHolder = ConstructComponent(turretHolderID, "TurretHolder", { + "TurretPoints": { + "Turret": { + "X": "15.0", + "Y": "5.0", + "Z": "6.0", + "Template": "units/iber/cavalry_javelineer_b" + } + } +}); + +cmpTurretHolder.OnOwnershipChanged({ + "to": 1, + "from": INVALID_PLAYER +}); +TS_ASSERT(cmpTurretHolder.OccupiesTurretPoint(spawned)); Index: ps/trunk/binaries/data/mods/public/simulation/helpers/Transform.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/helpers/Transform.js +++ ps/trunk/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);