Index: binaries/data/mods/public/gui/session/unit_actions.js =================================================================== --- binaries/data/mods/public/gui/session/unit_actions.js +++ binaries/data/mods/public/gui/session/unit_actions.js @@ -631,7 +631,7 @@ "getActionInfo": function(entState, targetState) { if (!entState.canGarrison || !targetState.garrisonHolder || - !playerCheck(entState, targetState, ["Player", "MutualAlly"])) + !playerCheck(entState, targetState, targetState.garrisonHolder.allowedPlayers)) return false; let tooltip = sprintf(translate("Current garrison: %(garrisoned)s/%(capacity)s"), { 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,6 +31,20 @@ "" + "" + "" + + "" + + "" + + "" + + "" + + "Player" + + "MutualAlly" + + "Neutral" + + "Enemy" + + "" + + "" + + "" + + "" + + "" + + "" + "" + "" + "" + @@ -65,6 +79,7 @@ { // Garrisoned Units this.entities = []; + this.allowedPlayers = this.template.AllowedPlayers ? this.template.AllowedPlayers.split(/\s+/) : ["Player", "MutualAlly"]; this.timer = undefined; this.allowGarrisoning = new Map(); this.visibleGarrisonPoints = []; @@ -106,6 +121,15 @@ }; /** + * What diplomatic stances are allowd to join. + * @return {string[]} An array containing the allowed diplomacies to garrison. + */ +GarrisonHolder.prototype.GetAllowedPlayers = function() +{ + return ApplyValueModificationsToEntity("GarrisonHolder/AllowedPlayers", this.allowedPlayers, this.entity); +}; + +/** * @return {Array} unit classes which can be garrisoned inside this * particular entity. Obtained from the entity's template. */ @@ -167,7 +191,7 @@ if (!this.IsGarrisoningAllowed()) return false; - if (!IsOwnedByMutualAllyOfEntity(ent, this.entity)) + if (!this.IsAllowedByDiplomacy(ent)) return false; let cmpIdentity = Engine.QueryInterface(ent, IID_Identity); @@ -509,6 +533,28 @@ }; /** + * Used to check if the garrisoning entity's diplomatic status is right for garrisoning this entity. + * @param {number} ent - The entity ID to check for. + * + * @return {boolean} - Whether garrisoning is allowed purely on diplomatic grounds. + */ +GarrisonHolder.prototype.IsAllowedByDiplomacy = function(ent) +{ + if (!this.template.AllowedPlayers) + return IsOwnedByMutualAllyOfEntity(this.entity, ent); + + let owner = INVALID_PLAYER; + let cmpOwner = Engine.QueryInterface(this.entity, IID_Ownership); + if (cmpOwner) + owner = cmpOwner.GetOwner(); + let allowedPlayers = this.GetAllowedPlayers(); + return allowedPlayers.indexOf("Player") != -1 && IsOwnedByPlayer(owner, ent) || + allowedPlayers.indexOf("MutualAlly") != -1 && IsOwnedByMutualAllyOfEntity(this.entity, ent) || + allowedPlayers.indexOf("Neutral") != -1 && IsOwnedByNeutralOfEntity(this.entity, ent) || + allowedPlayers.indexOf("Enemy") != -1 && IsOwnedByEnemyOfEntity(this.entity, ent); +}; + +/** * Called every second. Heals garrisoned units. */ GarrisonHolder.prototype.HealTimeout = function(data) @@ -564,7 +610,7 @@ // The ownership change may be on the garrisonholder if (this.entity == msg.entity) { - let entities = this.entities.filter(ent => msg.to == INVALID_PLAYER || !IsOwnedByMutualAllyOfEntity(this.entity, ent)); + let entities = this.entities.filter(ent => msg.to == INVALID_PLAYER || !this.IsAllowedByDiplomacy(ent)); if (entities.length) this.EjectOrKill(entities); @@ -588,7 +634,7 @@ if (point.entity == msg.entity) point.entity = null; } - else if (msg.to == INVALID_PLAYER || !IsOwnedByMutualAllyOfEntity(this.entity, msg.entity)) + else if (msg.to == INVALID_PLAYER || !this.IsAllowedByDiplomacy(msg.entity)) this.EjectOrKill([msg.entity]); } }; Index: binaries/data/mods/public/simulation/components/GuiInterface.js =================================================================== --- binaries/data/mods/public/simulation/components/GuiInterface.js +++ binaries/data/mods/public/simulation/components/GuiInterface.js @@ -360,7 +360,8 @@ "buffHeal": cmpGarrisonHolder.GetHealRate(), "allowedClasses": cmpGarrisonHolder.GetAllowedClasses(), "capacity": cmpGarrisonHolder.GetCapacity(), - "garrisonedEntitiesCount": cmpGarrisonHolder.GetGarrisonedEntitiesCount() + "garrisonedEntitiesCount": cmpGarrisonHolder.GetGarrisonedEntitiesCount(), + "allowedPlayers": cmpGarrisonHolder.GetAllowedPlayers() }; ret.canGarrison = !!Engine.QueryInterface(ent, IID_Garrisonable); Index: binaries/data/mods/public/simulation/components/UnitAI.js =================================================================== --- binaries/data/mods/public/simulation/components/UnitAI.js +++ binaries/data/mods/public/simulation/components/UnitAI.js @@ -5767,7 +5767,7 @@ // Verify that the target is owned by this entity's player or a mutual ally of this player var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); - if (!cmpOwnership || !(IsOwnedByPlayer(cmpOwnership.GetOwner(), target) || IsOwnedByMutualAllyOfPlayer(cmpOwnership.GetOwner(), target))) + if (!cmpOwnership || !cmpGarrisonHolder.IsAllowedByDiplomacy(this.entity)) return false; // Don't let animals garrison for now 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 @@ -33,14 +33,15 @@ }); AddMock(player, IID_Player, { - "IsAlly": id => true, - "IsMutualAlly": id => true, + "IsAlly": id => id != enemyPlayer, + "IsMutualAlly": id => id != enemyPlayer, + "IsEnemy": id => id == enemyPlayer, "GetPlayerID": () => player }); AddMock(friendlyPlayer, IID_Player, { - "IsAlly": id => true, - "IsMutualAlly": id => true, + "IsAlly": id => id != enemyPlayer, + "IsMutualAlly": id => id != enemyPlayer, "GetPlayerID": () => friendlyPlayer }); @@ -168,3 +169,29 @@ TS_ASSERT_EQUALS(cmpGarrisonHolder.IsFull(), false); TS_ASSERT_EQUALS(cmpGarrisonHolder.UnloadAll(), true); TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), []); + +// Test allowed players. +cmpGarrisonHolder = ConstructComponent(garrisonHolderId, "GarrisonHolder", { + "Max": 10, + "List": { "_string": "Infantry+Cavalry" }, + "EjectHealth": 0.1, + "EjectClassesOnDestroy": { "_string": "Infantry" }, + "BuffHeal": 1, + "LoadingRange": 2.1, + "Pickup": false, + "AllowedPlayers": "Enemy", + "VisibleGarrisonPoints": { + "archer1": { + "X": 12, + "Y": 5, + "Z": 6 + }, + "archer2": { + "X": 15, + "Y": 5, + "Z": 6 + } + } +}); +TS_ASSERT_EQUALS(cmpGarrisonHolder.IsAllowedToGarrison(enemyUnitId), true); +TS_ASSERT_EQUALS(cmpGarrisonHolder.IsAllowedToGarrison(unitToGarrisonId), false); Index: binaries/data/mods/public/simulation/helpers/Commands.js =================================================================== --- binaries/data/mods/public/simulation/helpers/Commands.js +++ binaries/data/mods/public/simulation/helpers/Commands.js @@ -468,14 +468,6 @@ "garrison": function(player, cmd, data) { - // Verify that the building can be controlled by the player or is mutualAlly - if (!CanControlUnitOrIsAlly(cmd.target, player, data.controlAllUnits)) - { - if (g_DebugCommands) - warn("Invalid command: garrison target cannot be controlled by player "+player+" (or ally): "+uneval(cmd)); - return; - } - GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => { cmpUnitAI.Garrison(cmd.target, cmd.queued); }); Index: binaries/data/mods/public/simulation/helpers/Player.js =================================================================== --- binaries/data/mods/public/simulation/helpers/Player.js +++ binaries/data/mods/public/simulation/helpers/Player.js @@ -277,6 +277,16 @@ return IsOwnedByEntityHelper(entity, target, "IsMutualAlly"); } +function IsOwnedByNeutralOfEntity(entity, target) +{ + return IsOwnedByEntityHelper(entity, target, "IsNeutral"); +} + +function IsOwnedByEnemyOfEntity(entity, target) +{ + return IsOwnedByEntityHelper(entity, target, "IsEnemy"); +} + function IsOwnedByEntityHelper(entity, target, check) { // Figure out which player controls us @@ -353,6 +363,8 @@ Engine.RegisterGlobal("QueryBuilderListInterface", QueryBuilderListInterface); Engine.RegisterGlobal("IsOwnedByAllyOfEntity", IsOwnedByAllyOfEntity); Engine.RegisterGlobal("IsOwnedByMutualAllyOfEntity", IsOwnedByMutualAllyOfEntity); +Engine.RegisterGlobal("IsOwnedByNeutralOfEntity", IsOwnedByNeutralOfEntity); +Engine.RegisterGlobal("IsOwnedByEnemyOfEntity", IsOwnedByEnemyOfEntity); Engine.RegisterGlobal("IsOwnedByPlayer", IsOwnedByPlayer); Engine.RegisterGlobal("IsOwnedByGaia", IsOwnedByGaia); Engine.RegisterGlobal("IsOwnedByAllyOfPlayer", IsOwnedByAllyOfPlayer);