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 @@ -831,7 +831,7 @@ "getActionInfo": function(entState, targetState) { if (!entState.garrisonable || !targetState || !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"), { @@ -1360,7 +1360,7 @@ { unloadAll(); }, - "allowedPlayers": ["Player", "Ally"] + "allowedPlayers": ["Player", "Ally", "Neutral", "Enemy"] }, "unload-all-turrets": { 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" + + "" + + "" + + "" + + "" + ""; /** @@ -47,6 +61,7 @@ { this.entities = []; this.allowedClasses = ApplyValueModificationsToEntity("GarrisonHolder/List/_string", this.template.List._string, this.entity); + this.allowedPlayers = this.template.AllowedPlayers ? this.template.AllowedPlayers.split(/\s+/) : ["Player", "MutualAlly"]; }; /** @@ -80,6 +95,15 @@ }; /** + * What diplomatic stances are allowd to garrison. + * @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. */ @@ -168,7 +192,7 @@ GarrisonHolder.prototype.IsAllowedToBeGarrisoned = function(entity) { - if (!IsOwnedByMutualAllyOfEntity(entity, this.entity)) + if (!this.IsAllowedByDiplomacy(entity)) return false; let cmpIdentity = Engine.QueryInterface(entity, IID_Identity); @@ -325,6 +349,24 @@ return !cmpHealth || cmpHealth.GetHitpoints() > Math.floor(+this.template.EjectHealth * cmpHealth.GetMaxHitpoints()); }; +/** + * 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); + + const allowedPlayers = this.GetAllowedPlayers(); + return allowedPlayers.indexOf("Player") != -1 && IsOwnedByPlayer(Engine.QueryInterface(this.entity, IID_Ownership)?.GetOwner() || INVALID_PLAYER, 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); +}; + GarrisonHolder.prototype.StartTimer = function() { if (this.timer) @@ -395,7 +437,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); @@ -405,7 +447,7 @@ // or on some of its garrisoned units let entityIndex = this.entities.indexOf(msg.entity); - if (entityIndex != -1 && (msg.to == INVALID_PLAYER || !IsOwnedByMutualAllyOfEntity(this.entity, msg.entity))) + if (entityIndex != -1 && (msg.to == INVALID_PLAYER || !this.IsAllowedByDiplomacy(msg.entity))) this.EjectOrKill([msg.entity]); }; @@ -436,7 +478,7 @@ */ GarrisonHolder.prototype.OnDiplomacyChanged = function() { - this.EjectOrKill(this.entities.filter(ent => !IsOwnedByMutualAllyOfEntity(this.entity, ent))); + this.EjectOrKill(this.entities.filter(ent => !this.IsAllowedByDiplomacy(ent))); }; /** 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 @@ -401,7 +401,8 @@ "buffHeal": cmpGarrisonHolder.GetHealRate(), "allowedClasses": cmpGarrisonHolder.GetAllowedClasses(), "capacity": cmpGarrisonHolder.GetCapacity(), - "occupiedSlots": cmpGarrisonHolder.OccupiedSlots() + "occupiedSlots": cmpGarrisonHolder.OccupiedSlots(), + "allowedPlayers": cmpGarrisonHolder.GetAllowedPlayers() }; let cmpTurretHolder = Engine.QueryInterface(ent, IID_TurretHolder); 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 @@ -34,6 +34,7 @@ AddMock(player, IID_Player, { "IsAlly": id => id != enemyPlayer, "IsMutualAlly": id => id != enemyPlayer, + "IsEnemy": id => id == enemyPlayer, "GetPlayerID": () => player }); @@ -289,3 +290,29 @@ TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), []); TS_ASSERT(!cmpGarrisonHolder.Garrison(siegeEngineId)); TS_ASSERT(cmpGarrisonHolder.Garrison(traderId)); + +// 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 @@ -482,13 +482,6 @@ "garrison": function(player, cmd, data) { - if (!CanPlayerOrAllyControlUnit(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, cmd, data.formation).forEach(cmpUnitAI => { cmpUnitAI.Garrison(cmd.target, cmd.queued, cmd.pushFront); }); 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 @@ -230,6 +230,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 @@ -306,6 +316,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); Index: binaries/data/mods/public/simulation/templates/template_structure_civic_house.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_structure_civic_house.xml +++ binaries/data/mods/public/simulation/templates/template_structure_civic_house.xml @@ -15,8 +15,9 @@ 0 0.1 Unit - Support+!Elephant + Unit 1 + Player Enemy 800