diff --git a/OpenRA.Game/Manifest.cs b/OpenRA.Game/Manifest.cs index deaca44e89..b01f8f7f14 100644 --- a/OpenRA.Game/Manifest.cs +++ b/OpenRA.Game/Manifest.cs @@ -18,6 +18,7 @@ using OpenRA.Primitives; namespace OpenRA { public enum TileShape { Rectangle, Diamond } + public interface IGlobalModData { } // Describes what is to be loaded in order to run a mod public class Manifest @@ -33,7 +34,6 @@ namespace OpenRA public readonly IReadOnlyDictionary MapFolders; public readonly MiniYaml LoadScreen; public readonly MiniYaml LobbyDefaults; - public readonly InstallData ContentInstaller; public readonly Dictionary> Fonts; public readonly Size TileSize = new Size(24, 24); public readonly TileShape TileShape = TileShape.Rectangle; @@ -54,10 +54,19 @@ namespace OpenRA [Desc("Default subcell index used if SubCellInit is absent", "0 - full cell, 1 - first sub-cell")] public readonly int SubCellDefaultIndex = 3; + 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", "TileSize", + "TileShape", "SubCells", "SupportsMapsFrom", "SpriteFormats" }; + + readonly TypeDictionary modules = new TypeDictionary(); + readonly Dictionary yaml; + public Manifest(string mod) { var path = Platform.ResolvePath(".", "mods", mod, "mod.yaml"); - var yaml = new MiniYaml(null, MiniYaml.FromFile(path)).ToDictionary(); + yaml = new MiniYaml(null, MiniYaml.FromFile(path)).ToDictionary(); Mod = FieldLoader.Load(yaml["Metadata"]); Mod.Id = mod; @@ -86,9 +95,6 @@ namespace OpenRA LoadScreen = yaml["LoadScreen"]; LobbyDefaults = yaml["LobbyDefaults"]; - if (yaml.ContainsKey("ContentInstaller")) - ContentInstaller = FieldLoader.Load(yaml["ContentInstaller"]); - Fonts = yaml["Fonts"].ToDictionary(my => { var nd = my.ToDictionary(); @@ -133,6 +139,23 @@ namespace OpenRA SpriteFormats = FieldLoader.GetValue("SpriteFormats", yaml["SpriteFormats"].Value); } + public void LoadCustomData(ObjectCreator oc) + { + foreach (var kv in yaml) + { + if (reservedModuleNames.Contains(kv.Key)) + continue; + + var t = oc.FindType(kv.Key); + if (t == null || !typeof(IGlobalModData).IsAssignableFrom(t)) + throw new InvalidDataException("`{0}` is not a valid mod manifest entry.".F(kv.Key)); + + var module = oc.CreateObject(kv.Key); + FieldLoader.Load(module, kv.Value); + modules.Add(module); + } + } + static string[] YamlList(Dictionary yaml, string key, bool parsePaths = false) { if (!yaml.ContainsKey(key)) @@ -152,5 +175,19 @@ namespace OpenRA return new ReadOnlyDictionary(inner); } + + public T Get() where T : IGlobalModData + { + var module = modules.GetOrDefault(); + + // Lazily create the default values if not explicitly defined. + if (module == null) + { + module = (T)Game.ModData.ObjectCreator.CreateBasic(typeof(T)); + modules.Add(module); + } + + return module; + } } } diff --git a/OpenRA.Game/ModData.cs b/OpenRA.Game/ModData.cs index 5c9e8d33c7..8cba16ed01 100644 --- a/OpenRA.Game/ModData.cs +++ b/OpenRA.Game/ModData.cs @@ -38,6 +38,8 @@ namespace OpenRA Languages = new string[0]; Manifest = new Manifest(mod); ObjectCreator = new ObjectCreator(Manifest); + Manifest.LoadCustomData(ObjectCreator); + if (useLoadScreen) { LoadScreen = ObjectCreator.CreateObject(Manifest.LoadScreen.Value); diff --git a/OpenRA.Game/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj index af61ef4bf9..ae14e7c544 100644 --- a/OpenRA.Game/OpenRA.Game.csproj +++ b/OpenRA.Game/OpenRA.Game.csproj @@ -275,7 +275,6 @@ - diff --git a/OpenRA.Game/InstallUtils.cs b/OpenRA.Mods.Common/InstallUtils.cs similarity index 98% rename from OpenRA.Game/InstallUtils.cs rename to OpenRA.Mods.Common/InstallUtils.cs index f33f447896..c545b56f20 100644 --- a/OpenRA.Game/InstallUtils.cs +++ b/OpenRA.Mods.Common/InstallUtils.cs @@ -16,9 +16,9 @@ using ICSharpCode.SharpZipLib; using ICSharpCode.SharpZipLib.Zip; using OpenRA.FileSystem; -namespace OpenRA +namespace OpenRA.Mods.Common { - public class InstallData + public class ContentInstaller : IGlobalModData { public readonly string MenuWidget = null; public readonly string MusicMenuWidget = null; diff --git a/OpenRA.Mods.Common/LoadScreens/BlankLoadScreen.cs b/OpenRA.Mods.Common/LoadScreens/BlankLoadScreen.cs index 6a4bfe5ed0..37722cd67a 100644 --- a/OpenRA.Mods.Common/LoadScreens/BlankLoadScreen.cs +++ b/OpenRA.Mods.Common/LoadScreens/BlankLoadScreen.cs @@ -40,7 +40,7 @@ namespace OpenRA.Mods.Common.LoadScreens // Check whether the mod content is installed // TODO: The installation code has finally been beaten into shape, so we can // finally move it all into the planned "Manage Content" panel in the modchooser mod. - var installData = Game.ModData.Manifest.ContentInstaller; + var installData = Game.ModData.Manifest.Get(); var installModContent = !installData.TestFiles.All(f => GlobalFileSystem.Exists(f)); var installModMusic = args != null && args.Contains("Install.Music"); diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index 6747ef62d6..4010600547 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -63,6 +63,10 @@ ..\thirdparty\Mono.Nat.dll False + + ..\thirdparty\ICSharpCode.SharpZipLib.dll + False + @@ -572,6 +576,7 @@ + diff --git a/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromCDLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromCDLogic.cs index 4e81894ae0..0f2524265f 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromCDLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromCDLogic.cs @@ -24,10 +24,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic readonly Action continueLoading; readonly ButtonWidget retryButton, backButton; readonly Widget installingContainer, insertDiskContainer; + readonly ContentInstaller installData; [ObjectCreator.UseCtor] public InstallFromCDLogic(Widget widget, Action continueLoading) { + installData = Game.ModData.Manifest.Get(); this.continueLoading = continueLoading; panel = widget.Get("INSTALL_FROMCD_PANEL"); progressBar = panel.Get("PROGRESS_BAR"); @@ -46,7 +48,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic bool IsValidDisk(string diskRoot) { - return Game.ModData.Manifest.ContentInstaller.DiskTestFiles.All(f => File.Exists(Path.Combine(diskRoot, f))); + return installData.DiskTestFiles.All(f => File.Exists(Path.Combine(diskRoot, f))); } void CheckForDisk() @@ -70,13 +72,13 @@ namespace OpenRA.Mods.Common.Widgets.Logic installingContainer.IsVisible = () => true; var dest = Platform.ResolvePath("^", "Content", Game.ModData.Manifest.Mod.Id); - var copyFiles = Game.ModData.Manifest.ContentInstaller.CopyFilesFromCD; + var copyFiles = installData.CopyFilesFromCD; - var packageToExtract = Game.ModData.Manifest.ContentInstaller.PackageToExtractFromCD.Split(':'); + var packageToExtract = installData.PackageToExtractFromCD.Split(':'); var extractPackage = packageToExtract.First(); var annotation = packageToExtract.Length > 1 ? packageToExtract.Last() : null; - var extractFiles = Game.ModData.Manifest.ContentInstaller.ExtractFilesFromCD; + var extractFiles = installData.ExtractFilesFromCD; var installCounter = 0; var installTotal = copyFiles.Length + extractFiles.Length; diff --git a/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallLogic.cs index e35bc9bea6..dff55c86e6 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallLogic.cs @@ -19,13 +19,13 @@ namespace OpenRA.Mods.Common.Widgets.Logic [ObjectCreator.UseCtor] public InstallLogic(Widget widget, Action continueLoading) { - var mirrorListUrl = Game.ModData.Manifest.ContentInstaller.PackageMirrorList; + var installData = Game.ModData.Manifest.Get(); var panel = widget.Get("INSTALL_PANEL"); var widgetArgs = new WidgetArgs() { { "afterInstall", () => { Ui.CloseWindow(); continueLoading(); } }, { "continueLoading", continueLoading }, - { "mirrorListUrl", mirrorListUrl }, + { "mirrorListUrl", installData.PackageMirrorList }, }; panel.Get("DOWNLOAD_BUTTON").OnClick = () => diff --git a/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallMusicLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallMusicLogic.cs index be0134846f..b3c7017a6a 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallMusicLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallMusicLogic.cs @@ -42,13 +42,13 @@ namespace OpenRA.Mods.Common.Widgets.Logic var downloadButton = installMusicContainer.GetOrNull("DOWNLOAD_BUTTON"); if (downloadButton != null) { - var mirrorListUrl = Game.ModData.Manifest.ContentInstaller.MusicPackageMirrorList; - downloadButton.IsVisible = () => !string.IsNullOrEmpty(mirrorListUrl); + var installData = Game.ModData.Manifest.Get(); + downloadButton.IsVisible = () => !string.IsNullOrEmpty(installData.MusicPackageMirrorList); downloadButton.OnClick = () => { Ui.OpenWindow("INSTALL_DOWNLOAD_PANEL", new WidgetArgs() { { "afterInstall", () => Game.InitializeMod(Game.Settings.Game.Mod, null) }, - { "mirrorListUrl", mirrorListUrl }, + { "mirrorListUrl", installData.MusicPackageMirrorList }, }); }; } diff --git a/OpenRA.Mods.Common/Widgets/Logic/MusicPlayerLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/MusicPlayerLogic.cs index e109d97a77..d8f9c41c75 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/MusicPlayerLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/MusicPlayerLogic.cs @@ -92,7 +92,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic Game.InitializeMod(Game.Settings.Game.Mod, new Arguments(args)); }; - var installData = Game.ModData.Manifest.ContentInstaller; + var installData = Game.ModData.Manifest.Get(); installButton.IsVisible = () => modRules.InstalledMusic.ToArray().Length <= installData.ShippedSoundtracks; } diff --git a/mods/cnc/mod.yaml b/mods/cnc/mod.yaml index 73ed0758d4..0cfff7afb0 100644 --- a/mods/cnc/mod.yaml +++ b/mods/cnc/mod.yaml @@ -121,10 +121,6 @@ ChromeLayout: ./mods/cnc/chrome/assetbrowser.yaml ./mods/cnc/chrome/missionbrowser.yaml -Movies: - ./mods/cnc/movies-gdi.yaml - ./mods/cnc/movies-nod.yaml - Voices: ./mods/cnc/audio/voices.yaml