Index: binaries/data/mods/public/maps/random/extinct_volcano_triggers.js =================================================================== --- binaries/data/mods/public/maps/random/extinct_volcano_triggers.js +++ binaries/data/mods/public/maps/random/extinct_volcano_triggers.js @@ -95,9 +95,7 @@ if (!cmpGarrisonHolder) continue; - for (let newEnt of TriggerHelper.SpawnUnits(gaiaEnt, garrisonedUnits, cmpGarrisonHolder.GetCapacity(), 0)) - if (Engine.QueryInterface(gaiaEnt, IID_GarrisonHolder).Garrison(newEnt)) - Engine.QueryInterface(newEnt, IID_UnitAI).Autogarrison(gaiaEnt); + TriggerHelper.SpawnGarrisonedUnits(gaiaEnt, garrisonedUnits, cmpGarrisonHolder.GetCapacity(), 0); } }; Index: binaries/data/mods/public/simulation/components/AlertRaiser.js =================================================================== --- binaries/data/mods/public/simulation/components/AlertRaiser.js +++ binaries/data/mods/public/simulation/components/AlertRaiser.js @@ -126,7 +126,7 @@ }); for (let unit of units) - if (cmpGarrisonHolder.PerformEject([unit], false)) + if (cmpGarrisonHolder.Unload(unit)) { let cmpUnitAI = Engine.QueryInterface(unit, IID_UnitAI); if (cmpUnitAI.HasWorkOrders()) Index: binaries/data/mods/public/simulation/components/GarrisonHolder.js =================================================================== --- binaries/data/mods/public/simulation/components/GarrisonHolder.js +++ binaries/data/mods/public/simulation/components/GarrisonHolder.js @@ -195,34 +195,6 @@ }; /** - * Simply eject the unit from the garrisoning entity without moving it - * @param {number} entity - Id of the entity to be ejected. - * @param {boolean} forced - Whether eject is forced (i.e. if building is destroyed). - * @param {boolean} renamed - Whether eject was due to entity renaming. - * - * @return {boolean} Whether the entity was ejected. - */ -GarrisonHolder.prototype.Eject = function(entity, forced, renamed = false) -{ - let entityIndex = this.entities.indexOf(entity); - // Error: invalid entity ID, usually it's already been ejected - if (entityIndex == -1) - return false; - - let cmpGarrisonable = Engine.QueryInterface(entity, IID_Garrisonable); - if (!cmpGarrisonable || !cmpGarrisonable.UnGarrison(forced, renamed)) - return false; - - this.entities.splice(entityIndex, 1); - Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { - "added": [], - "removed": [entity] - }); - - return true; -}; - -/** * @param {number} entity - EntityID to find the spawn position for. * @param {boolean} forced - Optionally whether the spawning is forced. * @return {Vector3D} - An appropriate spawning position. @@ -255,71 +227,48 @@ }; /** - * Ejects units and orders them to move to the rally point. If an ejection - * with a given obstruction radius has failed, we won't try anymore to eject - * entities with a bigger obstruction as that is compelled to also fail. - * @param {Array} entities - An array containing the ids of the entities to eject. - * @param {boolean} forced - Whether eject is forced (ie: if building is destroyed). - * @return {boolean} Whether the entities were ejected. + * @param {number} entity - The entity ID of the entity to eject. + * @param {boolean} forced - Whether eject is forced (e.g. if building is destroyed). + * @return {boolean} Whether the entity was ejected. */ -GarrisonHolder.prototype.PerformEject = function(entities, forced) +GarrisonHolder.prototype.Eject = function(entity, forced) { if (!this.IsGarrisoningAllowed() && !forced) return false; - let ejectedEntities = []; - let success = true; - let failedRadius; - let radius; - let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); - - for (let entity of entities) - { - if (failedRadius !== undefined) - { - let cmpObstruction = Engine.QueryInterface(entity, IID_Obstruction); - radius = cmpObstruction ? cmpObstruction.GetSize() : 0; - if (radius >= failedRadius) - continue; - } - - if (this.Eject(entity, forced)) - { - let cmpEntOwnership = Engine.QueryInterface(entity, IID_Ownership); - if (cmpOwnership && cmpEntOwnership && cmpOwnership.GetOwner() == cmpEntOwnership.GetOwner()) - ejectedEntities.push(entity); - } - else - { - success = false; - if (failedRadius !== undefined) - failedRadius = Math.min(failedRadius, radius); - else - { - let cmpObstruction = Engine.QueryInterface(entity, IID_Obstruction); - failedRadius = cmpObstruction ? cmpObstruction.GetSize() : 0; - } - } - } + let entityIndex = this.entities.indexOf(entity); + // Error: invalid entity ID, usually it's already been ejected, assume success. + if (entityIndex == -1) + return true; - this.OrderWalkToRallyPoint(ejectedEntities); + this.entities.splice(entityIndex, 1); this.UpdateGarrisonFlag(); + Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { + "added": [], + "removed": [entity] + }); - return success; + return true; }; /** - * Order entities to walk to the rally point. - * @param {Array} entities - An array containing all the ids of the entities. + * @param {number} entity - The entity ID of the entity to order to the rally point. */ -GarrisonHolder.prototype.OrderWalkToRallyPoint = function(entities) +GarrisonHolder.prototype.OrderToRallyPoint = function(entity) { - let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); let cmpRallyPoint = Engine.QueryInterface(this.entity, IID_RallyPoint); if (!cmpRallyPoint || !cmpRallyPoint.GetPositions()[0]) return; - let commands = GetRallyPointCommands(cmpRallyPoint, entities); + let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); + if (!cmpOwnership) + return; + + let cmpEntOwnership = Engine.QueryInterface(entity, IID_Ownership); + if (!cmpEntOwnership || cmpOwnership.GetOwner() != cmpEntOwnership.GetOwner()) + return; + + let commands = GetRallyPointCommands(cmpRallyPoint, [entity]); // Ignore the rally point if it is autogarrison if (commands[0].type == "garrison" && commands[0].target == this.entity) return; @@ -329,25 +278,38 @@ }; /** - * Unload unit from the garrisoning entity and order them - * to move to the rally point. + * Tell unit to unload from this entity. + * @param {number} entity - The entity to unload. * @return {boolean} Whether the command was successful. */ -GarrisonHolder.prototype.Unload = function(entity, forced) +GarrisonHolder.prototype.Unload = function(entity) +{ + let cmpGarrisonable = Engine.QueryInterface(entity, IID_Garrisonable); + return cmpGarrisonable && cmpGarrisonable.UnGarrison(); +}; + +/** + * Tell units to unload from this entity. + * @param {number[]} entities - The entities to unload. + * @return {boolean} - Whether all unloads were successful. + */ +GarrisonHolder.prototype.UnloadEntities = function(entities) { - return this.PerformEject([entity], forced); + let success = true; + for (let entity of entities) + if (!this.Unload(entity)) + success = false; + return success; }; /** - * Unload one or all units that match a template and owner from - * the garrisoning entity and order them to move to the rally point. + * Unload one or all units that match a template and owner from us. * @param {string} template - Type of units that should be ejected. * @param {number} owner - Id of the player whose units should be ejected. * @param {boolean} all - Whether all units should be ejected. - * @param {boolean} forced - Whether unload is forced. * @return {boolean} Whether the unloading was successful. */ -GarrisonHolder.prototype.UnloadTemplate = function(template, owner, all, forced) +GarrisonHolder.prototype.UnloadTemplate = function(template, owner, all) { let entities = []; let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); @@ -367,33 +329,31 @@ break; } - return this.PerformEject(entities, forced); + return this.UnloadEntities(entities); }; /** * Unload all units, that belong to certain player * and order all own units to move to the rally point. - * @param {boolean} forced - Whether unload is forced. * @param {number} owner - Id of the player whose units should be ejected. * @return {boolean} Whether the unloading was successful. */ -GarrisonHolder.prototype.UnloadAllByOwner = function(owner, forced) +GarrisonHolder.prototype.UnloadAllByOwner = function(owner) { let entities = this.entities.filter(ent => { let cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); return cmpOwnership && cmpOwnership.GetOwner() == owner; }); - return this.PerformEject(entities, forced); + return this.UnloadEntities(entities); }; /** * Unload all units from the entity and order them to move to the rally point. - * @param {boolean} forced - Whether unload is forced. * @return {boolean} Whether the unloading was successful. */ -GarrisonHolder.prototype.UnloadAll = function(forced) +GarrisonHolder.prototype.UnloadAll = function() { - return this.PerformEject(this.entities.slice(), forced); + return this.UnloadEntities(this.entities.slice()); }; /** @@ -544,7 +504,7 @@ { let ejectables = entities.filter(ent => this.IsEjectable(ent)); if (ejectables.length) - this.PerformEject(ejectables, false); + this.UnloadEntities(ejectables); } // And destroy all remaining entities @@ -557,6 +517,8 @@ let cmpHealth = Engine.QueryInterface(entity, IID_Health); if (cmpHealth) cmpHealth.Kill(); + else + Engine.DestroyEntity(entity); this.entities.splice(entityIndex, 1); killedEntities.push(entity); } Index: binaries/data/mods/public/simulation/components/Garrisonable.js =================================================================== --- binaries/data/mods/public/simulation/components/Garrisonable.js +++ binaries/data/mods/public/simulation/components/Garrisonable.js @@ -50,6 +50,9 @@ */ Garrisonable.prototype.CanGarrison = function(entity) { + if (this.holder) + return false; + let cmpGarrisonHolder = Engine.QueryInterface(entity, IID_GarrisonHolder); return cmpGarrisonHolder && cmpGarrisonHolder.IsAllowedToGarrison(this.entity); }; @@ -60,7 +63,7 @@ */ Garrisonable.prototype.Garrison = function(entity, renamed = false) { - if (this.holder) + if (!this.CanGarrison(entity)) return false; let cmpGarrisonHolder = Engine.QueryInterface(entity, IID_GarrisonHolder); @@ -114,25 +117,31 @@ */ Garrisonable.prototype.UnGarrison = function(forced = false, renamed = false) { + if (!this.holder) + return true; + + let cmpGarrisonHolder = Engine.QueryInterface(this.holder, IID_GarrisonHolder); + if (!cmpGarrisonHolder) + return false; + + let pos = cmpGarrisonHolder.GetSpawnPosition(this.entity, forced); + if (!pos) + return false; + + if (!cmpGarrisonHolder.Eject(this.entity, forced)) + return false; + let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); if (cmpPosition) { - let pos; - let cmpGarrisonHolder = Engine.QueryInterface(this.holder, IID_GarrisonHolder); - if (cmpGarrisonHolder) - pos = cmpGarrisonHolder.GetSpawnPosition(this.entity, forced); - - if (!pos) - return false; - cmpPosition.JumpTo(pos.x, pos.z); cmpPosition.SetHeightOffset(0); - - let cmpHolderPosition = Engine.QueryInterface(this.holder, IID_Position); - if (cmpHolderPosition) - cmpPosition.SetYRotation(cmpHolderPosition.GetPosition().horizAngleTo(pos)); } + let cmpHolderPosition = Engine.QueryInterface(this.holder, IID_Position); + if (cmpHolderPosition) + cmpPosition.SetYRotation(cmpHolderPosition.GetPosition().horizAngleTo(pos)); + let cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI); if (cmpUnitAI) cmpUnitAI.Ungarrison(); @@ -152,6 +161,8 @@ if (cmpTurretHolder) cmpTurretHolder.LeaveTurret(this.entity); + cmpGarrisonHolder.OrderToRallyPoint(this.entity); + delete this.holder; return true; }; @@ -164,8 +175,7 @@ let cmpGarrisonHolder = Engine.QueryInterface(this.holder, IID_GarrisonHolder); if (cmpGarrisonHolder) { - // ToDo: Clean this by using cmpGarrisonable to ungarrison. - cmpGarrisonHolder.Eject(msg.entity, true, true); + this.UnGarrison(true, true); let cmpGarrisonable = Engine.QueryInterface(msg.newentity, IID_Garrisonable); if (cmpGarrisonable) cmpGarrisonable.Garrison(this.holder, true); 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 @@ -700,12 +700,12 @@ for (let i = 0; i < count; ++i) this.entityCache.push(Engine.AddEntity(templateName)); - let cmpAutoGarrison; + let autoGarrison; if (cmpRallyPoint) { let data = cmpRallyPoint.GetData()[0]; if (data && data.target && data.target == this.entity && data.command == "garrison") - cmpAutoGarrison = Engine.QueryInterface(this.entity, IID_GarrisonHolder); + autoGarrison = true; } for (let i = 0; i < count; ++i) @@ -714,21 +714,16 @@ let cmpNewOwnership = Engine.QueryInterface(ent, IID_Ownership); let garrisoned = false; - if (cmpAutoGarrison) + if (autoGarrison) { + let cmpGarrisonable = Engine.QueryInterface(ent, IID_Garrisonable); // Temporary owner affectation needed for GarrisonHolder checks. cmpNewOwnership.SetOwnerQuiet(cmpOwnership.GetOwner()); - garrisoned = cmpAutoGarrison.Garrison(ent); + garrisoned = cmpGarrisonable && cmpGarrisonable.Autogarrison(this.entity); cmpNewOwnership.SetOwnerQuiet(INVALID_PLAYER); } - if (garrisoned) - { - let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); - if (cmpUnitAI) - cmpUnitAI.Autogarrison(this.entity); - } - else + if (!garrisoned) { let pos = cmpFootprint.PickSpawnPoint(ent); if (pos.y < 0) @@ -766,7 +761,7 @@ createdEnts.push(ent); } - if (spawnedEnts.length && !cmpAutoGarrison) + if (spawnedEnts.length && !autoGarrison) { // If a rally point is set, walk towards it (in formation) using a suitable command based on where the // rally point is placed. Index: binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js +++ binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js @@ -19,20 +19,15 @@ const enemyPlayer = 3; let cmpGarrisonHolder = ConstructComponent(garrisonHolderId, "GarrisonHolder", { - "Max": 10, + "Max": "10", "List": { "_string": "Infantry+Cavalry" }, - "EjectHealth": 0.1, + "EjectHealth": "0.1", "EjectClassesOnDestroy": { "_string": "Infantry" }, - "BuffHeal": 1, - "LoadingRange": 2.1, + "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) -}); - AddMock(garrisonHolderId, IID_Ownership, { "GetOwner": () => player }); @@ -81,15 +76,15 @@ AddMock(i, IID_Garrisonable, { "UnitSize": () => 9, "TotalSize": () => 9, - "Garrison": (entity, renamed) => cmpGarrisonHolder.Garrison(i, renamed), - "UnGarrison": () => true + "Garrison": (entity) => cmpGarrisonHolder.Garrison(i), + "UnGarrison": () => cmpGarrisonHolder.Eject(i) }); else AddMock(i, IID_Garrisonable, { "UnitSize": () => 1, "TotalSize": () => 1, - "Garrison": entity => true, - "UnGarrison": () => true + "Garrison": entity => cmpGarrisonHolder.Garrison(i), + "UnGarrison": () => cmpGarrisonHolder.Eject(i) }); AddMock(i, IID_Position, { @@ -114,8 +109,8 @@ TS_ASSERT_EQUALS(cmpGarrisonHolder.Garrison(unitToGarrisonId), true); TS_ASSERT_EQUALS(cmpGarrisonHolder.Garrison(largeUnitId), true); TS_ASSERT_EQUALS(cmpGarrisonHolder.IsFull(), true); - TS_ASSERT_EQUALS(cmpGarrisonHolder.Eject(largeUnitId), true); - TS_ASSERT_EQUALS(cmpGarrisonHolder.Eject(unitToGarrisonId), true); + TS_ASSERT_EQUALS(cmpGarrisonHolder.Unload(largeUnitId), true); + TS_ASSERT_EQUALS(cmpGarrisonHolder.Unload(unitToGarrisonId), true); TS_ASSERT_EQUALS(cmpGarrisonHolder.Garrison(unitToGarrisonId), true); for (let entity of garrisonedEntitiesList) TS_ASSERT_EQUALS(cmpGarrisonHolder.Garrison(entity), true); @@ -123,16 +118,16 @@ TS_ASSERT_EQUALS(cmpGarrisonHolder.IsFull(), true); TS_ASSERT_EQUALS(cmpGarrisonHolder.CanPickup(unitToGarrisonId), false); - TS_ASSERT_EQUALS(cmpGarrisonHolder.UnloadTemplate("spart_infantry_archer_a", 2, false, false), true); + TS_ASSERT_EQUALS(cmpGarrisonHolder.UnloadTemplate("spart_infantry_archer_a", 2, false), true); TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), [24, 25, 26, 27, 28, 29, 30, 31, 32]); - TS_ASSERT_EQUALS(cmpGarrisonHolder.UnloadAllByOwner(friendlyPlayer, false), true); + TS_ASSERT_EQUALS(cmpGarrisonHolder.UnloadAllByOwner(friendlyPlayer), true); TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), [24, 25, 26, 27]); TS_ASSERT_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), 4); TS_ASSERT_EQUALS(cmpGarrisonHolder.IsEjectable(25), true); TS_ASSERT_EQUALS(cmpGarrisonHolder.Unload(25), true); TS_ASSERT_EQUALS(cmpGarrisonHolder.IsEjectable(25), false); - TS_ASSERT_EQUALS(cmpGarrisonHolder.PerformEject([25], false), false); - TS_ASSERT_EQUALS(cmpGarrisonHolder.PerformEject([], false), true); + TS_ASSERT_EQUALS(cmpGarrisonHolder.Unload(25), true); + TS_ASSERT_EQUALS(cmpGarrisonHolder.Eject(null, false), true); TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), [24, 26, 27]); TS_ASSERT_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), 3); TS_ASSERT_EQUALS(cmpGarrisonHolder.IsFull(), false); @@ -141,7 +136,7 @@ TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), []); }; -// No health component yet. +// No health component yet.Pick testGarrisonAllowed(); AddMock(garrisonHolderId, IID_Health, { Index: binaries/data/mods/public/simulation/components/tests/test_Garrisonable.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_Garrisonable.js +++ binaries/data/mods/public/simulation/components/tests/test_Garrisonable.js @@ -10,7 +10,11 @@ const garrisonHolderID = 1; const garrisonableID = 2; AddMock(garrisonHolderID, IID_GarrisonHolder, { - "Garrison": () => true + "Garrison": () => true, + "GetSpawnPosition": () => new Vector3D(0, 0, 0), + "IsAllowedToGarrison": () => true, + "OrderToRallyPoint": () => {}, + "Eject": () => true }); let size = 1; @@ -18,16 +22,18 @@ "Size": size }); -TS_ASSERT_EQUALS(cmpGarrisonable.UnitSize(garrisonHolderID), size); -TS_ASSERT_EQUALS(cmpGarrisonable.TotalSize(garrisonHolderID), size); +TS_ASSERT_EQUALS(cmpGarrisonable.UnitSize(), size); +TS_ASSERT_EQUALS(cmpGarrisonable.TotalSize(), size); let extraSize = 2; AddMock(garrisonableID, IID_GarrisonHolder, { "OccupiedSlots": () => extraSize }); -TS_ASSERT_EQUALS(cmpGarrisonable.UnitSize(garrisonHolderID), size); -TS_ASSERT_EQUALS(cmpGarrisonable.TotalSize(garrisonHolderID), size + extraSize); +TS_ASSERT_EQUALS(cmpGarrisonable.UnitSize(), size); +TS_ASSERT_EQUALS(cmpGarrisonable.TotalSize(), size + extraSize); + +// Test garrisoning. TS_ASSERT(cmpGarrisonable.Garrison(garrisonHolderID)); TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonable.HolderID(), garrisonHolderID); @@ -37,3 +43,13 @@ cmpGarrisonable.UnGarrison(); TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonable.HolderID(), INVALID_ENTITY); + +// Test renaming. +const newGarrisonableID = 3; +let cmpGarrisonableNew = ConstructComponent(newGarrisonableID, "Garrisonable", { + "Size": 1 +}); +TS_ASSERT(cmpGarrisonable.Garrison(garrisonHolderID)); +cmpGarrisonable.OnEntityRenamed({ "entity": garrisonableID, "newentity": newGarrisonableID }); +TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonable.HolderID(), INVALID_ENTITY); +TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonableNew.HolderID(), garrisonHolderID); Index: binaries/data/mods/public/simulation/components/tests/test_Garrisoning.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_Garrisoning.js +++ binaries/data/mods/public/simulation/components/tests/test_Garrisoning.js @@ -11,6 +11,7 @@ Engine.LoadComponentScript("interfaces/UnitAI.js"); Engine.LoadComponentScript("Garrisonable.js"); Engine.LoadComponentScript("GarrisonHolder.js"); +Engine.LoadComponentScript("TurretHolder.js"); const player = 1; const enemyPlayer = 2; @@ -18,6 +19,32 @@ const garrison = 10; const holder = 11; +let createGarrisonCmp = entity => { + AddMock(entity, IID_Identity, { + "GetClassesList": () => ["Ranged"], + "GetSelectionGroupName": () => "mace_infantry_archer_a" + }); + + AddMock(entity, IID_Ownership, { + "GetOwner": () => player + }); + + AddMock(entity, IID_Position, { + "GetHeightOffset": () => 0, + "GetPosition": () => new Vector3D(4, 3, 25), + "GetRotation": () => new Vector3D(4, 0, 6), + "JumpTo": (posX, posZ) => {}, + "MoveOutOfWorld": () => {}, + "SetHeightOffset": height => {}, + "SetTurretParent": ent => {}, + "SetYRotation": angle => {} + }); + + return ConstructComponent(entity, "Garrisonable", { + "Size": "1" + }); +}; + AddMock(holder, IID_Footprint, { "PickSpawnPointBothPass": entity => new Vector3D(4, 3, 30), "PickSpawnPoint": entity => new Vector3D(4, 3, 30) @@ -62,7 +89,9 @@ "GetRotation": () => new Vector3D(4, 0, 6), "JumpTo": (posX, posZ) => {}, "MoveOutOfWorld": () => {}, - "SetHeightOffset": height => {} + "SetHeightOffset": height => {}, + "SetTurretParent": entity => {}, + "SetYRotation": angle => {} }); let cmpGarrisonable = ConstructComponent(garrison, "Garrisonable", { @@ -90,3 +119,78 @@ TS_ASSERT(cmpGarrisonable.Garrison(holder)); TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), [garrison]); + +// Can't garrison twice. +TS_ASSERT(!cmpGarrisonable.Garrison(holder)); +TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), [garrison]); + +TS_ASSERT(cmpGarrisonHolder.Unload(garrison)); +TS_ASSERT_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), 0); + +// Test initGarrison. +let entities = [21, 22, 23, 24]; +for (let entity of entities) + createGarrisonCmp(entity); +cmpGarrisonHolder.SetInitGarrison(entities); +cmpGarrisonHolder.OnGlobalInitGame(); +TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), entities); + +// They turned against us! +AddMock(entities[0], IID_Ownership, { + "GetOwner": () => enemyPlayer +}); +cmpGarrisonHolder.OnDiplomacyChanged(); +TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), entities.length - 1); + +TS_ASSERT(cmpGarrisonHolder.UnloadAll()); +TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), []); + +// Turrets! +AddMock(holder, IID_Position, { + "GetPosition": () => new Vector3D(4, 3, 25), + "GetRotation": () => new Vector3D(4, 0, 6) +}); + +let cmpTurretHolder = ConstructComponent(holder, "TurretHolder", { + "TurretPoints": { + "archer1": { + "X": "12.0", + "Y": "5.", + "Z": "6.0" + }, + "archer2": { + "X": "15.0", + "Y": "5.0", + "Z": "6.0" + } + } +}); + +TS_ASSERT(cmpGarrisonable.Garrison(holder)); +TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), [garrison]); +TS_ASSERT(cmpTurretHolder.OccupiesTurret(garrison)); +TS_ASSERT(cmpGarrisonable.UnGarrison()); +TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), []); +TS_ASSERT_UNEVAL_EQUALS(cmpTurretHolder.GetEntities(), []); + +// Test renaming on a turret. +// Ensure we test renaming from the second spot, not the first. +const newGarrison = 31; +let cmpGarrisonableNew = createGarrisonCmp(newGarrison); +TS_ASSERT(cmpGarrisonableNew.Garrison(holder)); +TS_ASSERT(cmpGarrisonable.Garrison(holder)); +TS_ASSERT(cmpGarrisonableNew.UnGarrison()); +let previousTurret = cmpTurretHolder.GetOccupiedTurretName(garrison); +cmpGarrisonable.OnEntityRenamed({ + "entity": garrison, + "newentity": newGarrison +}); +let newTurret = cmpTurretHolder.GetOccupiedTurretName(newGarrison); +TS_ASSERT_UNEVAL_EQUALS(newTurret, previousTurret); +TS_ASSERT(cmpGarrisonableNew.UnGarrison()); + +// Test initTurrets. +cmpTurretHolder.SetInitEntity("archer1", garrison); +cmpTurretHolder.SetInitEntity("archer2", newGarrison); +cmpTurretHolder.OnGlobalInitGame(); +TS_ASSERT_UNEVAL_EQUALS(cmpTurretHolder.GetEntities(), [garrison, newGarrison]); 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 @@ -252,7 +252,7 @@ let entities = cmpOldGarrison.GetEntities().slice(); for (let ent of entities) { - cmpOldGarrison.Eject(ent); + cmpOldGarrison.Unload(ent); if (!cmpNewGarrison) continue; let cmpGarrisonable = Engine.QueryInterface(ent, IID_Garrisonable);