diff --git a/OpenRA.Game/Game.cs b/OpenRA.Game/Game.cs index 131e89c8a8..b92382d5d2 100644 --- a/OpenRA.Game/Game.cs +++ b/OpenRA.Game/Game.cs @@ -513,7 +513,7 @@ namespace OpenRA ModData.MapCache.LoadMaps(); Cursor?.Dispose(); - Cursor = new CursorManager(ModData.CursorProvider, ModData.Manifest.CursorSheetSize); + Cursor = new CursorManager(ModData); var metadata = ModData.Manifest.Metadata; if (!string.IsNullOrEmpty(metadata.WindowTitleTranslated)) @@ -740,15 +740,12 @@ namespace OpenRA Ui.Draw(); - if (ModData != null && ModData.CursorProvider != null) + if (HideCursor) + Cursor?.SetCursor(null); + else { - if (HideCursor) - Cursor.SetCursor(null); - else - { - Cursor.SetCursor(Ui.Root.GetCursorOuter(Viewport.LastMousePos) ?? "default"); - Cursor.Render(Renderer); - } + Cursor?.SetCursor(Ui.Root.GetCursorOuter(Viewport.LastMousePos) ?? "default"); + Cursor?.Render(Renderer); } } diff --git a/OpenRA.Game/Graphics/CursorManager.cs b/OpenRA.Game/Graphics/CursorManager.cs index d86b619c22..5054a8be15 100644 --- a/OpenRA.Game/Graphics/CursorManager.cs +++ b/OpenRA.Game/Graphics/CursorManager.cs @@ -13,6 +13,7 @@ using System; using System.Collections.Generic; using System.Linq; using OpenRA.Primitives; +using OpenRA.Traits; namespace OpenRA.Graphics { @@ -39,19 +40,36 @@ namespace OpenRA.Graphics readonly bool hardwareCursorsDisabled = false; bool hardwareCursorsDoubled = false; - public CursorManager(CursorProvider cursorProvider, int cursorSheetSize) + public CursorManager(ModData modData) { hardwareCursorsDisabled = Game.Settings.Graphics.DisableHardwareCursors; graphicSettings = Game.Settings.Graphics; - SheetBuilder = new SheetBuilder(SheetType.BGRA, cursorSheetSize); + SheetBuilder = new SheetBuilder(SheetType.BGRA, modData.Manifest.CursorSheetSize); + + // Overwrite previous definitions if there are duplicates + var pals = new Dictionary(); + foreach (var p in modData.DefaultRules.Actors[SystemActors.World].TraitInfos()) + if (p.Palette != null) + pals[p.Palette] = p; + + var paletteCache = new Cache(p => pals[p].ReadPalette(modData.DefaultFileSystem)); + var frameCache = new FrameCache(modData.DefaultFileSystem, modData.SpriteLoaders); // Sort the cursors for better packing onto the sheet. - foreach (var kv in cursorProvider.Cursors - .OrderBy(kvp => kvp.Value.Frames.Max(f => f.Size.Height))) + foreach (var kv in modData.Cursors) { - var frames = kv.Value.Frames; - var palette = !string.IsNullOrEmpty(kv.Value.Palette) ? cursorProvider.Palettes[kv.Value.Palette] : null; + var cursorSprites = frameCache[kv.Value.Src]; + var length = kv.Value.Length ?? cursorSprites.Length - kv.Value.Start; + + if (kv.Value.Start > cursorSprites.Length) + throw new YamlException($"Cursor {kv.Value.Name}: {nameof(kv.Value.Start)} is greater than the length of the sprite sequence."); + + if (kv.Value.Length > cursorSprites.Length) + throw new YamlException($"Cursor {kv.Value.Name}: {nameof(kv.Value.Length)} is greater than the length of the sprite sequence."); + + var frames = cursorSprites.Skip(kv.Value.Start).Take(length).ToArray(); + var palette = !string.IsNullOrEmpty(kv.Value.Palette) ? paletteCache[kv.Value.Palette] : null; var c = new Cursor { diff --git a/OpenRA.Game/Graphics/CursorProvider.cs b/OpenRA.Game/Graphics/CursorProvider.cs deleted file mode 100644 index 831a3db7dc..0000000000 --- a/OpenRA.Game/Graphics/CursorProvider.cs +++ /dev/null @@ -1,67 +0,0 @@ -#region Copyright & License Information -/* - * Copyright (c) The OpenRA Developers and Contributors - * 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, either version 3 of - * the License, or (at your option) any later version. For more - * information, see COPYING. - */ -#endregion - -using System; -using System.Collections.Generic; -using System.Linq; -using OpenRA.Traits; - -namespace OpenRA.Graphics -{ - public sealed class CursorProvider - { - public readonly IReadOnlyDictionary Cursors; - public readonly IReadOnlyDictionary Palettes; - - public CursorProvider(ModData modData) - { - var fileSystem = modData.DefaultFileSystem; - var stringPool = new HashSet(); // Reuse common strings in YAML - var sequenceYaml = MiniYaml.Merge(modData.Manifest.Cursors.Select( - s => MiniYaml.FromStream(fileSystem.Open(s), s, stringPool: stringPool))); - - var cursorsYaml = new MiniYaml(null, sequenceYaml).NodeWithKey("Cursors").Value; - - // Overwrite previous definitions if there are duplicates - var pals = new Dictionary(); - foreach (var p in modData.DefaultRules.Actors[SystemActors.World].TraitInfos()) - if (p.Palette != null) - pals[p.Palette] = p; - - Palettes = cursorsYaml.Nodes.Select(n => n.Value.Value) - .Where(p => p != null) - .Distinct() - .ToDictionary(p => p, p => pals[p].ReadPalette(modData.DefaultFileSystem)); - - var frameCache = new FrameCache(fileSystem, modData.SpriteLoaders); - var cursors = new Dictionary(); - foreach (var s in cursorsYaml.Nodes) - foreach (var sequence in s.Value.Nodes) - cursors.Add(sequence.Key, new CursorSequence(frameCache, sequence.Key, s.Key, s.Value.Value, sequence.Value)); - - Cursors = cursors; - } - - public bool HasCursorSequence(string cursor) - { - return Cursors.ContainsKey(cursor); - } - - public CursorSequence GetCursorSequence(string cursor) - { - try { return Cursors[cursor]; } - catch (KeyNotFoundException) - { - throw new InvalidOperationException($"Cursor does not have a sequence `{cursor}`"); - } - } - } -} diff --git a/OpenRA.Game/Graphics/CursorSequence.cs b/OpenRA.Game/Graphics/CursorSequence.cs index e5dfaf2017..b356050062 100644 --- a/OpenRA.Game/Graphics/CursorSequence.cs +++ b/OpenRA.Game/Graphics/CursorSequence.cs @@ -9,50 +9,26 @@ */ #endregion -using System.Linq; - namespace OpenRA.Graphics { public class CursorSequence { public readonly string Name; + public readonly string Src; public readonly int Start; - public readonly int Length; + public readonly int? Length; public readonly string Palette; public readonly int2 Hotspot; - public readonly ISpriteFrame[] Frames; - - public CursorSequence(FrameCache cache, string name, string cursorSrc, string palette, MiniYaml info) + public CursorSequence(string name, string cursorSrc, string palette, MiniYaml info) { var d = info.ToDictionary(); - Start = Exts.ParseInt32Invariant(d["Start"].Value); Palette = palette; Name = name; + Src = cursorSrc; - var cursorSprites = cache[cursorSrc]; - Frames = cursorSprites.Skip(Start).ToArray(); - - if ((d.TryGetValue("Length", out var yaml) && yaml.Value == "*") || - (d.TryGetValue("End", out yaml) && yaml.Value == "*")) - Length = Frames.Length; - else if (d.TryGetValue("Length", out yaml)) - Length = Exts.ParseInt32Invariant(yaml.Value); - else if (d.TryGetValue("End", out yaml)) - Length = Exts.ParseInt32Invariant(yaml.Value) - Start; - else - Length = 1; - - Frames = Frames.Take(Length).ToArray(); - - if (Start > cursorSprites.Length) - throw new YamlException($"Cursor {name}: {nameof(Start)} is greater than the length of the sprite sequence."); - - if (Length > cursorSprites.Length) - throw new YamlException($"Cursor {name}: {nameof(Length)} is greater than the length of the sprite sequence."); - - if (d.TryGetValue("X", out yaml)) + if (d.TryGetValue("X", out var yaml)) { Exts.TryParseInt32Invariant(yaml.Value, out var x); Hotspot = Hotspot.WithX(x); @@ -63,6 +39,14 @@ namespace OpenRA.Graphics Exts.TryParseInt32Invariant(yaml.Value, out var y); Hotspot = Hotspot.WithY(y); } + + Start = Exts.ParseInt32Invariant(d["Start"].Value); + if (d.TryGetValue("Length", out yaml)) + Length = yaml.Value != "*" ? Exts.ParseInt32Invariant(yaml.Value) : null; + else if (d.TryGetValue("End", out yaml) && yaml.Value == "*") + Length = yaml.Value != "*" ? Exts.ParseInt32Invariant(yaml.Value) - Start : null; + else + Length = 1; } } } diff --git a/OpenRA.Game/ModData.cs b/OpenRA.Game/ModData.cs index 65cdf98d08..6208cbf298 100644 --- a/OpenRA.Game/ModData.cs +++ b/OpenRA.Game/ModData.cs @@ -10,6 +10,7 @@ #endregion using System; +using System.Collections.Frozen; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; @@ -35,9 +36,9 @@ namespace OpenRA public readonly IVideoLoader[] VideoLoaders; public readonly HotkeyManager Hotkeys; public readonly IFileSystemLoader FileSystemLoader; + public readonly FrozenDictionary Cursors; public ILoadScreen LoadScreen { get; } - public CursorProvider CursorProvider { get; private set; } public FS ModFiles; public IReadOnlyFileSystem DefaultFileSystem => ModFiles; @@ -97,6 +98,7 @@ namespace OpenRA SpriteSequenceLoader = (ISpriteSequenceLoader)sequenceCtor.Invoke([this]); Hotkeys = new HotkeyManager(ModFiles, Game.Settings.Keys, Manifest); + Cursors = ParseCursors(Manifest, DefaultFileSystem); defaultRules = Exts.Lazy(() => Ruleset.LoadDefaults(this)); defaultTerrainInfo = Exts.Lazy(() => @@ -115,6 +117,23 @@ namespace OpenRA initialThreadId = Environment.CurrentManagedThreadId; } + static FrozenDictionary ParseCursors(Manifest manifest, IReadOnlyFileSystem fileSystem) + { + var stringPool = new HashSet(); // Reuse common strings in YAML + var sequenceYaml = MiniYaml.Merge(manifest.Cursors.Select( + s => MiniYaml.FromStream(fileSystem.Open(s), s, stringPool: stringPool))); + + var cursors = new Dictionary(); + foreach (var node in sequenceYaml) + if (node.Key == "Cursors") + foreach (var fileNode in node.Value.Nodes) + foreach (var sequenceNode in fileNode.Value.Nodes) + cursors.Add(sequenceNode.Key, new CursorSequence( + sequenceNode.Key, fileNode.Key, fileNode.Value.Value, sequenceNode.Value)); + + return cursors.ToFrozenDictionary(); + } + // HACK: Only update the loading screen if we're in the main thread. readonly int initialThreadId; internal void HandleLoadingProgress() @@ -134,8 +153,6 @@ namespace OpenRA FluentProvider.Initialize(this, fileSystem); Game.Sound.Initialize(SoundLoaders, fileSystem); - - CursorProvider = new CursorProvider(this); } public IEnumerable Languages { get; } diff --git a/OpenRA.Mods.Common/UtilityCommands/DumpSequenceSheetsCommand.cs b/OpenRA.Mods.Common/UtilityCommands/DumpSequenceSheetsCommand.cs index 45b0a81a01..519c21f3c2 100644 --- a/OpenRA.Mods.Common/UtilityCommands/DumpSequenceSheetsCommand.cs +++ b/OpenRA.Mods.Common/UtilityCommands/DumpSequenceSheetsCommand.cs @@ -65,8 +65,7 @@ namespace OpenRA.Mods.Common.UtilityCommands } var sheetCount = 1; - var cursorProvider = new CursorProvider(modData); - using (var cursor = new CursorManager(cursorProvider, modData.Manifest.CursorSheetSize)) + using (var cursor = new CursorManager(modData)) foreach (var sheet in cursor.SheetBuilder.AllSheets) CommitSheet(null, sheet, "cursors", palette, ref sheetCount); diff --git a/OpenRA.Mods.Common/Widgets/RadarWidget.cs b/OpenRA.Mods.Common/Widgets/RadarWidget.cs index 93252c14d8..66e55b9814 100644 --- a/OpenRA.Mods.Common/Widgets/RadarWidget.cs +++ b/OpenRA.Mods.Common/Widgets/RadarWidget.cs @@ -302,7 +302,7 @@ namespace OpenRA.Mods.Common.Widgets if (cursor == null) return worldDefaultCursor; - return Game.ModData.CursorProvider.HasCursorSequence(cursor + "-minimap") ? cursor + "-minimap" : cursor; + return Game.ModData.Cursors.ContainsKey(cursor + "-minimap") ? cursor + "-minimap" : cursor; } public override bool HandleMouseInput(MouseInput mi)