From 7353985062975bfead0aa70713273cd06d31209c Mon Sep 17 00:00:00 2001 From: James Sherratt <jdsherratt3@gmail.com> Date: Thu, 3 Jun 2021 23:57:39 +0100 Subject: [PATCH] Add support for 0AD A25 (#7) * Add support for 0AD A25 * AllyMap -> AllyView + bump version. * All "Ally Map" changed to "Ally View". Also changed code names for consistency. * Bump version number * Add missing newlines (to meet unix file spec). * Remove pointless comments * Reduce code + more mod-friendly Rather than re-defining entire classes/ functions, just extend the code we want to extend (by e.g. saving game code temporarily an "old" variable and re-defining). Reduces code duplication + more friendly towards other mods that may want to modify the same classes/ functions. --- a25/README.md | 10 + .../common/gamedescription_CartographyMode.js | 254 ++++++++++++++++++ .../MapExploration_CartographyMode.js | 37 +++ .../GameSettingsLayout_CartographyMode.js | 1 + .../Single/Checkboxes/AlliedMap.js | 28 ++ .../Panels/GameDescription_CartographyMode.js | 8 + ...GB.public-gui-gamesetup-CartographyMode.po | 11 + a25/mod.json | 7 + a25/simulation/helpers/InitGame.js | 103 +++++++ 9 files changed, 459 insertions(+) create mode 100644 a25/README.md create mode 100644 a25/gui/common/gamedescription_CartographyMode.js create mode 100644 a25/gui/gamesettings/attributes/MapExploration_CartographyMode.js create mode 100644 a25/gui/gamesetup/Pages/GameSetupPage/GameSettings/GameSettingsLayout_CartographyMode.js create mode 100644 a25/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/AlliedMap.js create mode 100644 a25/gui/gamesetup/Pages/GameSetupPage/Panels/GameDescription_CartographyMode.js create mode 100644 a25/l10n/en_GB.public-gui-gamesetup-CartographyMode.po create mode 100644 a25/mod.json create mode 100644 a25/simulation/helpers/InitGame.js diff --git a/a25/README.md b/a25/README.md new file mode 100644 index 0000000..2d6299d --- /dev/null +++ b/a25/README.md @@ -0,0 +1,10 @@ +# CartographyMode + +A [0 A.D.](https://play0ad.com/) mod; adds an "ally view" option to the +game set-up menu, which allows allies to share vision at game start. + +[Forum post](https://wildfiregames.com/forum/index.php?/topic/27265-theres-any-mod-so-you-can-see-what-your-allies-see-from-the-start/&do=findComment&comment=397504) + +This mod was created by +[Jammyjamjamman](https://github.com/jammyjamjamman) and maintained by +him and [Andy Alt](https://github.com/andy5995). diff --git a/a25/gui/common/gamedescription_CartographyMode.js b/a25/gui/common/gamedescription_CartographyMode.js new file mode 100644 index 0000000..bc64832 --- /dev/null +++ b/a25/gui/common/gamedescription_CartographyMode.js @@ -0,0 +1,254 @@ +/** + * Sets an additional map label, map preview image and describes the chosen gamesettings more closely. + * + * Requires g_GameAttributes and g_VictoryConditions. + */ +function getGameDescriptionList(initAttributes, mapCache) +{ + let titles = []; + if (!initAttributes.settings.VictoryConditions.length) + titles.push({ + "label": translateWithContext("victory condition", "Endless Game"), + "value": translate("No winner will be determined, even if everyone is defeated.") + }); + + for (let victoryCondition of g_VictoryConditions) + { + if (initAttributes.settings.VictoryConditions.indexOf(victoryCondition.Name) == -1) + continue; + + let title = translateVictoryCondition(victoryCondition.Name); + if (victoryCondition.Name == "wonder") + { + let wonderDuration = Math.round(initAttributes.settings.WonderDuration); + title = sprintf( + translatePluralWithContext( + "victory condition", + "Wonder (%(min)s minute)", + "Wonder (%(min)s minutes)", + wonderDuration + ), + { "min": wonderDuration }); + } + + let isCaptureTheRelic = victoryCondition.Name == "capture_the_relic"; + if (isCaptureTheRelic) + { + let relicDuration = Math.round(initAttributes.settings.RelicDuration); + title = sprintf( + translatePluralWithContext( + "victory condition", + "Capture the Relic (%(min)s minute)", + "Capture the Relic (%(min)s minutes)", + relicDuration + ), + { "min": relicDuration }); + } + + titles.push({ + "label": title, + "value": victoryCondition.Description + }); + + if (isCaptureTheRelic) + titles.push({ + "label": translate("Relic Count"), + "value": Math.round(initAttributes.settings.RelicCount) + }); + + if (victoryCondition.Name == "regicide") + if (initAttributes.settings.RegicideGarrison) + titles.push({ + "label": translate("Hero Garrison"), + "value": translate("Heroes can be garrisoned.") + }); + else + titles.push({ + "label": translate("Exposed Heroes"), + "value": translate("Heroes cannot be garrisoned and they are vulnerable to raids.") + }); + } + + if (initAttributes.settings.RatingEnabled && + initAttributes.settings.PlayerData.length == 2) + titles.push({ + "label": translate("Rated game"), + "value": translate("When the winner of this match is determined, the lobby score will be adapted.") + }); + + if (initAttributes.settings.LockTeams) + titles.push({ + "label": translate("Locked Teams"), + "value": translate("Players can't change the initial teams.") + }); + else + titles.push({ + "label": translate("Diplomacy"), + "value": translate("Players can make alliances and declare war on allies.") + }); + + if (initAttributes.settings.LastManStanding) + titles.push({ + "label": translate("Last Man Standing"), + "value": translate("Only one player can win the game. If the remaining players are allies, the game continues until only one remains.") + }); + else + titles.push({ + "label": translate("Allied Victory"), + "value": translate("If one player wins, his or her allies win too. If one group of allies remains, they win.") + }); + + let ceasefire = Math.round(initAttributes.settings.Ceasefire); + titles.push({ + "label": translate("Ceasefire"), + "value": + !ceasefire ? + translate("disabled") : + sprintf(translatePlural( + "For the first minute, other players will stay neutral.", + "For the first %(min)s minutes, other players will stay neutral.", + ceasefire), + { "min": ceasefire }) + }); + + if (initAttributes.map == "random") + titles.push({ + "label": translateWithContext("Map Selection", "Random Map"), + "value": translate("Randomly select a map from the list.") + }); + else + { + titles.push({ + "label": translate("Map Name"), + "value": mapCache.translateMapName( + mapCache.getTranslatableMapName(initAttributes.mapType, initAttributes.map, initAttributes)) + }); + + titles.push({ + "label": translate("Map Description"), + "value": mapCache.getTranslatedMapDescription(initAttributes.mapType, initAttributes.map) + }); + } + + titles.push({ + "label": translate("Map Type"), + "value": g_MapTypes.Title[g_MapTypes.Name.indexOf(initAttributes.mapType)] + }); + + if (initAttributes.mapType == "random") + { + let mapSize = g_MapSizes.Name[g_MapSizes.Tiles.indexOf(initAttributes.settings.Size)]; + if (mapSize) + titles.push({ + "label": translate("Map Size"), + "value": mapSize + }); + } + + if (initAttributes.settings.Biome) + { + let biome = g_Settings.Biomes.find(b => b.Id == initAttributes.settings.Biome); + titles.push({ + "label": biome ? biome.Title : translateWithContext("biome", "Random Biome"), + "value": biome ? biome.Description : translate("Randomly select a biome from the list.") + }); + } + + if (initAttributes.settings.TriggerDifficulty !== undefined) + { + let triggerDifficulty = g_Settings.TriggerDifficulties.find(difficulty => difficulty.Difficulty == initAttributes.settings.TriggerDifficulty); + titles.push({ + "label": triggerDifficulty.Title, + "value": triggerDifficulty.Tooltip + }); + } + + if (initAttributes.settings.Nomad !== undefined) + titles.push({ + "label": initAttributes.settings.Nomad ? translate("Nomad Mode") : translate("Civic Centers"), + "value": + initAttributes.settings.Nomad ? + translate("Players start with only few units and have to find a suitable place to build their city.") : + translate("Players start with a Civic Center.") + }); + + if (initAttributes.settings.StartingResources !== undefined) + titles.push({ + "label": translate("Starting Resources"), + "value": + initAttributes.settings.PlayerData && + initAttributes.settings.PlayerData.some(pData => pData && pData.Resources !== undefined) ? + translateWithContext("starting resources", "Per Player") : + sprintf(translate("%(startingResourcesTitle)s (%(amount)s)"), { + "startingResourcesTitle": + g_StartingResources.Title[ + g_StartingResources.Resources.indexOf( + initAttributes.settings.StartingResources)], + "amount": initAttributes.settings.StartingResources + }) + }); + + if (initAttributes.settings.PopulationCap !== undefined) + titles.push({ + "label": translate("Population Limit"), + "value": + initAttributes.settings.PlayerData && + initAttributes.settings.PlayerData.some(pData => pData && pData.PopulationLimit !== undefined) ? + translateWithContext("population limit", "Per Player") : + g_PopulationCapacities.Title[ + g_PopulationCapacities.Population.indexOf( + initAttributes.settings.PopulationCap)] + }); + + if (initAttributes.settings.WorldPopulationCap !== undefined) + titles.push({ + "label": translate("World Population Cap"), + "value": + g_WorldPopulationCapacities.Title[ + g_WorldPopulationCapacities.Population.indexOf( + initAttributes.settings.WorldPopulationCap)] + }); + + titles.push({ + "label": translate("Treasures"), + "value": initAttributes.settings.DisableTreasures ? + translateWithContext("treasures", "Disabled") : + translateWithContext("treasures", "As defined by the map.") + }); + + titles.push({ + "label": translate("Explored Map"), + "value": initAttributes.settings.ExploreMap + }); + + titles.push({ + "label": translate("Revealed Map"), + "value": initAttributes.settings.RevealMap + }); + + titles.push({ + "label": translate("Cheats"), + "value": initAttributes.settings.CheatsEnabled + }); + + return titles; +} + +function modDescriptions(initAttributes, titles) { + titles.push({ + "label": translate("Allied View"), + "value": initAttributes.settings.AllyView + }); + return titles; +} + +function getGameDescription(initAttributes, mapCache) { + let titles = getGameDescriptionList(initAttributes, mapCache); + titles = modDescriptions(initAttributes, titles); + return titles.map(title => sprintf(translate("%(label)s %(details)s"), { + "label": coloredText(title.label, g_DescriptionHighlight), + "details": + title.value === true ? translateWithContext("game setup option", "enabled") : + title.value || translateWithContext("game setup option", "disabled") + })).join("\n"); +} diff --git a/a25/gui/gamesettings/attributes/MapExploration_CartographyMode.js b/a25/gui/gamesettings/attributes/MapExploration_CartographyMode.js new file mode 100644 index 0000000..606362a --- /dev/null +++ b/a25/gui/gamesettings/attributes/MapExploration_CartographyMode.js @@ -0,0 +1,37 @@ +{ + let initOld = GameSettings.prototype.Attributes.MapExploration.prototype.init; + GameSettings.prototype.Attributes.MapExploration.prototype.init = function () { + initOld.call(this); + this.allied = false; + } + + let toInitAttributesOld = GameSettings.prototype.Attributes.MapExploration.prototype.toInitAttributes; + GameSettings.prototype.Attributes.MapExploration.prototype.toInitAttributes = function (attribs) { + toInitAttributesOld.call(this, attribs); + attribs.settings.AllyView = this.allied; + } + + let fromInitAttributesOld = GameSettings.prototype.Attributes.MapExploration.prototype.fromInitAttributes; + GameSettings.prototype.Attributes.MapExploration.prototype.fromInitAttributes = function (attribs) { + fromInitAttributesOld.call(this, attribs); + this.allied = !!this.getLegacySetting(attribs, "AllyView"); + } + + let onMapChangeOld = GameSettings.prototype.Attributes.MapExploration.prototype.onMapChange; + GameSettings.prototype.Attributes.MapExploration.prototype.onMapChange = function (mapData) { + onMapChangeOld.call(this, mapData); + this.setRevealed(this.getMapSetting("AllyView")); + } + + let setRevealedOld = GameSettings.prototype.Attributes.MapExploration.prototype.setRevealed; + GameSettings.prototype.Attributes.MapExploration.prototype.setRevealed = function (enabled) { + setRevealedOld.call(this, enabled); + this.allied = this.allied || enabled; + } + + GameSettings.prototype.Attributes.MapExploration.prototype.setAllied = function (enabled) { + this.allied = enabled; + this.revealed = this.revealed && this.allied; + } + +} diff --git a/a25/gui/gamesetup/Pages/GameSetupPage/GameSettings/GameSettingsLayout_CartographyMode.js b/a25/gui/gamesetup/Pages/GameSetupPage/GameSettings/GameSettingsLayout_CartographyMode.js new file mode 100644 index 0000000..808245c --- /dev/null +++ b/a25/gui/gamesetup/Pages/GameSetupPage/GameSettings/GameSettingsLayout_CartographyMode.js @@ -0,0 +1 @@ +g_GameSettingsLayout[0]["settings"].push("AlliedView"); diff --git a/a25/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/AlliedMap.js b/a25/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/AlliedMap.js new file mode 100644 index 0000000..736be78 --- /dev/null +++ b/a25/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/AlliedMap.js @@ -0,0 +1,28 @@ +GameSettingControls.AlliedView = class AlliedView extends GameSettingControlCheckbox +{ + constructor(...args) + { + super(...args); + g_GameSettings.mapExploration.watch(() => this.render(), ["allied"]); + g_GameSettings.map.watch(() => this.render(), ["type"]); + this.render(); + } + + render() + { + this.setEnabled(g_GameSettings.map.type != "scenario"); + this.setChecked(g_GameSettings.mapExploration.allied); + } + + onPress(checked) + { + g_GameSettings.mapExploration.setAllied(checked); + this.gameSettingsController.setNetworkInitAttributes(); + } +}; + +GameSettingControls.AlliedView.prototype.TitleCaption = + translate("Allied View"); + +GameSettingControls.AlliedView.prototype.Tooltip = + translate("Toggle allied view (see what your allies see)."); diff --git a/a25/gui/gamesetup/Pages/GameSetupPage/Panels/GameDescription_CartographyMode.js b/a25/gui/gamesetup/Pages/GameSetupPage/Panels/GameDescription_CartographyMode.js new file mode 100644 index 0000000..a97145b --- /dev/null +++ b/a25/gui/gamesetup/Pages/GameSetupPage/Panels/GameDescription_CartographyMode.js @@ -0,0 +1,8 @@ +{ + let registerWatchersOld = GameDescription.prototype.registerWatchers; + GameDescription.prototype.registerWatchers = function () { + registerWatchersOld.call(this); + let update = () => this.updateGameDescription(); + g_GameSettings.mapExploration.watch(update, ["allied"]); + } +} diff --git a/a25/l10n/en_GB.public-gui-gamesetup-CartographyMode.po b/a25/l10n/en_GB.public-gui-gamesetup-CartographyMode.po new file mode 100644 index 0000000..c7dbdee --- /dev/null +++ b/a25/l10n/en_GB.public-gui-gamesetup-CartographyMode.po @@ -0,0 +1,11 @@ +#. Translation: Make sure to differentiate between the revealed map and +#. ally view settings! +#: gui/gamesetup/gamesetup_CartographMode.js +msgid "Ally View" +msgstr "Ally View" + +#. Translation: Make sure to differentiate between the revealed map and +#. ally view settings! +#: gui/gamesetup/gamesetup_CarographyMode.js +msgid "Toggle allied view (see everything you + allies can see)." +msgstr "Toggle allied view (see everything you + allies can see)." diff --git a/a25/mod.json b/a25/mod.json new file mode 100644 index 0000000..9df9b6c --- /dev/null +++ b/a25/mod.json @@ -0,0 +1,7 @@ +{ + "name": "CartographyMode", + "version": "2.0.0", + "label": "Cartography Mode", + "description": "Start a game with \"cartography mode\" researched for all players. Everyone sees what their allies can see from the start of the game.", + "dependencies": ["0ad=0.0.25"] +} diff --git a/a25/simulation/helpers/InitGame.js b/a25/simulation/helpers/InitGame.js new file mode 100644 index 0000000..918f6a9 --- /dev/null +++ b/a25/simulation/helpers/InitGame.js @@ -0,0 +1,103 @@ +/** + * Called when the map has been loaded, but before the simulation has started. + * Only called when a new game is started, not when loading a saved game. + * + * This is pretty ugly and (I think) overwrites InitGame.js in "Public" mod (aka the 0AD game). + * It's the only way I could get this to work because you can only "Engine.RegisterGlobal()"" once :/. + */ + function PreInitGame() + { + // We need to replace skirmish "default" entities with real ones. + // This needs to happen before AI initialization (in InitGame). + // And we need to flush destroyed entities otherwise the AI gets the wrong game state in + // the beginning and a bunch of "destroy" messages on turn 0, which just shouldn't happen. + Engine.BroadcastMessage(MT_SkirmishReplace, {}); + Engine.FlushDestroyedEntities(); + + let numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers(); + for (let i = 1; i < numPlayers; ++i) // ignore gaia + { + let cmpTechnologyManager = QueryPlayerIDInterface(i, IID_TechnologyManager); + if (cmpTechnologyManager) + cmpTechnologyManager.UpdateAutoResearch(); + } + + // Explore the map inside the players' territory borders + let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); + cmpRangeManager.ExploreTerritories(); + } + + function InitGame(settings) + { + // No settings when loading a map in Atlas, so do nothing + if (!settings) + { + // Map dependent initialisations of components (i.e. garrisoned units) + Engine.BroadcastMessage(MT_InitGame, {}); + return; + } + + if (settings.ExploreMap) + { + let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); + for (let i = 1; i < settings.PlayerData.length; ++i) + cmpRangeManager.ExploreMap(i); + } + + if (settings.AllyView) + { + for (let i = 1; i < settings.PlayerData.length; ++i) + { + let cmpPlayer = QueryPlayerIDInterface(i); + let cmpTechnologyManager = Engine.QueryInterface(cmpPlayer.entity, IID_TechnologyManager); + if (cmpTechnologyManager) + { + cmpTechnologyManager.ResearchTechnology(cmpPlayer.template.SharedLosTech); + cmpPlayer.UpdateSharedLos(); + } + } + } + + // Sandbox, Very Easy, Easy, Medium, Hard, Very Hard + // rate apply on resource stockpiling as gathering and trading + // time apply on building, upgrading, packing, training and technologies + let rate = [ 0.42, 0.56, 0.75, 1.00, 1.25, 1.56 ]; + let time = [ 1.40, 1.25, 1.10, 1.00, 1.00, 1.00 ]; + let cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager); + let cmpAIManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_AIManager); + for (let i = 0; i < settings.PlayerData.length; ++i) + { + let cmpPlayer = QueryPlayerIDInterface(i); + cmpPlayer.SetCheatsEnabled(!!settings.CheatsEnabled); + + if (settings.PlayerData[i] && !!settings.PlayerData[i].AI) + { + let AIDiff = +settings.PlayerData[i].AIDiff; + cmpAIManager.AddPlayer(settings.PlayerData[i].AI, i, AIDiff, settings.PlayerData[i].AIBehavior || "random"); + cmpPlayer.SetAI(true); + AIDiff = Math.min(AIDiff, rate.length - 1); + cmpModifiersManager.AddModifiers("AI Bonus", { + "ResourceGatherer/BaseSpeed": [{ "affects": ["Unit", "Structure"], "multiply": rate[AIDiff] }], + "Trader/GainMultiplier": [{ "affects": ["Unit", "Structure"], "multiply": rate[AIDiff] }], + "Cost/BuildTime": [{ "affects": ["Unit", "Structure"], "multiply": time[AIDiff] }], + }, cmpPlayer.entity); + } + + if (settings.PopulationCap) + cmpPlayer.SetMaxPopulation(settings.PopulationCap); + } + if (settings.WorldPopulationCap) + Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).SetMaxWorldPopulation(settings.WorldPopulationCap); + + // Update the grid with all entities created for the map init. + Engine.QueryInterface(SYSTEM_ENTITY, IID_Pathfinder).UpdateGrid(); + + // Map or player data (handicap...) dependent initialisations of components (i.e. garrisoned units). + Engine.BroadcastMessage(MT_InitGame, {}); + + cmpAIManager.TryLoadSharedComponent(); + cmpAIManager.RunGamestateInit(); + } + + Engine.RegisterGlobal("PreInitGame", PreInitGame); + Engine.RegisterGlobal("InitGame", InitGame);