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 @@ -31,39 +31,6 @@ "" + "" + "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "tokens" + - "" + - "" + - "" + - ""+ - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + ""; /** @@ -77,22 +44,6 @@ this.entities = []; this.timer = undefined; this.allowGarrisoning = new Map(); - this.visibleGarrisonPoints = []; - if (!this.template.VisibleGarrisonPoints) - return; - - let points = this.template.VisibleGarrisonPoints; - for (let point in points) - this.visibleGarrisonPoints.push({ - "offset": { - "x": +points[point].X, - "y": +points[point].Y, - "z": +points[point].Z - }, - "allowedClasses": points[point].AllowedClasses, - "angle": points[point].Angle ? +points[point].Angle * Math.PI / 180 : null, - "entity": null - }); }; /** @@ -197,29 +148,12 @@ }; /** - * @param {number} entity - The entity's id. - * @param {Object|undefined} visibleGarrisonPoint - The vgp object. - * @return {boolean} - Whether the unit is allowed be visible on that garrison point. - */ -GarrisonHolder.prototype.AllowedToVisibleGarrisoning = function(entity, visibleGarrisonPoint) -{ - if (!visibleGarrisonPoint) - return false; - - if (!visibleGarrisonPoint.allowedClasses) - return true; - - let cmpIdentity = Engine.QueryInterface(entity, IID_Identity); - return cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), visibleGarrisonPoint.allowedClasses._string); -}; - -/** * Garrison a unit inside. The timer for AutoHeal is started here. * @param {number} vgpEntity - The visual garrison point that will be used. * If vgpEntity is given, this visualGarrisonPoint will be used for the entity. * @return {boolean} - Whether the entity was garrisoned. */ -GarrisonHolder.prototype.Garrison = function(entity, vgpEntity) +GarrisonHolder.prototype.Garrison = function(entity) { if (!this.IsAllowedToGarrison(entity)) return false; @@ -227,10 +161,6 @@ if (!this.HasEnoughHealth()) return false; - let cmpPosition = Engine.QueryInterface(entity, IID_Position); - if (!cmpPosition) - return false; - if (!this.timer && this.GetHealRate() > 0) { let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); @@ -247,52 +177,16 @@ if (cmpAura && cmpAura.HasGarrisonAura()) cmpAura.ApplyGarrisonAura(this.entity); - let visibleGarrisonPoint; - if (vgpEntity && this.AllowedToVisibleGarrisoning(entity, vgpEntity)) - visibleGarrisonPoint = vgpEntity; - - if (!visibleGarrisonPoint) - visibleGarrisonPoint = this.visibleGarrisonPoints.find(vgp => !vgp.entity && this.AllowedToVisibleGarrisoning(entity, vgp)); - - let isVisiblyGarrisoned = false; - if (visibleGarrisonPoint) - { - visibleGarrisonPoint.entity = entity; - // Angle of turrets: - // Renamed entities (vgpEntity != undefined) should keep their angle. - // Otherwise if an angle is given in the visibleGarrisonPoint, use it. - // If no such angle given (usually walls for which outside/inside not well defined), we keep - // the current angle as it was used for garrisoning and thus quite often was from inside to - // outside, except when garrisoning from outWorld where we take as default PI. - let cmpTurretPosition = Engine.QueryInterface(this.entity, IID_Position); - if (!vgpEntity && visibleGarrisonPoint.angle != null) - cmpPosition.SetYRotation(cmpTurretPosition.GetRotation().y + visibleGarrisonPoint.angle); - else if (!vgpEntity && !cmpPosition.IsInWorld()) - cmpPosition.SetYRotation(cmpTurretPosition.GetRotation().y + Math.PI); - let cmpUnitMotion = Engine.QueryInterface(entity, IID_UnitMotion); - if (cmpUnitMotion) - cmpUnitMotion.SetFacePointAfterMove(false); - cmpPosition.SetTurretParent(this.entity, visibleGarrisonPoint.offset); - let cmpUnitAI = Engine.QueryInterface(entity, IID_UnitAI); - if (cmpUnitAI) - cmpUnitAI.SetTurretStance(); - - // Remove the unit's obstruction to avoid interfering with pathing. - let cmpObstruction = Engine.QueryInterface(entity, IID_Obstruction); - if (cmpObstruction) - cmpObstruction.SetActive(false); - - isVisiblyGarrisoned = true; - } - else + let cmpPosition = Engine.QueryInterface(entity, IID_Position); + if (cmpPosition) cmpPosition.MoveOutOfWorld(); - // Should only be called after the garrison has been performed else the visible Garrison Points are not updated yet. + let cmpTurretHolder = Engine.QueryInterface(this.entity, IID_TurretHolder); Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { "added": [entity], "removed": [], "visible": { - [entity]: isVisiblyGarrisoned, + [entity]: cmpTurretHolder && cmpTurretHolder.OccupyTurret(entity), } }); @@ -340,25 +234,6 @@ let cmpEntPosition = Engine.QueryInterface(entity, IID_Position); let cmpEntUnitAI = Engine.QueryInterface(entity, IID_UnitAI); - // Needs to be set before the visible garrison points are cleared. - let visible = { - [entity]: this.IsVisiblyGarrisoned(entity) - }; - - for (let vgp of this.visibleGarrisonPoints) - { - if (vgp.entity != entity) - continue; - cmpEntPosition.SetTurretParent(INVALID_ENTITY, new Vector3D()); - let cmpEntUnitMotion = Engine.QueryInterface(entity, IID_UnitMotion); - if (cmpEntUnitMotion) - cmpEntUnitMotion.SetFacePointAfterMove(true); - if (cmpEntUnitAI) - cmpEntUnitAI.ResetTurretStance(); - vgp.entity = null; - break; - } - // Reset the obstruction flags to template defaults. let cmpObstruction = Engine.QueryInterface(entity, IID_Obstruction); if (cmpObstruction) @@ -375,17 +250,23 @@ if (cmpEntAura && cmpEntAura.HasGarrisonAura()) cmpEntAura.RemoveGarrisonAura(this.entity); - cmpEntPosition.JumpTo(pos.x, pos.z); - cmpEntPosition.SetHeightOffset(0); + if (cmpEntPosition) + { + cmpEntPosition.JumpTo(pos.x, pos.z); + cmpEntPosition.SetHeightOffset(0); - let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); - if (cmpPosition) - cmpEntPosition.SetYRotation(cmpPosition.GetPosition().horizAngleTo(pos)); + let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); + if (cmpPosition) + cmpEntPosition.SetYRotation(cmpPosition.GetPosition().horizAngleTo(pos)); + } + let cmpTurretHolder = Engine.QueryInterface(this.entity, IID_TurretHolder); Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { "added": [], "removed": [entity], - "visible": visible + "visible": { + [entity]: cmpTurretHolder && cmpTurretHolder.LeaveTurret(entity) + } }); return true; @@ -626,18 +507,15 @@ if (cmpHealth && cmpHealth.GetHitpoints() == 0) { this.entities.splice(entityIndex, 1); + let cmpTurretHolder = Engine.QueryInterface(this.entity, IID_TurretHolder); Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { "added": [], "removed": [msg.entity], "visible": { - [msg.entity]: this.IsVisiblyGarrisoned(msg.entity) + [msg.entity]: cmpTurretHolder && cmpTurretHolder.LeaveTurret(msg.entity) } }); this.UpdateGarrisonFlag(); - - for (let point of this.visibleGarrisonPoints) - if (point.entity == msg.entity) - point.entity = null; } else if (msg.to == INVALID_PLAYER || !IsOwnedByMutualAllyOfEntity(this.entity, msg.entity)) this.EjectOrKill([msg.entity]); @@ -652,16 +530,8 @@ let entityIndex = this.entities.indexOf(msg.entity); if (entityIndex != -1) { - let vgpRenamed; - for (let vgp of this.visibleGarrisonPoints) - { - if (vgp.entity != msg.entity) - continue; - vgpRenamed = vgp; - break; - } this.Eject(msg.entity, true); - this.Garrison(msg.newentity, vgpRenamed); + this.Garrison(msg.newentity); } if (!this.initGarrison) @@ -722,9 +592,10 @@ if (killedEntities.length) { + let cmpTurretHolder = Engine.QueryInterface(this.entity, IID_TurretHolder); let visibleEntitiesIds = {}; for (let ent of killedEntities) - visibleEntitiesIds[ent] = this.IsVisiblyGarrisoned(ent); + visibleEntitiesIds[ent] = cmpTurretHolder && cmpTurretHolder.OccupiesTurret(ent); Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { "added": [], "removed": killedEntities, @@ -735,16 +606,6 @@ }; /** - * Gives insight about the unit type of garrisoning. - * @param {number} entity - The entity's id. - * @return {boolean} - Whether the entity is visible on the garrison holder. - */ -GarrisonHolder.prototype.IsVisiblyGarrisoned = function(entity) -{ - return this.visibleGarrisonPoints.some(point => point.entity == entity); -}; - -/** * Whether an entity is ejectable. * @param {number} entity - The entity-ID to be tested. * @return {boolean} - Whether the entity is ejectable. Index: binaries/data/mods/public/simulation/components/TurretHolder.js =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/components/TurretHolder.js @@ -0,0 +1,184 @@ +/** + * This class holds the functions regarding entities being visible on + * another entity, but tied to their location. + * Currently renaming and changing ownership are still managed by GarrisonHolder.js, + * but in the future these components could be independent. + */ +class TurretHolder +{ + Init() + { + this.turretPoints = []; + + let points = this.template.TurretPoints; + for (let point in points) + this.turretPoints.push({ + "offset": { + "x": +points[point].X, + "y": +points[point].Y, + "z": +points[point].Z + }, + "allowedClasses": points[point].AllowedClasses, + "angle": points[point].Angle ? +points[point].Angle * Math.PI / 180 : null, + "entity": null + }); + } + + /** + * @param {number} entity - The entity to check for. + * @param {Object} turretPoint - The turret point to use. + * + * @return {boolean} - Whether the entity is allowed to occupy the specified turret point. + */ + AllowedToOccupyTurret(entity, turretPoint) + { + if (!turretPoint || turretPoint.entity) + return false; + + if (!IsOwnedByMutualAllyOfEntity(entity, this.entity)) + return false; + + if (!turretPoint.allowedClasses) + return true; + + let cmpIdentity = Engine.QueryInterface(entity, IID_Identity); + return cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), turretPoint.allowedClasses._string); + } + + /** + * Occupy a turret point with the given entity. + * @param {number} entity - The entity to use. + * @param {Object} turretPoint - Optionally the specific turret point to occupy. + * + * @return {boolean} - Whether the occupation was successful. + */ + OccupyTurret(entity, requestedTurretPoint) + { + let cmpPositionOccupant = Engine.QueryInterface(entity, IID_Position); + if (!cmpPositionOccupant) + return false; + + let cmpPositionSelf = Engine.QueryInterface(this.entity, IID_Position); + if (!cmpPositionSelf) + return false; + + let turretPoint; + if (requestedTurretPoint) + { + if (this.AllowedToOccupyTurret(entity, requestedTurretPoint)) + turretPoint = requestedTurretPoint; + } + else + turretPoint = this.turretPoints.find(turret => !turret.entity && this.AllowedToOccupyTurret(entity, turret)); + + if (!turretPoint) + return false; + + turretPoint.entity = entity; + // Angle of turrets: + // Renamed entities (turretPoint != undefined) should keep their angle. + // Otherwise if an angle is given in the turretPoint, use it. + // If no such angle given (usually walls for which outside/inside not well defined), we keep + // the current angle as it was used for garrisoning and thus quite often was from inside to + // outside, except when garrisoning from outWorld where we take as default PI. + if (!turretPoint && turretPoint.angle != null) + cmpPositionOccupant.SetYRotation(cmpPositionSelf.GetRotation().y + turretPoint.angle); + else if (!turretPoint && !cmpPosition.IsInWorld()) + cmpPositionOccupant.SetYRotation(cmpPositionSelf.GetRotation().y + Math.PI); + + cmpPositionOccupant.SetTurretParent(this.entity, turretPoint.offset); + + let cmpUnitMotion = Engine.QueryInterface(entity, IID_UnitMotion); + if (cmpUnitMotion) + cmpUnitMotion.SetFacePointAfterMove(false); + + let cmpUnitAI = Engine.QueryInterface(entity, IID_UnitAI); + if (cmpUnitAI) + cmpUnitAI.SetTurretStance(); + + return true; + } + + /** + * Remove the entity from a turret. + * @param {number} entity - The specific entity to eject. + * @param {Object} turret - Optionally the turret to abandon. + * + * @return {boolean} - Whether the entity was occupying a/the turret before. + */ + LeaveTurret(entity, requestedTurretPoint) + { + let turretPoint; + if (requestedTurretPoint) + { + if (requestedTurretPoint.entity == entity) + turretPoint = requestedTurretPoint; + } + else + turretPoint = this.turretPoints.find(turret => turret.entity == entity); + + if (!turretPoint) + return false; + + let cmpPositionEntity = Engine.QueryInterface(entity, IID_Position); + cmpPositionEntity.SetTurretParent(INVALID_ENTITY, new Vector3D()); + + let cmpUnitMotionEntity = Engine.QueryInterface(entity, IID_UnitMotion); + if (cmpUnitMotionEntity) + cmpUnitMotionEntity.SetFacePointAfterMove(true); + + let cmpUnitAIEntity = Engine.QueryInterface(entity, IID_UnitAI); + if (cmpUnitAIEntity) + cmpUnitAIEntity.ResetTurretStance(); + + turretPoint.entity = null; + return true; + } + + /** + * @param {number} entity - The entity's id. + * @param {Object} turret - Optionally the turret to check. + * + * @return {boolean} - Whether the entity is positioned on a turret of this entity. + */ + OccupiesTurret(entity, requestedTurretPoint) + { + return requestedTurretPoint ? requestedTurretPoint.entity == entity : + this.turretPoints.some(turretPoint => turretPoint.entity == entity); + } +} + +TurretHolder.prototype.Schema = + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "tokens" + + "" + + "" + + "" + + ""+ + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""; + +Engine.RegisterComponentType(IID_TurretHolder, "TurretHolder", TurretHolder); Index: binaries/data/mods/public/simulation/components/interfaces/TurretHolder.js =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/components/interfaces/TurretHolder.js @@ -0,0 +1,7 @@ +Engine.RegisterInterface("TurretHolder"); + +/** + * Message of the form { "added": number[], "removed": number[] } + * sent from the TurretHolder component to the current entity whenever the turrets change. + */ +Engine.RegisterMessageType("TurretsChanged"); 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 @@ -2,6 +2,7 @@ Engine.LoadHelperScript("Player.js"); Engine.LoadComponentScript("interfaces/GarrisonHolder.js"); Engine.LoadComponentScript("interfaces/Garrisonable.js"); +Engine.LoadComponentScript("interfaces/TurretHolder.js"); Engine.LoadComponentScript("GarrisonHolder.js"); Engine.LoadComponentScript("interfaces/Auras.js"); Engine.LoadComponentScript("interfaces/Health.js"); @@ -73,11 +74,8 @@ "GetHeightOffset": () => 0, "GetPosition": () => new Vector3D(4, 3, 25), "GetRotation": () => new Vector3D(4, 0, 6), - "GetTurretParent": () => INVALID_ENTITY, - "IsInWorld": () => true, "JumpTo": (posX, posZ) => {}, "MoveOutOfWorld": () => {}, - "SetTurretParent": (entity, offset) => {}, "SetHeightOffset": height => {} }); } @@ -94,19 +92,7 @@ "EjectClassesOnDestroy": { "_string": "Infantry" }, "BuffHeal": 1, "LoadingRange": 2.1, - "Pickup": false, - "VisibleGarrisonPoints": { - "archer1": { - "X": 12, - "Y": 5, - "Z": 6 - }, - "archer2": { - "X": 15, - "Y": 5, - "Z": 6 - } - } + "Pickup": false }); let testGarrisonAllowed = function() @@ -182,23 +168,12 @@ "EjectClassesOnDestroy": { "_string": "Infantry" }, "BuffHeal": 1, "LoadingRange": 2.1, - "Pickup": false, - "VisibleGarrisonPoints": { - "archer1": { - "X": 12, - "Y": 5, - "Z": 6 - }, - "archer2": { - "X": 15, - "Y": 5, - "Z": 6 - } - } + "Pickup": false }); testGarrisonAllowed(); +// Test entity renaming. let siegeEngineId = 44; AddMock(siegeEngineId, IID_Identity, { "GetClassesList": () => ["Siege"] @@ -208,7 +183,6 @@ "GetClassesList": () => ["Infantry", "Ranged"] }); -// Test visible garrisoning restrictions. cmpGarrisonHolder = ConstructComponent(garrisonHolderId, "GarrisonHolder", { "Max": 10, "List": { "_string": "Infantry+Ranged Siege Cavalry" }, @@ -216,55 +190,19 @@ "EjectClassesOnDestroy": { "_string": "Infantry" }, "BuffHeal": 1, "LoadingRange": 2.1, - "Pickup": false, - "VisibleGarrisonPoints": { - "archer1": { - "X": 12, - "Y": 5, - "Z": 6 - }, - "archer2": { - "X": 15, - "Y": 5, - "Z": 6, - "AllowedClasses": { "_string": "Siege Trader" } - }, - "archer3": { - "X": 15, - "Y": 5, - "Z": 6, - "AllowedClasses": { "_string": "Siege Infantry+Ranged Infantry+Cavalry" } - } - } + "Pickup": false }); AddMock(32, IID_Identity, { "GetClassesList": () => ["Trader"] }); -TS_ASSERT_EQUALS(cmpGarrisonHolder.Garrison(32), false); -TS_ASSERT_EQUALS(cmpGarrisonHolder.AllowedToVisibleGarrisoning(siegeEngineId, cmpGarrisonHolder.visibleGarrisonPoints[0]), true); -TS_ASSERT_EQUALS(cmpGarrisonHolder.AllowedToVisibleGarrisoning(siegeEngineId, cmpGarrisonHolder.visibleGarrisonPoints[1]), true); -TS_ASSERT_EQUALS(cmpGarrisonHolder.AllowedToVisibleGarrisoning(siegeEngineId, cmpGarrisonHolder.visibleGarrisonPoints[2]), true); -TS_ASSERT_EQUALS(cmpGarrisonHolder.AllowedToVisibleGarrisoning(archerId, cmpGarrisonHolder.visibleGarrisonPoints[0]), true); -TS_ASSERT_EQUALS(cmpGarrisonHolder.AllowedToVisibleGarrisoning(archerId, cmpGarrisonHolder.visibleGarrisonPoints[1]), false); -TS_ASSERT_EQUALS(cmpGarrisonHolder.AllowedToVisibleGarrisoning(archerId, cmpGarrisonHolder.visibleGarrisonPoints[2]), true); -TS_ASSERT_EQUALS(cmpGarrisonHolder.AllowedToVisibleGarrisoning(33, cmpGarrisonHolder.visibleGarrisonPoints[0]), true); -TS_ASSERT_EQUALS(cmpGarrisonHolder.AllowedToVisibleGarrisoning(33, cmpGarrisonHolder.visibleGarrisonPoints[1]), false); -TS_ASSERT_EQUALS(cmpGarrisonHolder.AllowedToVisibleGarrisoning(33, cmpGarrisonHolder.visibleGarrisonPoints[2]), true); - -// If an entity gets renamed (e.g. promotion, upgrade) -// and is no longer able to be visibly garrisoned it -// should be garisoned instead or ejected. AddMock(siegeEngineId, IID_Position, { "GetHeightOffset": () => 0, "GetPosition": () => new Vector3D(4, 3, 25), "GetRotation": () => new Vector3D(4, 0, 6), - "GetTurretParent": () => INVALID_ENTITY, - "IsInWorld": () => true, "JumpTo": (posX, posZ) => {}, "MoveOutOfWorld": () => {}, - "SetTurretParent": (entity, offset) => {}, "SetHeightOffset": height => {} }); let currentSiegePlayer = player; @@ -280,11 +218,8 @@ "GetHeightOffset": () => 0, "GetPosition": () => new Vector3D(4, 3, 25), "GetRotation": () => new Vector3D(4, 0, 6), - "GetTurretParent": () => INVALID_ENTITY, - "IsInWorld": () => true, "JumpTo": (posX, posZ) => {}, "MoveOutOfWorld": () => {}, - "SetTurretParent": (entity, offset) => {}, "SetHeightOffset": height => {} }); @@ -295,14 +230,11 @@ AddMock(cavalryId, IID_Garrisonable, {}); TS_ASSERT(cmpGarrisonHolder.Garrison(siegeEngineId)); TS_ASSERT_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), 1); -TS_ASSERT(cmpGarrisonHolder.IsVisiblyGarrisoned(siegeEngineId)); cmpGarrisonHolder.OnGlobalEntityRenamed({ "entity": siegeEngineId, "newentity": cavalryId }); TS_ASSERT_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), 1); -TS_ASSERT(!cmpGarrisonHolder.IsVisiblyGarrisoned(siegeEngineId)); -TS_ASSERT(!cmpGarrisonHolder.IsVisiblyGarrisoned(archerId)); // Eject enemy units. currentCavalryPlayer = enemyPlayer; @@ -311,14 +243,3 @@ "to": enemyPlayer }); TS_ASSERT_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), 0); - -// Visibly garrisoned units should get ejected if they change players. -TS_ASSERT(cmpGarrisonHolder.Garrison(siegeEngineId)); -TS_ASSERT(cmpGarrisonHolder.IsVisiblyGarrisoned(siegeEngineId)); -TS_ASSERT_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), 1); -currentSiegePlayer = enemyPlayer; -cmpGarrisonHolder.OnGlobalOwnershipChanged({ - "entity": siegeEngineId, - "to": enemyPlayer -}); -TS_ASSERT_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), 0); Index: binaries/data/mods/public/simulation/components/tests/test_TurretHolder.js =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/components/tests/test_TurretHolder.js @@ -0,0 +1,123 @@ +Engine.LoadHelperScript("Player.js"); +Engine.LoadComponentScript("interfaces/TurretHolder.js"); +Engine.LoadComponentScript("interfaces/UnitAI.js"); +Engine.LoadComponentScript("TurretHolder.js"); + +AddMock(SYSTEM_ENTITY, IID_PlayerManager, { + "GetPlayerByID": id => id +}); + +const player = 1; +const enemyPlayer = 2; +const alliedPlayer = 3; +const turretHolderID = 9; +const entitiesToTest = [10, 11, 12]; + +AddMock(turretHolderID, IID_Ownership, { + "GetOwner": () => player +}); +AddMock(turretHolderID, IID_Position, { + "GetPosition": () => new Vector3D(4, 3, 25), + "GetRotation": () => new Vector3D(4, 0, 6), + "IsInWorld": () => true +}); + +for (let entity of entitiesToTest) +{ + AddMock(entity, IID_Position, { + "GetPosition": () => new Vector3D(4, 3, 25), + "GetRotation": () => new Vector3D(4, 0, 6), + "SetTurretParent": (entity, offset) => {}, + "IsInWorld": () => true + }); + + AddMock(entity, IID_Ownership, { + "GetOwner": () => player + }); +} + +AddMock(player, IID_Player, { + "IsAlly": id => id != enemyPlayer, + "IsMutualAlly": id => id != enemyPlayer, + "GetPlayerID": () => player +}); + +AddMock(alliedPlayer, IID_Player, { + "IsAlly": id => true, + "IsMutualAlly": id => true, + "GetPlayerID": () => alliedPlayer +}); + +let cmpTurretHolder = ConstructComponent(turretHolderID, "TurretHolder", { + "TurretPoints": { + "archer1": { + "X": "12.0", + "Y": "5.", + "Z": "6.0" + }, + "archer2": { + "X": "15.0", + "Y": "5.0", + "Z": "6.0", + "AllowedClasses": { "_string": "Siege Trader" } + }, + "archer3": { + "X": "15.0", + "Y": "5.0", + "Z": "6.0", + "AllowedClasses": { "_string": "Siege Infantry+Ranged Infantry+Cavalry" } + } + } +}); + +let siegeEngineID = entitiesToTest[0]; +AddMock(siegeEngineID, IID_Identity, { + "GetClassesList": () => ["Siege"] +}); + +let archerID = entitiesToTest[1]; +AddMock(archerID, IID_Identity, { + "GetClassesList": () => ["Infantry", "Ranged"] +}); + +let cavID = entitiesToTest[2]; +AddMock(cavID, IID_Identity, { + "GetClassesList": () => ["Infantry", "Cavalry"] +}); + +// Test visible garrisoning restrictions. +TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurret(siegeEngineID, cmpTurretHolder.turretPoints[0]), true); +TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurret(siegeEngineID, cmpTurretHolder.turretPoints[1]), true); +TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurret(siegeEngineID, cmpTurretHolder.turretPoints[2]), true); +TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurret(archerID, cmpTurretHolder.turretPoints[0]), true); +TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurret(archerID, cmpTurretHolder.turretPoints[1]), false); +TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurret(archerID, cmpTurretHolder.turretPoints[2]), true); +TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurret(cavID, cmpTurretHolder.turretPoints[0]), true); +TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurret(cavID, cmpTurretHolder.turretPoints[1]), false); +TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurret(cavID, cmpTurretHolder.turretPoints[2]), true); + +// Test that one cannot leave a turret that is not occupied. +TS_ASSERT(!cmpTurretHolder.LeaveTurret(archerID)); + +// Test occupying a turret. +TS_ASSERT(!cmpTurretHolder.OccupiesTurret(archerID)); +TS_ASSERT(cmpTurretHolder.OccupyTurret(archerID)); +TS_ASSERT(cmpTurretHolder.OccupiesTurret(archerID)); + +// We're not occupying a turret that we can't occupy. +TS_ASSERT(!cmpTurretHolder.OccupiesTurret(archerID, cmpTurretHolder.turretPoints[1])); +TS_ASSERT(!cmpTurretHolder.OccupyTurret(cavID, cmpTurretHolder.turretPoints[1])); +TS_ASSERT(!cmpTurretHolder.OccupyTurret(cavID, cmpTurretHolder.turretPoints[0])); +TS_ASSERT(cmpTurretHolder.OccupyTurret(cavID, cmpTurretHolder.turretPoints[2])); + +// Leave turrets. +TS_ASSERT(cmpTurretHolder.LeaveTurret(archerID)); +TS_ASSERT(cmpTurretHolder.LeaveTurret(cavID, cmpTurretHolder.turretPoints[2])); + +// ToDo: +// If an entity gets renamed (e.g. promotion, upgrade) +// and is no longer able to be visibly garrisoned it +// should be garisoned instead or ejected. +// This is currently tied to GarrisonHolder.js but ought to be decoupled, +// so that it can be tested here. +// Idem for ownership changes. Index: binaries/data/mods/public/simulation/templates/structures/athen_wall_long.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/athen_wall_long.xml +++ binaries/data/mods/public/simulation/templates/structures/athen_wall_long.xml @@ -4,8 +4,8 @@ 9.0 - - + + 0 11.5 @@ -31,8 +31,8 @@ 11.5 0 - - + + athen Teichos Index: binaries/data/mods/public/simulation/templates/structures/athen_wall_medium.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/athen_wall_medium.xml +++ binaries/data/mods/public/simulation/templates/structures/athen_wall_medium.xml @@ -4,8 +4,8 @@ 12.5 - - + + 0 11.5 @@ -21,8 +21,8 @@ 11.5 0 - - + + athen Teichos Index: binaries/data/mods/public/simulation/templates/structures/brit_wall_long.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/brit_wall_long.xml +++ binaries/data/mods/public/simulation/templates/structures/brit_wall_long.xml @@ -4,8 +4,8 @@ 10.3 - - + + 0 9.3 @@ -31,8 +31,8 @@ 9.3 0 - - + + brit Rate Index: binaries/data/mods/public/simulation/templates/structures/brit_wall_medium.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/brit_wall_medium.xml +++ binaries/data/mods/public/simulation/templates/structures/brit_wall_medium.xml @@ -4,8 +4,8 @@ 10.3 - - + + 0 9.3 @@ -21,8 +21,8 @@ 9.3 0 - - + + brit Rate Index: binaries/data/mods/public/simulation/templates/structures/cart_wall_long.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/cart_wall_long.xml +++ binaries/data/mods/public/simulation/templates/structures/cart_wall_long.xml @@ -4,8 +4,8 @@ 13 - - + + 0 12 @@ -31,8 +31,8 @@ 12 0 - - + + cart Homah Index: binaries/data/mods/public/simulation/templates/structures/cart_wall_medium.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/cart_wall_medium.xml +++ binaries/data/mods/public/simulation/templates/structures/cart_wall_medium.xml @@ -4,8 +4,8 @@ 13 - - + + 0 12 @@ -21,8 +21,8 @@ 12 0 - - + + cart Homah Index: binaries/data/mods/public/simulation/templates/structures/gaul_wall_long.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/gaul_wall_long.xml +++ binaries/data/mods/public/simulation/templates/structures/gaul_wall_long.xml @@ -4,8 +4,8 @@ 10.3 - - + + 0 9.3 @@ -31,8 +31,8 @@ 9.3 0 - - + + gaul Rate Index: binaries/data/mods/public/simulation/templates/structures/gaul_wall_medium.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/gaul_wall_medium.xml +++ binaries/data/mods/public/simulation/templates/structures/gaul_wall_medium.xml @@ -4,8 +4,8 @@ 10.3 - - + + 0 9.3 @@ -21,8 +21,8 @@ 9.3 0 - - + + gaul Rate Index: binaries/data/mods/public/simulation/templates/structures/iber_wall_long.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/iber_wall_long.xml +++ binaries/data/mods/public/simulation/templates/structures/iber_wall_long.xml @@ -4,8 +4,8 @@ 10 - - + + 0 9 @@ -31,8 +31,8 @@ 9 0 - - + + iber Zabal Horma Index: binaries/data/mods/public/simulation/templates/structures/iber_wall_medium.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/iber_wall_medium.xml +++ binaries/data/mods/public/simulation/templates/structures/iber_wall_medium.xml @@ -4,8 +4,8 @@ 10 - - + + 0 9 @@ -21,8 +21,8 @@ 9 0 - - + + iber Zabal Horma Index: binaries/data/mods/public/simulation/templates/structures/kush_wall_long.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/kush_wall_long.xml +++ binaries/data/mods/public/simulation/templates/structures/kush_wall_long.xml @@ -4,8 +4,8 @@ 12.6 - - + + 0 11.6 @@ -31,8 +31,8 @@ 11.6 0 - - + + kush sbty Index: binaries/data/mods/public/simulation/templates/structures/kush_wall_medium.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/kush_wall_medium.xml +++ binaries/data/mods/public/simulation/templates/structures/kush_wall_medium.xml @@ -4,8 +4,8 @@ 12.6 - - + + 0 11.6 @@ -21,8 +21,8 @@ 11.6 0 - - + + kush sbty Index: binaries/data/mods/public/simulation/templates/structures/mace_wall_long.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/mace_wall_long.xml +++ binaries/data/mods/public/simulation/templates/structures/mace_wall_long.xml @@ -4,8 +4,8 @@ 12.5 - - + + 0 11.5 @@ -31,8 +31,8 @@ 11.5 0 - - + + mace Teichos Index: binaries/data/mods/public/simulation/templates/structures/mace_wall_medium.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/mace_wall_medium.xml +++ binaries/data/mods/public/simulation/templates/structures/mace_wall_medium.xml @@ -4,8 +4,8 @@ 12.5 - - + + 0 11.5 @@ -21,8 +21,8 @@ 11.5 0 - - + + mace Teichos Index: binaries/data/mods/public/simulation/templates/structures/maur_tower_double.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/maur_tower_double.xml +++ binaries/data/mods/public/simulation/templates/structures/maur_tower_double.xml @@ -23,7 +23,9 @@ Infantry+Archer 20 - + + + 212.50 @@ -72,8 +74,8 @@ -2.118.0-2.1 - - + + 1200 Index: binaries/data/mods/public/simulation/templates/structures/maur_wall_long.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/maur_wall_long.xml +++ binaries/data/mods/public/simulation/templates/structures/maur_wall_long.xml @@ -10,8 +10,8 @@ 10.5 - - + + 0 9.5 @@ -37,8 +37,8 @@ 9.5 0 - - + + maur Shilabanda Index: binaries/data/mods/public/simulation/templates/structures/maur_wall_medium.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/maur_wall_medium.xml +++ binaries/data/mods/public/simulation/templates/structures/maur_wall_medium.xml @@ -10,8 +10,8 @@ 10.5 - - + + 0 9.5 @@ -27,8 +27,8 @@ 9.5 0 - - + + maur Shilabanda Index: binaries/data/mods/public/simulation/templates/structures/pers_wall_long.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/pers_wall_long.xml +++ binaries/data/mods/public/simulation/templates/structures/pers_wall_long.xml @@ -4,8 +4,8 @@ 11.6 - - + + 0 10.6 @@ -31,8 +31,8 @@ 10.6 0 - - + + pers Para Index: binaries/data/mods/public/simulation/templates/structures/pers_wall_medium.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/pers_wall_medium.xml +++ binaries/data/mods/public/simulation/templates/structures/pers_wall_medium.xml @@ -4,8 +4,8 @@ 11.6 - - + + 0 10.6 @@ -21,8 +21,8 @@ 10.6 0 - - + + pers Para Index: binaries/data/mods/public/simulation/templates/structures/ptol_wall_long.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/ptol_wall_long.xml +++ binaries/data/mods/public/simulation/templates/structures/ptol_wall_long.xml @@ -4,8 +4,8 @@ 10.8 - - + + 0 9.8 @@ -31,8 +31,8 @@ 9.8 0 - - + + ptol Teichos Index: binaries/data/mods/public/simulation/templates/structures/ptol_wall_medium.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/ptol_wall_medium.xml +++ binaries/data/mods/public/simulation/templates/structures/ptol_wall_medium.xml @@ -4,8 +4,8 @@ 10.8 - - + + 0 9.8 @@ -21,8 +21,8 @@ 9.8 0 - - + + ptol Teichos Index: binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_long.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_long.xml +++ binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_long.xml @@ -23,8 +23,8 @@ 6.7 - - + + 0 5.7 @@ -50,8 +50,8 @@ 5.7 0 - - + + 0.75 Index: binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_medium.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_medium.xml +++ binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_medium.xml @@ -23,8 +23,8 @@ 6.7 - - + + 0 5.7 @@ -40,8 +40,8 @@ 5.7 0 - - + + 0.75 Index: binaries/data/mods/public/simulation/templates/structures/rome_wall_long.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/rome_wall_long.xml +++ binaries/data/mods/public/simulation/templates/structures/rome_wall_long.xml @@ -4,8 +4,8 @@ 9.9 - - + + 0 8.9 @@ -31,8 +31,8 @@ 8.9 0 - - + + rome Moenia Index: binaries/data/mods/public/simulation/templates/structures/rome_wall_medium.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/rome_wall_medium.xml +++ binaries/data/mods/public/simulation/templates/structures/rome_wall_medium.xml @@ -4,8 +4,8 @@ 9.9 - - + + 0 8.9 @@ -21,8 +21,8 @@ 8.9 0 - - + + rome Moenia Index: binaries/data/mods/public/simulation/templates/structures/sele_wall_long.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/sele_wall_long.xml +++ binaries/data/mods/public/simulation/templates/structures/sele_wall_long.xml @@ -4,8 +4,8 @@ 11.4 - - + + 0 10.4 @@ -31,8 +31,8 @@ 10.4 0 - - + + sele Teichos Index: binaries/data/mods/public/simulation/templates/structures/sele_wall_medium.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/sele_wall_medium.xml +++ binaries/data/mods/public/simulation/templates/structures/sele_wall_medium.xml @@ -4,8 +4,8 @@ 11.4 - - + + 0 10.4 @@ -21,8 +21,8 @@ 10.4 0 - - + + sele Teichos Index: binaries/data/mods/public/simulation/templates/structures/spart_wall_long.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/spart_wall_long.xml +++ binaries/data/mods/public/simulation/templates/structures/spart_wall_long.xml @@ -4,8 +4,8 @@ 12.5 - - + + 0 11.5 @@ -31,8 +31,8 @@ 11.5 0 - - + + spart Teichos Index: binaries/data/mods/public/simulation/templates/structures/spart_wall_medium.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/spart_wall_medium.xml +++ binaries/data/mods/public/simulation/templates/structures/spart_wall_medium.xml @@ -4,8 +4,8 @@ 12.5 - - + + 0 11.5 @@ -21,8 +21,8 @@ 11.5 0 - - + + spart Teichos