From 9001edf4fa09a3e762562a82f6041786e6847df1 Mon Sep 17 00:00:00 2001 From: Stan Date: Wed, 10 May 2023 15:13:52 +0000 Subject: [PATCH] Workaround TriggerHelper.js not being able to spawn units correctly because of promotion. Avoid duplication by moving the function to Transform.js. Add tests to Transform.js Add tests to TurretHolder.js Optimize slightly Trainer.js by removing some useless Engine.QueryInterface calls. Refs #6784 (symptoms in TriggerHelper.js are fixed underlying cause not) `SkirmishReplacer` is called before the Modifier Manager so it does not suffer the same fate. Entities using `ChangeEntityTemplate` function are still affected. Maps calling Engine.AddEntity directly are still affected. Ideally this should be handled by hotloading components instead of creating new entities each time. See => https://code.wildfiregames.com/D4991 Tested using: https://github.com/0ADMods/trailer_tools/commit/908dd631d950b5050d1784530c65f29ccfc67913 Differential Revision: https://code.wildfiregames.com/D4984 git-svn-id: https://svn.wildfiregames.com/public/ps/trunk@27636 3db68df2-c116-0410-a063-a993310a9797 --- .../data/mods/public/maps/scripts/TriggerHelper.js | 22 ++++- .../mods/public/simulation/components/Trainer.js | 28 +----- .../public/simulation/components/TurretHolder.js | 18 ++-- .../simulation/components/tests/test_Trainer.js | 10 +++ .../components/tests/test_TurretHolder.js | 99 ++++++++++++++++++++++ .../mods/public/simulation/helpers/Transform.js | 25 ++++++ 6 files changed, 163 insertions(+), 39 deletions(-) diff --git a/binaries/data/mods/public/maps/scripts/TriggerHelper.js b/binaries/data/mods/public/maps/scripts/TriggerHelper.js index 9d47fdc071..753159cb65 100644 --- a/binaries/data/mods/public/maps/scripts/TriggerHelper.js +++ b/binaries/data/mods/public/maps/scripts/TriggerHelper.js @@ -96,6 +96,21 @@ TriggerHelper.SetUnitFormation = function(playerID, entities, formation) }; /** + * 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 @@ TriggerHelper.SpawnUnits = function(source, template, count, owner) 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 @@ TriggerHelper.SpawnGarrisonedUnits = function(entity, template, count, owner) 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 @@ TriggerHelper.SpawnTurretedUnits = function(entity, template, count, owner) 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) diff --git a/binaries/data/mods/public/simulation/components/Trainer.js b/binaries/data/mods/public/simulation/components/Trainer.js index 16f4c0f61c..a0f34804f4 100644 --- a/binaries/data/mods/public/simulation/components/Trainer.js +++ b/binaries/data/mods/public/simulation/components/Trainer.js @@ -554,7 +554,7 @@ Trainer.prototype.CalculateEntitiesMap = function() return entMap; } - token = this.GetUpgradedTemplate(token); + token = GetUpgradedTemplate(cmpPlayer.GetPlayerID(), token); entMap.set(rawToken, token); updateAllQueuedTemplate(rawToken, token); return entMap; @@ -563,32 +563,6 @@ Trainer.prototype.CalculateEntitiesMap = function() 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"])) diff --git a/binaries/data/mods/public/simulation/components/TurretHolder.js b/binaries/data/mods/public/simulation/components/TurretHolder.js index ebce1a66e8..1144dccf2c 100644 --- a/binaries/data/mods/public/simulation/components/TurretHolder.js +++ b/binaries/data/mods/public/simulation/components/TurretHolder.js @@ -35,22 +35,21 @@ class TurretHolder */ 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 @@ class TurretHolder this.EjectOrKill(this.GetEntities()); return; } + for (let point of this.turretPoints) { // If we were created, create any subunits now. diff --git a/binaries/data/mods/public/simulation/components/tests/test_Trainer.js b/binaries/data/mods/public/simulation/components/tests/test_Trainer.js index 43834b964d..c867984d58 100644 --- a/binaries/data/mods/public/simulation/components/tests/test_Trainer.js +++ b/binaries/data/mods/public/simulation/components/tests/test_Trainer.js @@ -55,6 +55,16 @@ AddMock(entityID, IID_Identity, { "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(), diff --git a/binaries/data/mods/public/simulation/components/tests/test_TurretHolder.js b/binaries/data/mods/public/simulation/components/tests/test_TurretHolder.js index 71b7959f7d..b200c93032 100644 --- a/binaries/data/mods/public/simulation/components/tests/test_TurretHolder.js +++ b/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.OccupyTurretPoint(cavID, cmpTurretHolder.turretPoints[ 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)); diff --git a/binaries/data/mods/public/simulation/helpers/Transform.js b/binaries/data/mods/public/simulation/helpers/Transform.js index e9af17741d..e1f003e876 100644 --- a/binaries/data/mods/public/simulation/helpers/Transform.js +++ b/binaries/data/mods/public/simulation/helpers/Transform.js @@ -294,5 +294,30 @@ function TransferGarrisonedUnits(oldEnt, newEnt) } } +/** + * @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); -- 2.11.4.GIT