diff --git a/OpenRA.Game/Game.cs b/OpenRA.Game/Game.cs index 50bb78bab7..a7be84ea6c 100644 --- a/OpenRA.Game/Game.cs +++ b/OpenRA.Game/Game.cs @@ -249,10 +249,10 @@ namespace OpenRA // Special case handling of Game.Mod argument: if it matches a real filesystem path // then we use this to override the mod search path, and replace it with the mod id var modArgument = args.GetValue("Game.Mod", null); - string customModPath = null; + var explicitModPaths = new string[0]; if (modArgument != null && (File.Exists(modArgument) || Directory.Exists(modArgument))) { - customModPath = modArgument; + explicitModPaths = new[] { modArgument }; args.ReplaceValue("Game.Mod", Path.GetFileNameWithoutExtension(modArgument)); } @@ -314,7 +314,12 @@ namespace OpenRA GlobalChat = new GlobalChat(); - Mods = new InstalledMods(customModPath); + var modSearchArg = args.GetValue("Engine.ModSearchPaths", null); + var modSearchPaths = modSearchArg != null ? + FieldLoader.GetValue("Engine.ModsPath", modSearchArg) : + new[] { Path.Combine(".", "mods"), Path.Combine("^", "mods") }; + + Mods = new InstalledMods(modSearchPaths, explicitModPaths); Console.WriteLine("Internal mods:"); foreach (var mod in Mods) Console.WriteLine("\t{0}: {1} ({2})", mod.Key, mod.Value.Metadata.Title, mod.Value.Metadata.Version); @@ -368,8 +373,8 @@ namespace OpenRA ModData = null; // Fall back to default if the mod doesn't exist or has missing prerequisites. - if (!IsModInstalled(mod)) - mod = new GameSettings().Mod; + if (mod == null || !IsModInstalled(mod)) + mod = args.GetValue("Engine.DefaultMod", "modchooser"); Console.WriteLine("Loading mod: {0}", mod); Settings.Game.Mod = mod; @@ -379,16 +384,12 @@ namespace OpenRA ModData = new ModData(Mods[mod], Mods, true); ExternalMods.Register(ModData.Manifest); + if (!ModData.LoadScreen.BeforeLoad()) + return; + using (new PerfTimer("LoadMaps")) ModData.MapCache.LoadMaps(); - // Mod assets are missing! - if (!ModData.LoadScreen.RequiredContentIsInstalled()) - { - InitializeMod("modchooser", new Arguments()); - return; - } - ModData.InitializeLoaders(ModData.DefaultFileSystem); Renderer.InitializeFonts(ModData); diff --git a/OpenRA.Game/InstalledMods.cs b/OpenRA.Game/InstalledMods.cs index 2d191b6509..e255e5c44e 100644 --- a/OpenRA.Game/InstalledMods.cs +++ b/OpenRA.Game/InstalledMods.cs @@ -29,34 +29,35 @@ namespace OpenRA readonly Dictionary icons = new Dictionary(); public readonly IReadOnlyDictionary Icons; - public InstalledMods(string customModPath) + /// Initializes the collection of locally installed mods. + /// Filesystem paths to search for mod packages. + /// Filesystem paths to additional mod packages. + public InstalledMods(IEnumerable searchPaths, IEnumerable explicitPaths) { sheetBuilder = new SheetBuilder(SheetType.BGRA, 256); Icons = new ReadOnlyDictionary(icons); - mods = GetInstalledMods(customModPath); + mods = GetInstalledMods(searchPaths, explicitPaths); } - static IEnumerable> GetCandidateMods() + static IEnumerable> GetCandidateMods(IEnumerable searchPaths) { - // Get mods that are in the game folder. - var basePath = Platform.ResolvePath(Path.Combine(".", "mods")); - var mods = Directory.GetDirectories(basePath) - .Select(x => Pair.New(x.Substring(basePath.Length + 1), x)) - .ToList(); + var mods = new List>(); + foreach (var path in searchPaths) + { + try + { + var directory = new DirectoryInfo(Platform.ResolvePath(path)); + foreach (var subdir in directory.EnumerateDirectories()) + mods.Add(Pair.New(subdir.Name, subdir.FullName)); - foreach (var m in Directory.GetFiles(basePath, "*.oramod")) - mods.Add(Pair.New(Path.GetFileNameWithoutExtension(m), m)); - - // Get mods that are in the support folder. - var supportPath = Platform.ResolvePath(Path.Combine("^", "mods")); - if (!Directory.Exists(supportPath)) - return mods; - - foreach (var pair in Directory.GetDirectories(supportPath).ToDictionary(x => x.Substring(supportPath.Length + 1))) - mods.Add(Pair.New(pair.Key, pair.Value)); - - foreach (var m in Directory.GetFiles(supportPath, "*.oramod")) - mods.Add(Pair.New(Path.GetFileNameWithoutExtension(m), m)); + foreach (var file in directory.EnumerateFiles("*.oramod")) + mods.Add(Pair.New(Path.GetFileNameWithoutExtension(file.Name), file.FullName)); + } + catch (Exception e) + { + Console.WriteLine("Failed to enumerate mod search path {0}: {1}", path, e.Message); + } + } return mods; } @@ -102,12 +103,11 @@ namespace OpenRA } } - Dictionary GetInstalledMods(string customModPath) + Dictionary GetInstalledMods(IEnumerable searchPaths, IEnumerable explicitPaths) { var ret = new Dictionary(); - var candidates = GetCandidateMods(); - if (customModPath != null) - candidates = candidates.Append(Pair.New(Path.GetFileNameWithoutExtension(customModPath), customModPath)); + var candidates = GetCandidateMods(searchPaths) + .Concat(explicitPaths.Select(p => Pair.New(Path.GetFileNameWithoutExtension(p), p))); foreach (var pair in candidates) { diff --git a/OpenRA.Game/ModData.cs b/OpenRA.Game/ModData.cs index 9b50b9fef6..d24dba8fda 100644 --- a/OpenRA.Game/ModData.cs +++ b/OpenRA.Game/ModData.cs @@ -222,9 +222,19 @@ namespace OpenRA public interface ILoadScreen : IDisposable { + /// Initializes the loadscreen with yaml data from the LoadScreen block in mod.yaml. void Init(ModData m, Dictionary info); + + /// Called at arbitrary times during mod load to rerender the loadscreen. void Display(); - bool RequiredContentIsInstalled(); + + /// + /// Called before loading the mod assets. + /// Returns false if mod loading should be aborted (e.g. switching to another mod instead). + /// + bool BeforeLoad(); + + /// Called when the engine expects to connect to a server/replay or load the shellmap. void StartGame(Arguments args); } } diff --git a/OpenRA.Game/Settings.cs b/OpenRA.Game/Settings.cs index 9433a6b045..fdb85236b3 100644 --- a/OpenRA.Game/Settings.cs +++ b/OpenRA.Game/Settings.cs @@ -153,8 +153,8 @@ namespace OpenRA public class GameSettings { - [Desc("Load a specific mod on startup. Shipped ones include: ra, cnc and d2k")] - public string Mod = "modchooser"; + [Desc("Load a specific mod on startup.")] + public string Mod = null; public string PreviousMod = "ra"; public string Platform = "Default"; diff --git a/OpenRA.Mods.Common/LoadScreens/BlankLoadScreen.cs b/OpenRA.Mods.Common/LoadScreens/BlankLoadScreen.cs index e942a61157..6f5eda33f0 100644 --- a/OpenRA.Mods.Common/LoadScreens/BlankLoadScreen.cs +++ b/OpenRA.Mods.Common/LoadScreens/BlankLoadScreen.cs @@ -109,12 +109,23 @@ namespace OpenRA.Mods.Common.LoadScreens GC.SuppressFinalize(this); } - public bool RequiredContentIsInstalled() + public bool BeforeLoad() { + // If a ModContent section is defined then we need to make sure that the + // required content is installed or switch to the defined content installer. + if (!modData.Manifest.Contains()) + return true; + var content = modData.Manifest.Get(); - return content.Packages + var contentInstalled = content.Packages .Where(p => p.Value.Required) .All(p => p.Value.TestFiles.All(f => File.Exists(Platform.ResolvePath(f)))); + + if (contentInstalled) + return true; + + Game.InitializeMod(content.ContentInstallerMod, new Arguments()); + return false; } } } \ No newline at end of file diff --git a/OpenRA.Mods.Common/LoadScreens/ModChooserLoadScreen.cs b/OpenRA.Mods.Common/LoadScreens/ModChooserLoadScreen.cs index f3911fccb5..69e5fa3e64 100644 --- a/OpenRA.Mods.Common/LoadScreens/ModChooserLoadScreen.cs +++ b/OpenRA.Mods.Common/LoadScreens/ModChooserLoadScreen.cs @@ -58,7 +58,7 @@ namespace OpenRA.Mods.Common.LoadScreens sprite.Sheet.Dispose(); } - public bool RequiredContentIsInstalled() + public bool BeforeLoad() { return true; } diff --git a/OpenRA.Mods.Common/ModContent.cs b/OpenRA.Mods.Common/ModContent.cs index 050273f3b9..5481875ced 100644 --- a/OpenRA.Mods.Common/ModContent.cs +++ b/OpenRA.Mods.Common/ModContent.cs @@ -85,6 +85,7 @@ namespace OpenRA public readonly string InstallPromptMessage; public readonly string QuickDownload; public readonly string HeaderMessage; + public readonly string ContentInstallerMod = "modchooser"; [FieldLoader.LoadUsing("LoadPackages")] public readonly Dictionary Packages = new Dictionary(); diff --git a/OpenRA.Server/Program.cs b/OpenRA.Server/Program.cs index 0bdf8fa365..74d76dae6c 100644 --- a/OpenRA.Server/Program.cs +++ b/OpenRA.Server/Program.cs @@ -30,10 +30,10 @@ namespace OpenRA.Server // Special case handling of Game.Mod argument: if it matches a real filesystem path // then we use this to override the mod search path, and replace it with the mod id var modArgument = arguments.GetValue("Game.Mod", null); - string customModPath = null; + var explicitModPaths = new string[0]; if (modArgument != null && (File.Exists(modArgument) || Directory.Exists(modArgument))) { - customModPath = modArgument; + explicitModPaths = new[] { modArgument }; arguments.ReplaceValue("Game.Mod", Path.GetFileNameWithoutExtension(modArgument)); } @@ -43,7 +43,8 @@ namespace OpenRA.Server var settings = Game.Settings.Server; var mod = Game.Settings.Game.Mod; - var mods = new InstalledMods(customModPath); + var modSearchPaths = new[] { Path.Combine(".", "mods"), Path.Combine("^", "mods") }; + var mods = new InstalledMods(modSearchPaths, explicitModPaths); // HACK: The engine code *still* assumes that Game.ModData is set var modData = Game.ModData = new ModData(mods[mod], mods); diff --git a/OpenRA.Utility/Program.cs b/OpenRA.Utility/Program.cs index b554d6e065..447cb65de7 100644 --- a/OpenRA.Utility/Program.cs +++ b/OpenRA.Utility/Program.cs @@ -47,19 +47,20 @@ namespace OpenRA if (args.Length == 0) { - PrintUsage(new InstalledMods(null), null); + PrintUsage(new InstalledMods(new string[0], new string[0]), null); return; } var modId = args[0]; - string customModPath = null; + var explicitModPaths = new string[0]; if (File.Exists(modId) || Directory.Exists(modId)) { - customModPath = modId; + explicitModPaths = new[] { modId }; modId = Path.GetFileNameWithoutExtension(modId); } - var mods = new InstalledMods(customModPath); + var modSearchPaths = new[] { Path.Combine(".", "mods"), Path.Combine("^", "mods") }; + var mods = new InstalledMods(modSearchPaths, explicitModPaths); if (!mods.Keys.Contains(modId)) { PrintUsage(mods, null);