diff --git a/Makefile b/Makefile index ea4c1937a7..a49724fde2 100644 --- a/Makefile +++ b/Makefile @@ -320,7 +320,8 @@ all-dependencies: cli-dependencies windows-dependencies osx-dependencies version: mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modchooser/mod.yaml mods/all/mod.yaml @for i in $? ; do \ awk '{sub("Version:.*$$","Version: $(VERSION)"); print $0}' $${i} > $${i}.tmp && \ - mv -f $${i}.tmp $${i} ; \ + awk '{sub("\tmodchooser:.*$$","\tmodchooser: $(VERSION)"); print $0}' $${i}.tmp > $${i} && \ + rm $${i}.tmp ; \ done docs: utility mods version diff --git a/OpenRA.Game/Game.cs b/OpenRA.Game/Game.cs index fae6837950..07b6522dd1 100644 --- a/OpenRA.Game/Game.cs +++ b/OpenRA.Game/Game.cs @@ -253,6 +253,13 @@ namespace OpenRA RunAfterDelay(Settings.Server.NatDiscoveryTimeout, UPnP.StoppingNatDiscovery); } + public static bool IsModInstalled(string modId) + { + return Manifest.AllMods[modId].RequiresMods.All(mod => ModMetadata.AllMods.ContainsKey(mod.Key) + && ModMetadata.AllMods[mod.Key].Version == mod.Value + && IsModInstalled(mod.Key)); + } + public static void InitializeMod(string mod, Arguments args) { // Clear static state if we have switched mods @@ -276,8 +283,8 @@ namespace OpenRA ModData.Dispose(); ModData = null; - // Fall back to default if the mod doesn't exist - if (!ModMetadata.AllMods.ContainsKey(mod)) + // Fall back to default if the mod doesn't exist or has missing prerequisites. + if (!ModMetadata.AllMods.ContainsKey(mod) || !IsModInstalled(mod)) mod = new GameSettings().Mod; Console.WriteLine("Loading mod: {0}", mod); diff --git a/OpenRA.Game/Manifest.cs b/OpenRA.Game/Manifest.cs index cba7be926e..d35432d9a8 100644 --- a/OpenRA.Game/Manifest.cs +++ b/OpenRA.Game/Manifest.cs @@ -32,6 +32,8 @@ namespace OpenRA // Describes what is to be loaded in order to run a mod public class Manifest { + public static readonly Dictionary AllMods = LoadMods(); + public readonly ModMetadata Mod; public readonly string[] Folders, Rules, ServerTraits, @@ -44,6 +46,7 @@ namespace OpenRA public readonly MiniYaml LoadScreen; public readonly MiniYaml LobbyDefaults; + public readonly Dictionary RequiresMods; public readonly Dictionary> Fonts; public readonly string[] SpriteFormats = { }; @@ -51,7 +54,7 @@ namespace OpenRA readonly string[] reservedModuleNames = { "Metadata", "Folders", "MapFolders", "Packages", "Rules", "Sequences", "VoxelSequences", "Cursors", "Chrome", "Assemblies", "ChromeLayout", "Weapons", "Voices", "Notifications", "Music", "Translations", "TileSets", "ChromeMetrics", "Missions", - "ServerTraits", "LoadScreen", "LobbyDefaults", "Fonts", "SupportsMapsFrom", "SpriteFormats" }; + "ServerTraits", "LoadScreen", "LobbyDefaults", "Fonts", "SupportsMapsFrom", "SpriteFormats", "RequiresMods" }; readonly TypeDictionary modules = new TypeDictionary(); readonly Dictionary yaml; @@ -98,6 +101,8 @@ namespace OpenRA return Pair.New(nd["Font"].Value, Exts.ParseIntegerInvariant(nd["Size"].Value)); }); + RequiresMods = yaml["RequiresMods"].ToDictionary(my => my.Value); + // Allow inherited mods to import parent maps. var compat = new List(); compat.Add(mod); @@ -174,5 +179,29 @@ namespace OpenRA return module; } + + static Dictionary LoadMods() + { + var basePath = Platform.ResolvePath(".", "mods"); + var mods = Directory.GetDirectories(basePath) + .Select(x => x.Substring(basePath.Length + 1)); + + var ret = new Dictionary(); + foreach (var mod in mods) + { + try + { + var manifest = new Manifest(mod); + ret.Add(mod, manifest); + } + catch (Exception ex) + { + Log.Write("debug", "An exception occured while trying to load mod {0}:", mod); + Log.Write("debug", ex.ToString()); + } + } + + return ret; + } } } diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index f73008306c..945a0003d1 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -596,6 +596,7 @@ + diff --git a/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallModLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallModLogic.cs new file mode 100644 index 0000000000..e89ee00cad --- /dev/null +++ b/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallModLogic.cs @@ -0,0 +1,30 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System.Linq; +using OpenRA.Widgets; + +namespace OpenRA.Mods.Common.Widgets.Logic +{ + public class InstallModLogic : ChromeLogic + { + [ObjectCreator.UseCtor] + public InstallModLogic(Widget widget, string modId) + { + var panel = widget.Get("INSTALL_MOD_PANEL"); + + var mods = Manifest.AllMods[modId].RequiresMods.Select(x => "{0} ({1})".F(x.Key, x.Value)); + var text = string.Join(", ", mods); + panel.Get("MOD_LIST").Text = text; + + panel.Get("BACK_BUTTON").OnClick = Ui.CloseWindow; + } + } +} diff --git a/OpenRA.Mods.Common/Widgets/Logic/ModBrowserLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/ModBrowserLogic.cs index d633897aba..bab41933bc 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/ModBrowserLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/ModBrowserLogic.cs @@ -27,6 +27,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic readonly Dictionary previews = new Dictionary(); readonly Dictionary logos = new Dictionary(); readonly Cache modInstallStatus; + readonly Cache modPrerequisitesFulfilled; readonly Widget modChooserPanel; readonly ButtonWidget loadButton; readonly SheetBuilder sheetBuilder; @@ -38,6 +39,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic [ObjectCreator.UseCtor] public ModBrowserLogic(Widget widget) { + modInstallStatus = new Cache(IsModInstalled); + modPrerequisitesFulfilled = new Cache(Game.IsModInstalled); + modChooserPanel = widget; loadButton = modChooserPanel.Get("LOAD_BUTTON"); loadButton.OnClick = () => LoadMod(selectedMod); @@ -93,8 +97,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic catch (Exception) { } } - modInstallStatus = new Cache(IsModInstalled); - ModMetadata initialMod; ModMetadata.AllMods.TryGetValue(Game.Settings.Game.PreviousMod, out initialMod); SelectMod(initialMod != null && initialMod.Id != "modchooser" ? initialMod : ModMetadata.AllMods["ra"]); @@ -156,11 +158,23 @@ namespace OpenRA.Mods.Common.Widgets.Logic if (selectedIndex - modOffset > 4) modOffset = selectedIndex - 4; - loadButton.Text = modInstallStatus[mod] ? "Load Mod" : "Install Assets"; + loadButton.Text = !modPrerequisitesFulfilled[mod.Id] ? "Install mod" : + modInstallStatus[mod] ? "Load Mod" : "Install Assets"; } void LoadMod(ModMetadata mod) { + if (!modPrerequisitesFulfilled[mod.Id]) + { + var widgetArgs = new WidgetArgs + { + { "modId", mod.Id } + }; + + Ui.OpenWindow("INSTALL_MOD_PANEL", widgetArgs); + return; + } + if (!modInstallStatus[mod]) { var widgetArgs = new WidgetArgs diff --git a/make.ps1 b/make.ps1 index f625bd9f16..eccb8b91ee 100644 --- a/make.ps1 +++ b/make.ps1 @@ -113,6 +113,8 @@ elseif ($command -eq "version") { $replacement = (gc $mod) -Replace "Version:.*", ("Version: {0}" -f $version) sc $mod $replacement + $replacement = (gc $mod) -Replace "modchooser:.*", ("modchooser: {0}" -f $version) + sc $mod $replacement } echo ("Version strings set to '{0}'." -f $version) } diff --git a/mods/cnc/mod.yaml b/mods/cnc/mod.yaml index 57c2ac67b3..f127fdfe10 100644 --- a/mods/cnc/mod.yaml +++ b/mods/cnc/mod.yaml @@ -4,6 +4,9 @@ Metadata: Version: {DEV_VERSION} Author: the OpenRA Developers +RequiresMods: + modchooser: {DEV_VERSION} + Folders: . ./mods/cnc diff --git a/mods/d2k/mod.yaml b/mods/d2k/mod.yaml index 239899aa2e..13b7743f66 100644 --- a/mods/d2k/mod.yaml +++ b/mods/d2k/mod.yaml @@ -4,6 +4,9 @@ Metadata: Version: {DEV_VERSION} Author: the OpenRA Developers +RequiresMods: + modchooser: {DEV_VERSION} + Folders: . ./mods/d2k diff --git a/mods/modchooser/install.yaml b/mods/modchooser/install.yaml index 7f8d32093d..92921272d2 100644 --- a/mods/modchooser/install.yaml +++ b/mods/modchooser/install.yaml @@ -260,3 +260,50 @@ Container@INSTALL_MUSIC_PANEL: Text: Back Font: Bold Key: escape +Container@INSTALL_MOD_PANEL: + Logic: InstallModLogic + X: (WINDOW_RIGHT - WIDTH)/2 + Y: (WINDOW_BOTTOM - HEIGHT)/2 + Width: 500 + Height: 177 + Children: + Background: + Width: PARENT_RIGHT + Height: PARENT_BOTTOM + Background: panel-bg + Background@RULE: + X: 30 + Y: 50 + Width: 440 + Height: 150 + Background: panel-rule + Label@TITLE: + X: 0 + Y: 12 + Width: PARENT_RIGHT + Height: 25 + Text: Missing dependencies + Align: Center + Font: MediumBold + Label@DESC: + X: 0 + Y: 65 + Width: PARENT_RIGHT + Height: 25 + Align: Center + Text: Please fully install the following mods then try again: + Label@MOD_LIST: + X: 0 + Y: 85 + Width: PARENT_RIGHT + Height: 25 + Align: Center + Button@BACK_BUTTON: + X: PARENT_RIGHT - 130 + Y: PARENT_BOTTOM - 52 + Background: button-highlighted + Width: 110 + Height: 32 + Text: Back + Font: Bold + Key: escape diff --git a/mods/modchooser/mod.yaml b/mods/modchooser/mod.yaml index 4b6d50e8bd..1af5f13d77 100644 --- a/mods/modchooser/mod.yaml +++ b/mods/modchooser/mod.yaml @@ -4,6 +4,8 @@ Metadata: Author: The OpenRA Developers Hidden: true +RequiresMods: + Folders: . ./mods/modchooser diff --git a/mods/ra/mod.yaml b/mods/ra/mod.yaml index 31b4148707..43bd07c24e 100644 --- a/mods/ra/mod.yaml +++ b/mods/ra/mod.yaml @@ -4,6 +4,9 @@ Metadata: Version: {DEV_VERSION} Author: the OpenRA Developers +RequiresMods: + modchooser: {DEV_VERSION} + Folders: . ./mods/ra diff --git a/mods/ts/mod.yaml b/mods/ts/mod.yaml index fe85a82a54..2f48629b1e 100644 --- a/mods/ts/mod.yaml +++ b/mods/ts/mod.yaml @@ -4,6 +4,9 @@ Metadata: Version: {DEV_VERSION} Author: the OpenRA Developers +RequiresMods: + modchooser: {DEV_VERSION} + Folders: . # Tiberian Sun