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 @@ -707,7 +707,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"), { 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" + + "" + + "" + + "" + + "" + ""; /** @@ -42,6 +56,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"]; }; /** @@ -75,6 +90,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. */ @@ -163,7 +187,7 @@ GarrisonHolder.prototype.IsAllowedToBeGarrisoned = function(entity) { - if (!IsOwnedByMutualAllyOfEntity(entity, this.entity)) + if (!this.IsAllowedByDiplomacy(entity)) return false; let cmpIdentity = Engine.QueryInterface(entity, IID_Identity); @@ -382,6 +406,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) @@ -437,7 +483,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); @@ -447,7 +493,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]); }; @@ -478,7 +524,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 @@ -379,7 +379,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/UnitAI.js =================================================================== --- binaries/data/mods/public/simulation/components/UnitAI.js +++ binaries/data/mods/public/simulation/components/UnitAI.js @@ -6323,7 +6323,7 @@ return false; let 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; return true; 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 @@ -35,6 +35,7 @@ AddMock(player, IID_Player, { "IsAlly": id => id != enemyPlayer, "IsMutualAlly": id => id != enemyPlayer, + "IsEnemy": id => id == enemyPlayer, "GetPlayerID": () => player }); @@ -290,3 +291,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 @@ -464,13 +464,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 @@ -287,6 +287,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 @@ -363,6 +373,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 @@ -19,8 +19,9 @@ 0 0.1 Unit - Support+!Elephant + Unit 1 + Player Enemy 800