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 @@ -184,14 +184,9 @@ if (cmpOwnership) cmpOwnership.SetOwner(owner); - if (cmpGarrisonHolder.Garrison(ent)) - { - let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); - if (cmpUnitAI) - cmpUnitAI.Autogarrison(entity); - + let cmpGarrisonable = Engine.QueryInterface(ent, IID_Garrisonable); + if (cmpGarrisonable && cmpGarrisonable.Autogarrison(entity)) entities.push(ent); - } else error("failed to garrison entity " + ent + " (" + template + ") inside " + entity); } Index: ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js +++ ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js @@ -164,11 +164,9 @@ /** * @param {number} entity - The entityID to garrison. - * @param {boolean} renamed - Whether the entity was renamed. - * * @return {boolean} - Whether the entity was garrisoned. */ -GarrisonHolder.prototype.Garrison = function(entity, renamed = false) +GarrisonHolder.prototype.Garrison = function(entity) { if (!this.IsAllowedToGarrison(entity)) return false; @@ -176,10 +174,6 @@ if (!this.HasEnoughHealth()) return false; - let cmpGarrisonable = Engine.QueryInterface(entity, IID_Garrisonable); - if (!cmpGarrisonable || !cmpGarrisonable.Garrison(this.entity)) - return false; - if (!this.timer && this.GetHealRate() > 0) { let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); @@ -191,8 +185,7 @@ Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { "added": [entity], - "removed": [], - "renamed": renamed + "removed": [] }); return true; @@ -214,14 +207,13 @@ return false; let cmpGarrisonable = Engine.QueryInterface(entity, IID_Garrisonable); - if (!cmpGarrisonable || !cmpGarrisonable.UnGarrison(forced)) + if (!cmpGarrisonable || !cmpGarrisonable.UnGarrison(forced, renamed)) return false; this.entities.splice(entityIndex, 1); Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { "added": [], - "removed": [entity], - "renamed": renamed + "removed": [entity] }); return true; @@ -510,24 +502,6 @@ */ GarrisonHolder.prototype.OnGlobalEntityRenamed = function(msg) { - let entityIndex = this.entities.indexOf(msg.entity); - if (entityIndex != -1) - { - this.Eject(msg.entity, true, true); - this.Garrison(msg.newentity, true); - - // TurretHolder is not subscribed to GarrisonChanged, so we must inform it explicitly. - // Otherwise a renaming entity may re-occupy another turret instead of its previous one, - // since the message does not know what turret point whas used, which is not wanted. - // Also ensure the TurretHolder receives the message after we process it. - // If it processes it before us we garrison a turret and subsequently - // are hidden by GarrisonHolder again. - // This could be fixed by not requiring a turret to be 'garrisoned'. - let cmpTurretHolder = Engine.QueryInterface(this.entity, IID_TurretHolder); - if (cmpTurretHolder) - cmpTurretHolder.SwapEntities(msg.entity, msg.newentity); - } - if (!this.initGarrison) return; @@ -540,7 +514,7 @@ } else { - entityIndex = this.initGarrison.indexOf(msg.entity); + let entityIndex = this.initGarrison.indexOf(msg.entity); if (entityIndex != -1) this.initGarrison[entityIndex] = msg.newentity; } @@ -630,9 +604,9 @@ for (let ent of this.initGarrison) { - let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); - if (cmpUnitAI && cmpUnitAI.CanGarrison(this.entity) && this.Garrison(ent)) - cmpUnitAI.Autogarrison(this.entity); + let cmpGarrisonable = Engine.QueryInterface(ent, IID_Garrisonable); + if (cmpGarrisonable) + cmpGarrisonable.Autogarrison(this.entity); } this.initGarrison = undefined; }; @@ -665,4 +639,3 @@ }; Engine.RegisterComponentType(IID_GarrisonHolder, "GarrisonHolder", GarrisonHolder); - Index: ps/trunk/binaries/data/mods/public/simulation/components/Garrisonable.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Garrisonable.js +++ ps/trunk/binaries/data/mods/public/simulation/components/Garrisonable.js @@ -45,14 +45,28 @@ }; /** + * @param {number} - The entity ID to check. + * @return {boolean} - Whether we can garrison. + */ +Garrisonable.prototype.CanGarrison = function(entity) +{ + let cmpGarrisonHolder = Engine.QueryInterface(entity, IID_GarrisonHolder); + return cmpGarrisonHolder && cmpGarrisonHolder.IsAllowedToGarrison(this.entity); +}; + +/** * @param {number} entity - The entity ID of the entity this entity is being garrisoned in. * @return {boolean} - Whether garrisoning succeeded. */ -Garrisonable.prototype.Garrison = function(entity) +Garrisonable.prototype.Garrison = function(entity, renamed = false) { if (this.holder) return false; + let cmpGarrisonHolder = Engine.QueryInterface(entity, IID_GarrisonHolder); + if (!cmpGarrisonHolder || !cmpGarrisonHolder.Garrison(this.entity)) + return false; + this.holder = entity; let cmpProductionQueue = Engine.QueryInterface(this.entity, IID_ProductionQueue); @@ -67,14 +81,38 @@ if (cmpPosition) cmpPosition.MoveOutOfWorld(); + if (renamed) + return true; + + let cmpTurretHolder = Engine.QueryInterface(entity, IID_TurretHolder); + if (cmpTurretHolder) + cmpTurretHolder.OccupyTurret(this.entity); + + return true; +}; + +/** + * Called on game init when the entity was part of init garrison. + * @param {number} entity - The entityID to autogarrison. + * @return {boolean} - Whether garrisoning succeeded. + */ +Garrisonable.prototype.Autogarrison = function(entity) +{ + if (!this.Garrison(entity)) + return false; + + let cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI); + if (cmpUnitAI) + cmpUnitAI.Autogarrison(this.entity); return true; }; /** * @param {boolean} forced - Optionally whether the spawning is forced. + * @param {boolean} renamed - Optionally whether the ungarrisoning is due to renaming. * @return {boolean} - Whether the ungarrisoning succeeded. */ -Garrisonable.prototype.UnGarrison = function(forced) +Garrisonable.prototype.UnGarrison = function(forced = false, renamed = false) { let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); if (cmpPosition) @@ -107,8 +145,42 @@ if (cmpAura && cmpAura.HasGarrisonAura()) cmpAura.RemoveGarrisonAura(this.holder); + if (renamed) + return true; + + let cmpTurretHolder = Engine.QueryInterface(this.holder, IID_TurretHolder); + if (cmpTurretHolder) + cmpTurretHolder.LeaveTurret(this.entity); + delete this.holder; return true; }; +Garrisonable.prototype.OnEntityRenamed = function(msg) +{ + if (!this.holder) + return; + + let cmpGarrisonHolder = Engine.QueryInterface(this.holder, IID_GarrisonHolder); + if (cmpGarrisonHolder) + { + // ToDo: Clean this by using cmpGarrisonable to ungarrison. + cmpGarrisonHolder.Eject(msg.entity, true, true); + let cmpGarrisonable = Engine.QueryInterface(msg.newentity, IID_Garrisonable); + if (cmpGarrisonable) + cmpGarrisonable.Garrison(this.holder, true); + } + + // We process EntityRenamed of turrets here because we need to be sure that we + // receive it after it is processed by GarrisonHolder.js. + // ToDo: Make this not needed by fully separating TurretHolder from GarrisonHolder. + // That means an entity with TurretHolder should not need a GarrisonHolder + // for e.g. the garrisoning logic. + let cmpTurretHolder = Engine.QueryInterface(this.holder, IID_TurretHolder); + if (cmpTurretHolder) + cmpTurretHolder.SwapEntities(msg.entity, msg.newentity); + + delete this.holder; +}; + Engine.RegisterComponentType(IID_Garrisonable, "Garrisonable", Garrisonable); 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 @@ -238,12 +238,6 @@ } /** - * We process EntityRenamed here because we need to be sure that we receive - * it after it is processed by GarrisonHolder.js. - * ToDo: Make this not needed by fully separating TurretHolder from GarrisonHolder. - * That means an entity with TurretHolder should not need a GarrisonHolder - * for e.g. the garrisoning logic. - * * @param {number} from - The entity to substitute. * @param {number} to - The entity to subtitute with. */ @@ -251,24 +245,10 @@ { let turretPoint = this.GetOccupiedTurret(from); if (turretPoint) + { this.LeaveTurret(from, turretPoint); - - let cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder); - if (cmpGarrisonHolder && cmpGarrisonHolder.IsGarrisoned(to)) this.OccupyTurret(to, turretPoint); - } - - OnGarrisonedUnitsChanged(msg) - { - // Ignore renaming for that is handled seperately - // (i.e. called directly from GarrisonHolder.js). - if (msg.renamed) - return; - - for (let entity of msg.removed) - this.LeaveTurret(entity); - for (let entity of msg.added) - this.OccupyTurret(entity); + } } /** Index: ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js +++ ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js @@ -3163,8 +3163,8 @@ if (this.CanGarrison(target)) if (this.CheckGarrisonRange(target)) { - var cmpGarrisonHolder = Engine.QueryInterface(target, IID_GarrisonHolder); - if (cmpGarrisonHolder.Garrison(this.entity)) + let cmpGarrisonable = Engine.QueryInterface(this.entity, IID_Garrisonable); + if (cmpGarrisonable.Garrison(target)) { this.isGarrisoned = true; this.SetImmobile(true); Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js +++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js @@ -1,12 +1,13 @@ Engine.LoadHelperScript("ValueModification.js"); Engine.LoadHelperScript("Player.js"); -Engine.LoadComponentScript("interfaces/GarrisonHolder.js"); Engine.LoadComponentScript("interfaces/Garrisonable.js"); +Engine.LoadComponentScript("interfaces/GarrisonHolder.js"); Engine.LoadComponentScript("interfaces/TurretHolder.js"); -Engine.LoadComponentScript("GarrisonHolder.js"); Engine.LoadComponentScript("interfaces/Health.js"); Engine.LoadComponentScript("interfaces/ModifiersManager.js"); Engine.LoadComponentScript("interfaces/Timer.js"); +Engine.LoadComponentScript("Garrisonable.js"); +Engine.LoadComponentScript("GarrisonHolder.js"); const garrisonedEntitiesList = [25, 26, 27, 28, 29, 30, 31, 32, 33]; const garrisonHolderId = 15; @@ -17,6 +18,16 @@ const friendlyPlayer = 2; const enemyPlayer = 3; +let cmpGarrisonHolder = ConstructComponent(garrisonHolderId, "GarrisonHolder", { + "Max": 10, + "List": { "_string": "Infantry+Cavalry" }, + "EjectHealth": 0.1, + "EjectClassesOnDestroy": { "_string": "Infantry" }, + "BuffHeal": 1, + "LoadingRange": 2.1, + "Pickup": false +}); + AddMock(garrisonHolderId, IID_Footprint, { "PickSpawnPointBothPass": entity => new Vector3D(4, 3, 30), "PickSpawnPoint": entity => new Vector3D(4, 3, 30) @@ -70,7 +81,7 @@ AddMock(i, IID_Garrisonable, { "UnitSize": () => 9, "TotalSize": () => 9, - "Garrison": entity => true, + "Garrison": (entity, renamed) => cmpGarrisonHolder.Garrison(i, renamed), "UnGarrison": () => true }); else @@ -96,16 +107,6 @@ "GetSelectionGroupName": () => "spart_infantry_archer_a" }); -let cmpGarrisonHolder = ConstructComponent(garrisonHolderId, "GarrisonHolder", { - "Max": 10, - "List": { "_string": "Infantry+Cavalry" }, - "EjectHealth": 0.1, - "EjectClassesOnDestroy": { "_string": "Infantry" }, - "BuffHeal": 1, - "LoadingRange": 2.1, - "Pickup": false -}); - let testGarrisonAllowed = function() { TS_ASSERT_EQUALS(cmpGarrisonHolder.HasEnoughHealth(), true); @@ -232,7 +233,7 @@ AddMock(siegeEngineId, IID_Garrisonable, { "UnitSize": () => 1, "TotalSize": () => 1, - "Garrison": entity => true, + "Garrison": (entity, renamed) => cmpGarrisonHolder.Garrison(siegeEngineId, renamed), "UnGarrison": () => true }); let cavalryId = 46; @@ -255,15 +256,10 @@ AddMock(cavalryId, IID_Garrisonable, { "UnitSize": () => 1, "TotalSize": () => 1, - "Garrison": entity => true, + "Garrison": (entity, renamed) => cmpGarrisonHolder.Garrison(cavalryId, renamed), "UnGarrison": () => true }); -TS_ASSERT(cmpGarrisonHolder.Garrison(siegeEngineId)); -TS_ASSERT_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), 1); -cmpGarrisonHolder.OnGlobalEntityRenamed({ - "entity": siegeEngineId, - "newentity": cavalryId -}); +TS_ASSERT(cmpGarrisonHolder.Garrison(cavalryId)); TS_ASSERT_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), 1); // Eject enemy units. Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Garrisonable.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Garrisonable.js +++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Garrisonable.js @@ -9,8 +9,11 @@ const garrisonHolderID = 1; const garrisonableID = 2; +AddMock(garrisonHolderID, IID_GarrisonHolder, { + "Garrison": () => true +}); -let size = 1 +let size = 1; let cmpGarrisonable = ConstructComponent(garrisonableID, "Garrisonable", { "Size": size }); Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Garrisoning.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Garrisoning.js +++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Garrisoning.js @@ -0,0 +1,92 @@ +Engine.LoadHelperScript("ValueModification.js"); +Engine.LoadHelperScript("Player.js"); +Engine.LoadComponentScript("interfaces/Auras.js"); +Engine.LoadComponentScript("interfaces/Garrisonable.js"); +Engine.LoadComponentScript("interfaces/GarrisonHolder.js"); +Engine.LoadComponentScript("interfaces/Health.js"); +Engine.LoadComponentScript("interfaces/ModifiersManager.js"); +Engine.LoadComponentScript("interfaces/ProductionQueue.js"); +Engine.LoadComponentScript("interfaces/Timer.js"); +Engine.LoadComponentScript("interfaces/TurretHolder.js"); +Engine.LoadComponentScript("interfaces/UnitAI.js"); +Engine.LoadComponentScript("Garrisonable.js"); +Engine.LoadComponentScript("GarrisonHolder.js"); + +const player = 1; +const enemyPlayer = 2; +const friendlyPlayer = 3; +const garrison = 10; +const holder = 11; + +AddMock(holder, IID_Footprint, { + "PickSpawnPointBothPass": entity => new Vector3D(4, 3, 30), + "PickSpawnPoint": entity => new Vector3D(4, 3, 30) +}); + +AddMock(holder, IID_Ownership, { + "GetOwner": () => player +}); + +AddMock(player, IID_Player, { + "IsAlly": id => id != enemyPlayer, + "IsMutualAlly": id => id != enemyPlayer, + "GetPlayerID": () => player +}); + +AddMock(friendlyPlayer, IID_Player, { + "IsAlly": id => true, + "IsMutualAlly": id => true, + "GetPlayerID": () => friendlyPlayer +}); + +AddMock(SYSTEM_ENTITY, IID_Timer, { + "SetTimeout": (ent, iid, funcname, time, data) => 1 +}); + +AddMock(SYSTEM_ENTITY, IID_PlayerManager, { + "GetPlayerByID": id => id +}); + +AddMock(garrison, IID_Identity, { + "GetClassesList": () => ["Ranged"], + "GetSelectionGroupName": () => "mace_infantry_archer_a" +}); + +AddMock(garrison, IID_Ownership, { + "GetOwner": () => player +}); + +AddMock(garrison, IID_Position, { + "GetHeightOffset": () => 0, + "GetPosition": () => new Vector3D(4, 3, 25), + "GetRotation": () => new Vector3D(4, 0, 6), + "JumpTo": (posX, posZ) => {}, + "MoveOutOfWorld": () => {}, + "SetHeightOffset": height => {} +}); + +let cmpGarrisonable = ConstructComponent(garrison, "Garrisonable", { + "Size": "1" +}); + +let cmpGarrisonHolder = ConstructComponent(holder, "GarrisonHolder", { + "Max": "10", + "List": { "_string": "Ranged" }, + "EjectHealth": "0.1", + "EjectClassesOnDestroy": { "_string": "Infantry" }, + "BuffHeal": "1", + "LoadingRange": "2.1", + "Pickup": "false" +}); + +TS_ASSERT(cmpGarrisonable.Garrison(holder)); +TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), [garrison]); + +cmpGarrisonable.OnEntityRenamed({ + "entity": garrison, + "newentity": -1 +}); +TS_ASSERT_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), 0); + +TS_ASSERT(cmpGarrisonable.Garrison(holder)); +TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), [garrison]); 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 @@ -67,6 +67,8 @@ cmpNewUnitAI.SetHeldPosition(pos.x, pos.z); if (cmpUnitAI.GetStanceName()) cmpNewUnitAI.SwitchToStance(cmpUnitAI.GetStanceName()); + if (cmpUnitAI.IsGarrisoned()) + cmpNewUnitAI.SetGarrisoned(); cmpNewUnitAI.AddOrders(cmpUnitAI.GetOrders()); if (cmpUnitAI.IsGuardOf()) { @@ -78,8 +80,6 @@ cmpNewUnitAI.SetGuardOf(guarded); } } - if (cmpUnitAI.IsGarrisoned()) - cmpNewUnitAI.SetGarrisoned(); } let cmpPromotion = Engine.QueryInterface(oldEnt, IID_Promotion); @@ -255,11 +255,10 @@ cmpOldGarrison.Eject(ent); if (!cmpNewGarrison) continue; - let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); - if (!cmpUnitAI) + let cmpGarrisonable = Engine.QueryInterface(ent, IID_Garrisonable); + if (!cmpGarrisonable) continue; - cmpUnitAI.Autogarrison(newEnt); - cmpNewGarrison.Garrison(ent); + cmpGarrisonable.Autogarrison(newEnt); } }