diff --git a/OpenRA.Game/FluentProvider.cs b/OpenRA.Game/FluentProvider.cs index 88b24efdc5..a6b6941566 100644 --- a/OpenRA.Game/FluentProvider.cs +++ b/OpenRA.Game/FluentProvider.cs @@ -23,11 +23,11 @@ namespace OpenRA static FluentBundle modFluentBundle; static FluentBundle mapFluentBundle; - public static void Initialize(ModData modData, IReadOnlyFileSystem fileSystem) + public static void Initialize(Manifest manifest, IReadOnlyFileSystem fileSystem) { lock (SyncObject) { - modFluentBundle = new FluentBundle(modData.Manifest.FluentCulture, modData.Manifest.FluentMessages, fileSystem); + modFluentBundle = new FluentBundle(manifest.FluentCulture, manifest.FluentMessages, fileSystem); if (fileSystem is Map map && map.FluentMessageDefinitions != null) { var files = ImmutableArray.Empty; @@ -45,7 +45,7 @@ namespace OpenRA text = builder.ToString(); } - mapFluentBundle = new FluentBundle(modData.Manifest.FluentCulture, files, fileSystem, text); + mapFluentBundle = new FluentBundle(manifest.FluentCulture, files, fileSystem, text); } } } diff --git a/OpenRA.Game/Game.cs b/OpenRA.Game/Game.cs index baad236d6c..ee58439e50 100644 --- a/OpenRA.Game/Game.cs +++ b/OpenRA.Game/Game.cs @@ -168,6 +168,7 @@ namespace OpenRA { return ModData.WidgetLoader.LoadWidget(new WidgetArgs(args) { + { "modData", ModData }, { "world", world }, { "orderManager", OrderManager }, { "worldRenderer", worldRenderer }, @@ -502,14 +503,14 @@ namespace OpenRA LocalPlayerProfile = new LocalPlayerProfile(Path.Combine(Platform.SupportDir, Settings.Game.AuthProfile), ModData.GetOrCreate()); - if (!ModData.LoadScreen.BeforeLoad()) + if (!ModData.LoadScreen.BeforeLoad(ModData)) return; ModData.InitializeLoaders(ModData.DefaultFileSystem); Renderer.InitializeFonts(ModData); using (new PerfTimer("LoadMaps")) - ModData.MapCache.LoadMaps(); + ModData.MapCache.LoadMaps(ModData); Cursor?.Dispose(); Cursor = new CursorManager(ModData); diff --git a/OpenRA.Game/Map/MapCache.cs b/OpenRA.Game/Map/MapCache.cs index 141db9d83c..95ae11d2b9 100644 --- a/OpenRA.Game/Map/MapCache.cs +++ b/OpenRA.Game/Map/MapCache.cs @@ -21,6 +21,7 @@ using OpenRA.Graphics; using OpenRA.Primitives; using OpenRA.Support; using OpenRA.Traits; +using FS = OpenRA.FileSystem.FileSystem; namespace OpenRA { @@ -31,8 +32,9 @@ namespace OpenRA readonly Dictionary mapLocations = []; public bool LoadPreviewImages = true; - readonly Cache previews; - readonly ModData modData; + readonly Manifest manifest; + readonly FS modFiles; + Cache previews; readonly SheetBuilder sheetBuilder; Thread previewLoaderThread; bool previewLoaderThreadShutDown = true; @@ -67,13 +69,11 @@ namespace OpenRA return null; } - public MapCache(ModData modData) + public MapCache(Manifest manifest, FS modFiles) { - this.modData = modData; - - var gridType = Exts.Lazy(() => modData.GetOrCreate().Type); - previews = new Cache(uid => new MapPreview(modData, uid, gridType.Value, this)); - sheetBuilder = new SheetBuilder(SheetType.BGRA, modData.Manifest.RendererConstants.MapPreviewSheetSize); + this.manifest = manifest; + this.modFiles = modFiles; + sheetBuilder = new SheetBuilder(SheetType.BGRA, manifest.RendererConstants.MapPreviewSheetSize); } public void UpdateMaps() @@ -82,14 +82,17 @@ namespace OpenRA tracker.UpdateMaps(this); } - public void LoadMaps() + public void LoadMaps(ModData modData) { // Utility mod that does not support maps - if (modData.Manifest.MapFolders.Count == 0) + if (manifest.MapFolders.Count == 0) return; + var gridType = modData.GetOrCreate().Type; + previews = new Cache(uid => new MapPreview(modData, uid, gridType, this)); + // Enumerate map directories - foreach (var kv in modData.Manifest.MapFolders) + foreach (var kv in manifest.MapFolders) { var name = kv.Key; var classification = string.IsNullOrEmpty(kv.Value) @@ -108,7 +111,7 @@ namespace OpenRA if (resolved.StartsWith(Platform.SupportDir, StringComparison.Ordinal) && !File.Exists(resolved)) Directory.CreateDirectory(resolved); - package = modData.ModFiles.OpenPackage(name); + package = modFiles.OpenPackage(name); } catch { @@ -124,12 +127,9 @@ namespace OpenRA // PERF: Load the mod YAML once outside the loop, and reuse it when resolving each maps custom YAML. var modDataRules = modData.GetRulesYaml(); - var gridType = modData.GetOrCreate().Type; foreach (var kv in MapLocations) - { foreach (var map in kv.Key.Contents) LoadMapInternal(map, kv.Key, kv.Value, null, gridType, modDataRules); - } // We only want to track maps in runtime, not at loadtime LastModifiedMap = null; @@ -148,7 +148,7 @@ namespace OpenRA { using (new PerfTimer(map)) { - mapPackage = package.OpenPackage(map, modData.ModFiles); + mapPackage = package.OpenPackage(map, modFiles); if (mapPackage != null) { var uid = Map.ComputeUID(mapPackage); @@ -179,7 +179,7 @@ namespace OpenRA public IEnumerable EnumerateMapDirPackages(MapClassification classification = MapClassification.System) { // Enumerate map directories - foreach (var kv in modData.Manifest.MapFolders) + foreach (var kv in manifest.MapFolders) { if (!Enum.TryParse(kv.Value, out MapClassification packageClassification)) continue; @@ -197,7 +197,7 @@ namespace OpenRA if (resolved.StartsWith(Platform.SupportDir, StringComparison.Ordinal) && (!Directory.Exists(resolved) || !File.Exists(resolved))) continue; - using (var package = (IReadWritePackage)modData.ModFiles.OpenPackage(name)) + using (var package = (IReadWritePackage)modFiles.OpenPackage(name)) yield return package; } } @@ -217,11 +217,11 @@ namespace OpenRA foreach (var mapDirPackage in mapDirPackages) foreach (var map in mapDirPackage.Contents) - if (mapDirPackage.OpenPackage(map, modData.ModFiles) is IReadWritePackage mapPackage) + if (mapDirPackage.OpenPackage(map, modFiles) is IReadWritePackage mapPackage) yield return mapPackage; } - public void GenerateMap(MapGenerationArgs args) + public void GenerateMap(ModData modData, MapGenerationArgs args) { var p = previews[args.Uid]; if (p.Class == MapClassification.Generated) diff --git a/OpenRA.Game/ModData.cs b/OpenRA.Game/ModData.cs index 031117a155..17ff2fec35 100644 --- a/OpenRA.Game/ModData.cs +++ b/OpenRA.Game/ModData.cs @@ -35,7 +35,6 @@ namespace OpenRA public readonly IPackageLoader[] PackageLoaders; public readonly ISoundLoader[] SoundLoaders; public readonly ISpriteLoader[] SpriteLoaders; - public readonly ITerrainLoader TerrainLoader; public readonly ISpriteSequenceLoader SpriteSequenceLoader; public readonly IVideoLoader[] VideoLoaders; public readonly HotkeyManager Hotkeys; @@ -92,41 +91,38 @@ namespace OpenRA modules.Add(module); } - FluentProvider.Initialize(this, DefaultFileSystem); + FluentProvider.Initialize(Manifest, DefaultFileSystem); if (useLoadScreen) { LoadScreen = ObjectCreator.CreateObject(Manifest.LoadScreen.Value); - LoadScreen.Init(this, Manifest.LoadScreen.ToDictionary(my => my.Value)); + LoadScreen.Init(Manifest, DefaultFileSystem); LoadScreen.Display(); } - WidgetLoader = new WidgetLoader(this); - MapCache = new MapCache(this); + WidgetLoader = new WidgetLoader(Manifest, DefaultFileSystem); + MapCache = new MapCache(Manifest, ModFiles); SoundLoaders = ObjectCreator.GetLoaders(Manifest.SoundFormats, "sound"); SpriteLoaders = ObjectCreator.GetLoaders(Manifest.SpriteFormats, "sprite"); VideoLoaders = ObjectCreator.GetLoaders(Manifest.VideoFormats, "video"); SpriteSequenceLoader = ObjectCreator.GetLoader(Manifest.SpriteSequenceFormat, "sequence"); - - var terrainLoader = ObjectCreator.FindType(Manifest.TerrainFormat + "Loader"); - var terrainCtor = terrainLoader?.GetConstructor([typeof(ModData)]); - if (terrainLoader == null || !terrainLoader.GetInterfaces().Contains(typeof(ITerrainLoader)) || terrainCtor == null) - throw new InvalidOperationException($"Unable to find a terrain loader for type '{Manifest.TerrainFormat}'."); - - TerrainLoader = (ITerrainLoader)terrainCtor.Invoke([this]); - Hotkeys = new HotkeyManager(ModFiles, Game.Settings.Keys, Manifest); Cursors = ParseCursors(Manifest, DefaultFileSystem); defaultRules = Exts.Lazy(() => Ruleset.LoadDefaults(this)); defaultTerrainInfo = Exts.Lazy(() => { - var items = new Dictionary(); + var terrainType = ObjectCreator.FindType(Manifest.TerrainFormat + "Loader"); + var terrainCtor = terrainType?.GetConstructor([typeof(ModData)]); + if (terrainType == null || !terrainType.GetInterfaces().Contains(typeof(ITerrainLoader)) || terrainCtor == null) + throw new InvalidOperationException($"Unable to find a terrain loader for type '{Manifest.TerrainFormat}'."); + var items = new Dictionary(); + var terrainLoader = (ITerrainLoader)terrainCtor.Invoke([this]); foreach (var file in Manifest.TileSets) { - var t = TerrainLoader.ParseTerrain(DefaultFileSystem, file); + var t = terrainLoader.ParseTerrain(DefaultFileSystem, file); items.Add(t.Id, t); } @@ -169,7 +165,8 @@ namespace OpenRA // horribly when you use ModData in unexpected ways. ChromeMetrics.Initialize(this); ChromeProvider.Initialize(this); - FluentProvider.Initialize(this, fileSystem); + Ui.Initialize(this); + FluentProvider.Initialize(Manifest, fileSystem); Game.Sound.Initialize(SoundLoaders, fileSystem); } @@ -234,7 +231,7 @@ 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); + void Init(Manifest manifest, IReadOnlyFileSystem fileSystem); /// Called at arbitrary times during mod load to rerender the loadscreen. void Display(); @@ -243,7 +240,7 @@ namespace OpenRA /// Called before loading the mod assets. /// Returns false if mod loading should be aborted (e.g. switching to another mod instead). /// - bool BeforeLoad(); + bool BeforeLoad(ModData modData); /// Called when the engine expects to connect to a server/replay or load the shellmap. void StartGame(Arguments args); diff --git a/OpenRA.Game/Network/UnitOrders.cs b/OpenRA.Game/Network/UnitOrders.cs index 3e3b0f7b16..a354f48b24 100644 --- a/OpenRA.Game/Network/UnitOrders.cs +++ b/OpenRA.Game/Network/UnitOrders.cs @@ -393,7 +393,7 @@ namespace OpenRA.Network case "GenerateMap": { var yaml = new MiniYaml(order.OrderString, MiniYaml.FromString(order.TargetString, order.OrderString)); - Game.ModData.MapCache.GenerateMap(FieldLoader.Load(yaml)); + Game.ModData.MapCache.GenerateMap(Game.ModData, FieldLoader.Load(yaml)); break; } diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs index c2fab1cd52..4a9d607bac 100644 --- a/OpenRA.Game/Server/Server.cs +++ b/OpenRA.Game/Server/Server.cs @@ -1136,7 +1136,7 @@ namespace OpenRA.Server var args = FieldLoader.Load(yaml); var preview = ModData.MapCache[args.Uid]; if (preview.Status != MapStatus.Available) - ModData.MapCache.GenerateMap(args); + ModData.MapCache.GenerateMap(ModData, args); GeneratedMapData = o.TargetString; DispatchServerOrdersToClients(Order.FromTargetString("GenerateMap", o.TargetString, true)); diff --git a/OpenRA.Game/Widgets/Widget.cs b/OpenRA.Game/Widgets/Widget.cs index 7927fc1c27..580e238751 100644 --- a/OpenRA.Game/Widgets/Widget.cs +++ b/OpenRA.Game/Widgets/Widget.cs @@ -36,6 +36,12 @@ namespace OpenRA.Widgets public static Widget MouseOverWidget; static readonly Mediator Mediator = new(); + static ModData modData; + + public static void Initialize(ModData modData) + { + Ui.modData = modData; + } public static void CloseWindow() { @@ -66,6 +72,9 @@ namespace OpenRA.Widgets public static Widget OpenWindow(string id, WidgetArgs args) { + if (!args.ContainsKey("modData")) + args = new WidgetArgs(args) { { "modData", modData } }; + var window = Game.ModData.WidgetLoader.LoadWidget(args, Root, id); if (WindowList.Count > 0) Root.HideChild(WindowList.Peek()); @@ -88,6 +97,9 @@ namespace OpenRA.Widgets public static Widget LoadWidget(string id, Widget parent, WidgetArgs args) { + if (!args.ContainsKey("modData")) + args = new WidgetArgs(args) { { "modData", modData } }; + return Game.ModData.WidgetLoader.LoadWidget(args, parent, id); } diff --git a/OpenRA.Game/Widgets/WidgetLoader.cs b/OpenRA.Game/Widgets/WidgetLoader.cs index 0f90d17638..f3c04405f9 100644 --- a/OpenRA.Game/Widgets/WidgetLoader.cs +++ b/OpenRA.Game/Widgets/WidgetLoader.cs @@ -12,6 +12,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using OpenRA.FileSystem; using OpenRA.Widgets; namespace OpenRA @@ -19,15 +20,12 @@ namespace OpenRA public class WidgetLoader { readonly Dictionary widgets = []; - readonly ModData modData; - public WidgetLoader(ModData modData) + public WidgetLoader(Manifest manifest, IReadOnlyFileSystem fileSystem) { - this.modData = modData; - var stringPool = new HashSet(); // Reuse common strings in YAML - foreach (var file in modData.Manifest.ChromeLayout.Select( - a => MiniYaml.FromStream(modData.DefaultFileSystem.Open(a), a, stringPool: stringPool))) + foreach (var file in manifest.ChromeLayout.Select( + a => MiniYaml.FromStream(fileSystem.Open(a), a, stringPool: stringPool))) foreach (var w in file) { var key = w.Key[(w.Key.IndexOf('@') + 1)..]; @@ -47,9 +45,6 @@ namespace OpenRA public Widget LoadWidget(WidgetArgs args, Widget parent, MiniYamlNode node) { - if (!args.ContainsKey("modData")) - args = new WidgetArgs(args) { { "modData", modData } }; - var widget = NewWidget(node.Key, args); parent?.AddChild(widget); diff --git a/OpenRA.Mods.Cnc/CncLoadScreen.cs b/OpenRA.Mods.Cnc/CncLoadScreen.cs index bdcdce4c83..d564a7ced2 100644 --- a/OpenRA.Mods.Cnc/CncLoadScreen.cs +++ b/OpenRA.Mods.Cnc/CncLoadScreen.cs @@ -9,7 +9,7 @@ */ #endregion -using System.Collections.Generic; +using OpenRA.FileSystem; using OpenRA.Graphics; using OpenRA.Mods.Common.LoadScreens; using OpenRA.Mods.Common.Widgets; @@ -36,11 +36,11 @@ namespace OpenRA.Mods.Cnc string message = ""; - public override void Init(ModData modData, Dictionary info) + public override void Init(Manifest manifest, IReadOnlyFileSystem fileSystem) { - base.Init(modData, info); + base.Init(manifest, fileSystem); - versionText = modData.Manifest.Metadata.Version; + versionText = manifest.Metadata.Version; message = FluentProvider.GetMessage(Loading); } diff --git a/OpenRA.Mods.Common/LoadScreens/BlankLoadScreen.cs b/OpenRA.Mods.Common/LoadScreens/BlankLoadScreen.cs index 743be7715b..220d1cc70d 100644 --- a/OpenRA.Mods.Common/LoadScreens/BlankLoadScreen.cs +++ b/OpenRA.Mods.Common/LoadScreens/BlankLoadScreen.cs @@ -10,9 +10,9 @@ #endregion using System; -using System.Collections.Generic; using System.IO; using OpenRA.FileFormats; +using OpenRA.FileSystem; using OpenRA.Mods.Common.FileSystem; using OpenRA.Mods.Common.Widgets.Logic; using OpenRA.Widgets; @@ -22,13 +22,12 @@ namespace OpenRA.Mods.Common.LoadScreens public class BlankLoadScreen : ILoadScreen { public LaunchArguments Launch; - protected ModData ModData { get; private set; } - + protected IReadOnlyFileSystem fileSystem; bool initialized; - public virtual void Init(ModData modData, Dictionary info) + public virtual void Init(Manifest manifest, IReadOnlyFileSystem fileSystem) { - ModData = modData; + this.fileSystem = fileSystem; } public virtual void Display() @@ -108,12 +107,12 @@ namespace OpenRA.Mods.Common.LoadScreens GC.SuppressFinalize(this); } - public virtual bool BeforeLoad() + public virtual bool BeforeLoad(ModData modData) { var graphicSettings = Game.Settings.Graphics; // Reset the UI scaling if the user has configured a UI scale that pushes us below the minimum allowed effective resolution - var minResolution = ModData.GetOrCreate().MinEffectiveResolution; + var minResolution = modData.GetOrCreate().MinEffectiveResolution; var resolution = Game.Renderer.Resolution; if ((resolution.Width < minResolution.Width || resolution.Height < minResolution.Height) && Game.Settings.Graphics.UIScale > 1.0f) { @@ -126,10 +125,10 @@ namespace OpenRA.Mods.Common.LoadScreens if (graphicSettings.GLProfile != GLProfile.Automatic && graphicSettings.GLProfile != Game.Renderer.GLProfile) graphicSettings.GLProfile = GLProfile.Automatic; - if (ModData.FileSystemLoader is not IFileSystemExternalContent content) + if (modData.FileSystemLoader is not IFileSystemExternalContent content) return true; - return !content.InstallContentIfRequired(ModData); + return !content.InstallContentIfRequired(modData); } } } diff --git a/OpenRA.Mods.Common/LoadScreens/LogoStripeLoadScreen.cs b/OpenRA.Mods.Common/LoadScreens/LogoStripeLoadScreen.cs index ec3aa6aa35..26c35f4afe 100644 --- a/OpenRA.Mods.Common/LoadScreens/LogoStripeLoadScreen.cs +++ b/OpenRA.Mods.Common/LoadScreens/LogoStripeLoadScreen.cs @@ -9,8 +9,8 @@ */ #endregion -using System.Collections.Generic; using System.Linq; +using OpenRA.FileSystem; using OpenRA.Graphics; using OpenRA.Mods.Common.Widgets; using OpenRA.Primitives; @@ -32,9 +32,9 @@ namespace OpenRA.Mods.Common.LoadScreens string[] messages = []; - public override void Init(ModData modData, Dictionary info) + public override void Init(Manifest manifest, IReadOnlyFileSystem fileSystem) { - base.Init(modData, info); + base.Init(manifest, fileSystem); messages = FluentProvider.GetMessage(Loading).Split(',').Select(x => x.Trim()).ToArray(); } diff --git a/OpenRA.Mods.Common/LoadScreens/ModContentLoadScreen.cs b/OpenRA.Mods.Common/LoadScreens/ModContentLoadScreen.cs index 8278d305e9..3eeaf3ceec 100644 --- a/OpenRA.Mods.Common/LoadScreens/ModContentLoadScreen.cs +++ b/OpenRA.Mods.Common/LoadScreens/ModContentLoadScreen.cs @@ -48,7 +48,7 @@ namespace OpenRA.Mods.Common.LoadScreens Ui.LoadWidget("MODCONTENT_BACKGROUND", Ui.Root, []); } - public override bool BeforeLoad() + public override bool BeforeLoad(ModData modData) { return true; } diff --git a/OpenRA.Mods.Common/LoadScreens/SheetLoadScreen.cs b/OpenRA.Mods.Common/LoadScreens/SheetLoadScreen.cs index e80465b949..b0536acc69 100644 --- a/OpenRA.Mods.Common/LoadScreens/SheetLoadScreen.cs +++ b/OpenRA.Mods.Common/LoadScreens/SheetLoadScreen.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using System.Diagnostics; +using OpenRA.FileSystem; using OpenRA.Graphics; using OpenRA.Primitives; @@ -26,10 +27,10 @@ namespace OpenRA.Mods.Common.LoadScreens Sheet sheet; int density; - public override void Init(ModData modData, Dictionary info) + public override void Init(Manifest manifest, IReadOnlyFileSystem fileSystem) { - base.Init(modData, info); - Info = info; + base.Init(manifest, fileSystem); + Info = manifest.LoadScreen.ToDictionary(my => my.Value); } public abstract void DisplayInner(Renderer r, Sheet s, int density); @@ -70,7 +71,7 @@ namespace OpenRA.Mods.Common.LoadScreens density = 2; } - using (var stream = ModData.DefaultFileSystem.Open(Platform.ResolvePath(image))) + using (var stream = fileSystem.Open(Platform.ResolvePath(image))) { sheet = new Sheet(SheetType.BGRA, stream); sheet.GetTexture().ScaleFilter = TextureScaleFilter.Linear; diff --git a/OpenRA.Mods.Common/UtilityCommands/MapCommand.cs b/OpenRA.Mods.Common/UtilityCommands/MapCommand.cs index 9c203b15a2..168cb69f3c 100644 --- a/OpenRA.Mods.Common/UtilityCommands/MapCommand.cs +++ b/OpenRA.Mods.Common/UtilityCommands/MapCommand.cs @@ -54,7 +54,7 @@ namespace OpenRA.Mods.Common.UtilityCommands // HACK: The engine code assumes that Game.modData is set. // HACK: We know that maps can only be oramap or folders, which are ReadWrite var modData = Game.ModData = utility.ModData; - modData.MapCache.LoadMaps(); + modData.MapCache.LoadMaps(modData); foreach (var kv in modData.MapCache.MapLocations) { foreach (var mapFilename in kv.Key.Contents) diff --git a/OpenRA.Server/Program.cs b/OpenRA.Server/Program.cs index 605c84ac35..29325e7de5 100644 --- a/OpenRA.Server/Program.cs +++ b/OpenRA.Server/Program.cs @@ -94,7 +94,7 @@ namespace OpenRA.Server // HACK: The engine code *still* assumes that Game.ModData is set var modData = Game.ModData = new ModData(mods[modID], mods); modData.MapCache.LoadPreviewImages = false; // PERF: Server doesn't need previews, save memory by not loading them. - modData.MapCache.LoadMaps(); + modData.MapCache.LoadMaps(modData); var endpoints = new List { new(IPAddress.IPv6Any, settings.ListenPort), new(IPAddress.Any, settings.ListenPort) }; var server = new Server(endpoints, settings, modData, ServerType.Dedicated);