Index: binaries/data/mods/public/gui/common/gamedescription.js =================================================================== --- binaries/data/mods/public/gui/common/gamedescription.js +++ binaries/data/mods/public/gui/common/gamedescription.js @@ -231,6 +231,18 @@ { "min": g_GameAttributes.settings.VictoryDuration } ); + else if (g_VictoryConditions.Name[victoryIdx] == "regicide") + if (g_GameAttributes.settings.RegicideGarrison) + titles.push({ + "label": translate("Hero Garrison: Enabled"), + "value": translate("Heroes can be garrisoned in structures.") + }); + else + titles.push({ + "label": translate("Hero Garrison: Disabled"), + "value": translate("Heroes cannot be garrisoned, and they are vulnerable to raids.") + }); + titles.push({ "label": title, "value": g_VictoryConditions.Description[victoryIdx] Index: binaries/data/mods/public/gui/gamesetup/gamesetup.js =================================================================== --- binaries/data/mods/public/gui/gamesetup/gamesetup.js +++ binaries/data/mods/public/gui/gamesetup/gamesetup.js @@ -321,6 +321,7 @@ "ceasefire", ], "Checkbox": [ + "regicideGarrison", "exploreMap", "revealMap", "disableTreasures", @@ -641,6 +642,18 @@ * Contains the logic of all boolean gamesettings. */ var g_Checkboxes = { + "regicideGarrison": { + "title": () => translate("Hero Garrison"), + "tooltip": () => translate("Toggle whether heroes can be garrisoned."), + "default": () => false, + "defined": () => g_GameAttributes.settings.RegicideGarrison !== undefined, + "get": () => g_GameAttributes.settings.RegicideGarrison, + "set": checked => { + g_GameAttributes.settings.RegicideGarrison = checked; + }, + "hidden": () => g_GameAttributes.settings.GameType != "regicide", + "enabled": () => g_GameAttributes.mapType != "scenario", + }, "revealMap": { "title": () => // Translation: Make sure to differentiate between the revealed map and explored map options! @@ -1509,6 +1522,7 @@ { delete g_GameAttributes.settings.VictoryDuration; delete g_GameAttributes.settings.LastManStanding; + delete g_GameAttributes.settings.RegicideGarrison; } if (mapSettings.PlayerData) 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 @@ -647,7 +647,7 @@ }, "getActionInfo": function(entState, targetState) { - if (!hasClass(entState, "Unit") || + if (!hasClass(entState, "Unit") || !entState.canGarrison || !targetState.garrisonHolder || !playerCheck(entState, targetState, ["Player", "MutualAlly"])) return false; Index: binaries/data/mods/public/maps/scripts/Regicide.js =================================================================== --- binaries/data/mods/public/maps/scripts/Regicide.js +++ binaries/data/mods/public/maps/scripts/Regicide.js @@ -6,6 +6,9 @@ Trigger.prototype.InitRegicideGame = function(msg) { + let cmpEndGameManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager); + this.regicideGarrison = cmpEndGameManager.GetGameTypeSettings().regicideGarrison; + let playersCivs = []; for (let playerID = 1; playerID < TriggerHelper.GetNumberOfPlayers(); ++playerID) playersCivs[playerID] = QueryPlayerIDInterface(playerID).GetCiv(); @@ -30,7 +33,7 @@ if (heroTemplates[identity.Civ].indexOf(templateName) == -1) heroTemplates[identity.Civ].push({ - "templateName": templateName, + "templateName": this.regicideGarrison ? templateName : "ungarrisonable|" + templateName, "classes": classes }); } @@ -51,6 +54,28 @@ return 0; }; + let cmpWaterManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_WaterManager); + let cmpTerritoryManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TerritoryManager); + + let gaiaSpawnPreferences = (shipEnt, entity) => { + + let cmpIdentity = Engine.QueryInterface(entity, IID_Identity); + let cmpPosition = Engine.QueryInterface(entity, IID_Position); + let cmpShipPosition = Engine.QueryInterface(shipEnt, IID_Position); + + if (!cmpIdentity || !cmpPosition || !cmpPosition.IsInWorld()) + return Infinity; + + let pos = cmpPosition.GetPosition(); + + // Do not spawn heroes underwater + if (pos.y <= cmpWaterManager.GetWaterLevel(pos.x, pos.z) || + cmpTerritoryManager.GetOwner(pos.x, pos.z) != 0) + return Infinity; + + return Engine.QueryInterface(shipEnt, IID_Position).GetPosition2D().distanceTo(Vector2D.from3D(pos)); + } + // Attempt to spawn one hero per player let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); for (let playerID = 1; playerID < TriggerHelper.GetNumberOfPlayers(); ++playerID) @@ -58,6 +83,11 @@ let spawnPoints = cmpRangeManager.GetEntitiesByPlayer(playerID).sort((entity1, entity2) => getSpawnPreference(entity2) - getSpawnPreference(entity1)); + // Spawn the hero on land as close as possible + if (!this.regicideGarrison && TriggerHelper.EntityHasClass(spawnPoints[0], "Ship")) + spawnPoints = cmpRangeManager.GetEntitiesByPlayer(0).sort((entity1, entity2) => + gaiaSpawnPreferences(spawnPoints[0], entity1) - gaiaSpawnPreferences(spawnPoints[0], entity2)); + this.regicideHeroes[playerID] = this.SpawnRegicideHero(playerID, heroTemplates[playersCivs[playerID]], spawnPoints); } }; @@ -109,6 +139,7 @@ { let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger); cmpTrigger.regicideHeroes = []; + cmpTrigger.regicideGarrison = false; cmpTrigger.DoAfterDelay(0, "InitRegicideGame", {}); cmpTrigger.RegisterTrigger("OnOwnershipChanged", "CheckRegicideDefeat", { "enabled": true }); } Index: binaries/data/mods/public/simulation/ai/common-api/entity.js =================================================================== --- binaries/data/mods/public/simulation/ai/common-api/entity.js +++ binaries/data/mods/public/simulation/ai/common-api/entity.js @@ -768,6 +768,8 @@ "canGuard": function() { return this.get("UnitAI/CanGuard") === "true"; }, + "canGarrison": function() { return this.get("Garrisonable") !== "false"; }, + move: function(x, z, queued = false) { Engine.PostCommand(PlayerID,{"type": "walk", "entities": [this.id()], "x": x, "z": z, "queued": queued }); return this; Index: binaries/data/mods/public/simulation/ai/common-api/shared.js =================================================================== --- binaries/data/mods/public/simulation/ai/common-api/shared.js +++ binaries/data/mods/public/simulation/ai/common-api/shared.js @@ -114,6 +114,20 @@ this._derivedTemplates[name] = resource; return resource; } + else if (name.indexOf("ungarrisonable|") !== -1) + { + let base = this.GetTemplate(name.substr(15)); + + let ent = {}; + for (let key in base) + if (key !== "Garrisonable") + ent[key] = base[key]; + else + ent[key] = "false"; + + this._derivedTemplates[name] = ent; + return ent; + } error("Tried to retrieve invalid template '"+name+"'"); return null; Index: binaries/data/mods/public/simulation/ai/petra/garrisonManager.js =================================================================== --- binaries/data/mods/public/simulation/ai/petra/garrisonManager.js +++ binaries/data/mods/public/simulation/ai/petra/garrisonManager.js @@ -198,7 +198,7 @@ /** This is just a pre-garrison state, while the entity walk to the garrison holder */ m.GarrisonManager.prototype.garrison = function(gameState, ent, holder, type) { - if (this.numberOfGarrisonedUnits(holder) >= holder.garrisonMax()) + if (this.numberOfGarrisonedUnits(holder) >= holder.garrisonMax() || !ent.canGarrison()) return; this.registerHolder(gameState, holder); Index: binaries/data/mods/public/simulation/ai/petra/navalManager.js =================================================================== --- binaries/data/mods/public/simulation/ai/petra/navalManager.js +++ binaries/data/mods/public/simulation/ai/petra/navalManager.js @@ -379,6 +379,9 @@ */ m.NavalManager.prototype.requireTransport = function(gameState, entity, startIndex, endIndex, endPos) { + if (!entity.canGarrison()) + return false; + if (entity.getMetadata(PlayerID, "transport") !== undefined) { if (this.Config.debug > 0) 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 @@ -200,7 +200,7 @@ if (!cmpIdentity) return false; var entityClasses = cmpIdentity.GetClassesList(); - return MatchesClassList(entityClasses, this.template.List._string); + return MatchesClassList(entityClasses, this.template.List._string) && Engine.QueryInterface(entity, IID_Garrisonable); }; /** 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 @@ -0,0 +1,11 @@ +function Garrisonable() {} + +Garrisonable.prototype.Schema = ""; + +Garrisonable.prototype.Init = function() +{ +}; + +Garrisonable.prototype.Serialize = null; + +Engine.RegisterComponentType(IID_Garrisonable, "Garrisonable", Garrisonable); 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 @@ -241,6 +241,7 @@ "alertRaiser": null, "builder": null, + "canGarrison": null, "identity": null, "fogging": null, "foundation": null, @@ -375,6 +376,8 @@ "garrisonedEntitiesCount": cmpGarrisonHolder.GetGarrisonedEntitiesCount() }; + ret.canGarrison = !!Engine.QueryInterface(ent, IID_Garrisonable); + let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); if (cmpUnitAI) ret.unitAI = { Index: binaries/data/mods/public/simulation/components/interfaces/Garrisonable.js =================================================================== --- binaries/data/mods/public/simulation/components/interfaces/Garrisonable.js +++ binaries/data/mods/public/simulation/components/interfaces/Garrisonable.js @@ -0,0 +1 @@ +Engine.RegisterInterface("Garrisonable"); Index: binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js +++ binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js @@ -10,6 +10,7 @@ Engine.LoadComponentScript("interfaces/EndGameManager.js"); Engine.LoadComponentScript("interfaces/EntityLimits.js"); Engine.LoadComponentScript("interfaces/Foundation.js"); +Engine.LoadComponentScript("interfaces/Garrisonable.js"); Engine.LoadComponentScript("interfaces/GarrisonHolder.js"); Engine.LoadComponentScript("interfaces/Gate.js"); Engine.LoadComponentScript("interfaces/Guard.js"); @@ -599,6 +600,7 @@ template: "example", alertRaiser: null, builder: true, + canGarrison: false, identity: { rank: "foo", classes: ["class1", "class2"], Index: binaries/data/mods/public/simulation/helpers/Setup.js =================================================================== --- binaries/data/mods/public/simulation/helpers/Setup.js +++ binaries/data/mods/public/simulation/helpers/Setup.js @@ -49,6 +49,8 @@ gameTypeSettings.relicCount = settings.RelicCount; if (settings.VictoryDuration) gameTypeSettings.victoryDuration = settings.VictoryDuration * 60 * 1000; + if (settings.RegicideGarrison) + gameTypeSettings.regicideGarrison = settings.RegicideGarrison; if (settings.GameType) cmpEndGameManager.SetGameType(settings.GameType, gameTypeSettings); Index: binaries/data/mods/public/simulation/templates/special_filter/ungarrisonable.xml =================================================================== --- binaries/data/mods/public/simulation/templates/special_filter/ungarrisonable.xml +++ binaries/data/mods/public/simulation/templates/special_filter/ungarrisonable.xml @@ -0,0 +1,4 @@ + + + + Index: binaries/data/mods/public/simulation/templates/template_unit.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_unit.xml +++ binaries/data/mods/public/simulation/templates/template_unit.xml @@ -27,6 +27,7 @@ 2.5 + corpse