From 7292bd20b395364ac20b3b882395610814e1ce67 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 8 Mar 2015 17:43:41 +0000 Subject: [PATCH] Move Sequence parsing into mod code. --- OpenRA.Game/Graphics/SequenceProvider.cs | 51 +++++++------ OpenRA.Game/Manifest.cs | 14 +++- OpenRA.Game/ModData.cs | 15 +++- OpenRA.Game/OpenRA.Game.csproj | 1 - .../Graphics/DefaultSpriteSequence.cs | 71 +++++++++++++------ OpenRA.Mods.Common/OpenRA.Mods.Common.csproj | 1 + mods/cnc/mod.yaml | 2 + mods/d2k/mod.yaml | 2 + mods/modchooser/mod.yaml | 4 +- mods/ra/mod.yaml | 2 + mods/ts/mod.yaml | 2 + 11 files changed, 114 insertions(+), 51 deletions(-) rename OpenRA.Game/Graphics/Sequence.cs => OpenRA.Mods.Common/Graphics/DefaultSpriteSequence.cs (67%) diff --git a/OpenRA.Game/Graphics/SequenceProvider.cs b/OpenRA.Game/Graphics/SequenceProvider.cs index 8342f916cd..b6b558c74c 100644 --- a/OpenRA.Game/Graphics/SequenceProvider.cs +++ b/OpenRA.Game/Graphics/SequenceProvider.cs @@ -12,12 +12,36 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; namespace OpenRA.Graphics { using Sequences = IReadOnlyDictionary>>; using UnitSequences = Lazy>; + public interface ISpriteSequence + { + string Name { get; } + int Start { get; } + int Length { get; } + int Stride { get; } + int Facings { get; } + int Tick { get; } + int ZOffset { get; } + int ShadowStart { get; } + int ShadowZOffset { get; } + int[] Frames { get; } + + Sprite GetSprite(int frame); + Sprite GetSprite(int frame, int facing); + Sprite GetShadow(int frame, int facing); + } + + public interface ISpriteSequenceLoader + { + IReadOnlyDictionary ParseUnitSequences(ModData modData, TileSet tileSet, SpriteCache cache, MiniYamlNode node); + } + public class SequenceProvider { readonly Lazy sequences; @@ -77,6 +101,7 @@ namespace OpenRA.Graphics public sealed class SequenceCache : IDisposable { readonly ModData modData; + readonly TileSet tileSet; readonly Lazy spriteCache; public SpriteCache SpriteCache { get { return spriteCache.Value; } } @@ -85,7 +110,9 @@ namespace OpenRA.Graphics public SequenceCache(ModData modData, TileSet tileSet) { this.modData = modData; + this.tileSet = tileSet; + // Every time we load a tile set, we create a sequence cache for it spriteCache = Exts.Lazy(() => new SpriteCache(modData.SpriteLoaders, tileSet.Extensions, new SheetBuilder(SheetType.Indexed))); } @@ -116,7 +143,7 @@ namespace OpenRA.Graphics items.Add(node.Key, t); else { - t = Exts.Lazy(() => CreateUnitSequences(node)); + t = Exts.Lazy(() => modData.SpriteSequenceLoader.ParseUnitSequences(modData, tileSet, SpriteCache, node)); sequenceCache.Add(key, t); items.Add(node.Key, t); } @@ -125,28 +152,6 @@ namespace OpenRA.Graphics return new ReadOnlyDictionary(items); } - IReadOnlyDictionary CreateUnitSequences(MiniYamlNode node) - { - var unitSequences = new Dictionary(); - - foreach (var kvp in node.Value.ToDictionary()) - { - using (new Support.PerfTimer("new Sequence(\"{0}\")".F(node.Key), 20)) - { - try - { - unitSequences.Add(kvp.Key, new Sequence(spriteCache.Value, node.Key, kvp.Key, kvp.Value)); - } - catch (FileNotFoundException ex) - { - Log.Write("debug", ex.Message); - } - } - } - - return new ReadOnlyDictionary(unitSequences); - } - public void Dispose() { if (spriteCache.IsValueCreated) diff --git a/OpenRA.Game/Manifest.cs b/OpenRA.Game/Manifest.cs index e749ba3db2..fe3945141e 100644 --- a/OpenRA.Game/Manifest.cs +++ b/OpenRA.Game/Manifest.cs @@ -20,6 +20,17 @@ namespace OpenRA public enum TileShape { Rectangle, Diamond } public interface IGlobalModData { } + public sealed class SpriteSequenceFormat : IGlobalModData + { + public readonly string Type; + public readonly IReadOnlyDictionary Metadata; + public SpriteSequenceFormat(MiniYaml yaml) + { + Type = yaml.Value; + Metadata = new ReadOnlyDictionary(yaml.ToDictionary()); + } + } + // Describes what is to be loaded in order to run a mod public class Manifest { @@ -34,6 +45,7 @@ namespace OpenRA public readonly IReadOnlyDictionary MapFolders; public readonly MiniYaml LoadScreen; public readonly MiniYaml LobbyDefaults; + public readonly Dictionary> Fonts; public readonly Size TileSize = new Size(24, 24); public readonly TileShape TileShape = TileShape.Rectangle; @@ -155,7 +167,7 @@ namespace OpenRA throw new InvalidDataException("`{0}` is not a valid mod manifest entry.".F(kv.Key)); IGlobalModData module; - var ctor = t.GetConstructor(new Type[] { typeof(MiniYaml) } ); + var ctor = t.GetConstructor(new Type[] { typeof(MiniYaml) }); if (ctor != null) { // Class has opted-in to DIY initialization diff --git a/OpenRA.Game/ModData.cs b/OpenRA.Game/ModData.cs index 8cba16ed01..38cacea1fd 100644 --- a/OpenRA.Game/ModData.cs +++ b/OpenRA.Game/ModData.cs @@ -25,6 +25,7 @@ namespace OpenRA public readonly WidgetLoader WidgetLoader; public readonly MapCache MapCache; public readonly ISpriteLoader[] SpriteLoaders; + public readonly ISpriteSequenceLoader SpriteSequenceLoader; public readonly RulesetCache RulesetCache; public ILoadScreen LoadScreen { get; private set; } public VoxelLoader VoxelLoader { get; private set; } @@ -52,17 +53,25 @@ namespace OpenRA RulesetCache.LoadingProgress += HandleLoadingProgress; MapCache = new MapCache(this); - var loaders = new List(); + var spriteLoaders = new List(); foreach (var format in Manifest.SpriteFormats) { var loader = ObjectCreator.FindType(format + "Loader"); if (loader == null || !loader.GetInterfaces().Contains(typeof(ISpriteLoader))) throw new InvalidOperationException("Unable to find a sprite loader for type '{0}'.".F(format)); - loaders.Add((ISpriteLoader)ObjectCreator.CreateBasic(loader)); + spriteLoaders.Add((ISpriteLoader)ObjectCreator.CreateBasic(loader)); } - SpriteLoaders = loaders.ToArray(); + SpriteLoaders = spriteLoaders.ToArray(); + + var sequenceFormat = Manifest.Get(); + var sequenceLoader = ObjectCreator.FindType(sequenceFormat.Type + "Loader"); + var ctor = sequenceLoader != null ? sequenceLoader.GetConstructor(new[] { typeof(ModData) }) : null; + if (sequenceLoader == null || !sequenceLoader.GetInterfaces().Contains(typeof(ISpriteSequenceLoader)) || ctor == null) + throw new InvalidOperationException("Unable to find a sequence loader for type '{0}'.".F(sequenceFormat.Type)); + + SpriteSequenceLoader = (ISpriteSequenceLoader)ctor.Invoke(new[] { this }); // HACK: Mount only local folders so we have a half-working environment for the asset installer GlobalFileSystem.UnmountAll(); diff --git a/OpenRA.Game/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj index ae14e7c544..e86c309d37 100644 --- a/OpenRA.Game/OpenRA.Game.csproj +++ b/OpenRA.Game/OpenRA.Game.csproj @@ -109,7 +109,6 @@ - diff --git a/OpenRA.Game/Graphics/Sequence.cs b/OpenRA.Mods.Common/Graphics/DefaultSpriteSequence.cs similarity index 67% rename from OpenRA.Game/Graphics/Sequence.cs rename to OpenRA.Mods.Common/Graphics/DefaultSpriteSequence.cs index 5eb52fa56d..c7abb06b0b 100644 --- a/OpenRA.Game/Graphics/Sequence.cs +++ b/OpenRA.Mods.Common/Graphics/DefaultSpriteSequence.cs @@ -9,33 +9,53 @@ #endregion using System; +using System.Collections.Generic; +using System.IO; using System.Linq; +using OpenRA.Graphics; -namespace OpenRA.Graphics +namespace OpenRA.Mods.Common.Graphics { - public interface ISpriteSequence + public class DefaultSpriteSequenceLoader : ISpriteSequenceLoader { - string Name { get; } - int Start { get; } - int Length { get; } - int Stride { get; } - int Facings { get; } - int Tick { get; } - int ZOffset { get; } - int ShadowStart { get; } - int ShadowZOffset { get; } - int[] Frames { get; } + public DefaultSpriteSequenceLoader(ModData modData) { } - Sprite GetSprite(int frame); - Sprite GetSprite(int frame, int facing); - Sprite GetShadow(int frame, int facing); + public virtual ISpriteSequence CreateSequence(ModData modData, TileSet tileSet, SpriteCache cache, string unit, string name, MiniYaml info) + { + return new DefaultSpriteSequence(modData, tileSet, cache, this, unit, name, info); + } + + public IReadOnlyDictionary ParseUnitSequences(ModData modData, TileSet tileSet, SpriteCache cache, MiniYamlNode node) + { + var unitSequences = new Dictionary(); + + foreach (var kvp in node.Value.ToDictionary()) + { + using (new Support.PerfTimer("new Sequence(\"{0}\")".F(node.Key), 20)) + { + try + { + unitSequences.Add(kvp.Key, CreateSequence(modData, tileSet, cache, node.Key, kvp.Key, kvp.Value)); + } + catch (FileNotFoundException ex) + { + // Eat the FileNotFound exceptions from missing sprites + Log.Write("debug", ex.Message); + } + } + } + + return new ReadOnlyDictionary(unitSequences); + } } - public class Sequence : ISpriteSequence + public class DefaultSpriteSequence : ISpriteSequence { readonly Sprite[] sprites; readonly bool reverseFacings, transpose; + protected readonly ISpriteSequenceLoader Loader; + public string Name { get; private set; } public int Start { get; private set; } public int Length { get; private set; } @@ -47,10 +67,16 @@ namespace OpenRA.Graphics public int ShadowZOffset { get; private set; } public int[] Frames { get; private set; } - public Sequence(SpriteCache cache, string unit, string name, MiniYaml info) + protected virtual string GetSpriteSrc(ModData modData, TileSet tileSet, string unit, string name, MiniYaml info, Dictionary d) + { + return info.Value ?? unit; + } + + public DefaultSpriteSequence(ModData modData, TileSet tileSet, SpriteCache cache, ISpriteSequenceLoader loader, string unit, string name, MiniYaml info) { - var srcOverride = info.Value; Name = name; + Loader = loader; + var d = info.ToDictionary(); var offset = float2.Zero; var blendMode = BlendMode.Alpha; @@ -68,7 +94,8 @@ namespace OpenRA.Graphics // Apply offset to each sprite in the sequence // Different sequences may apply different offsets to the same frame - sprites = cache[srcOverride ?? unit].Select( + var src = GetSpriteSrc(modData, tileSet, unit, name, info, d); + sprites = cache[src].Select( s => new Sprite(s.Sheet, s.Bounds, s.Offset + offset, s.Channel, blendMode)).ToArray(); if (!d.ContainsKey("Length")) @@ -132,7 +159,7 @@ namespace OpenRA.Graphics if (Start < 0 || Start + Facings * Stride > sprites.Length || ShadowStart + Facings * Stride > sprites.Length) throw new InvalidOperationException( "{6}: Sequence {0}.{1} uses frames [{2}..{3}] of SHP `{4}`, but only 0..{5} actually exist" - .F(unit, name, Start, Start + Facings * Stride - 1, srcOverride ?? unit, sprites.Length - 1, + .F(unit, name, Start, Start + Facings * Stride - 1, src, sprites.Length - 1, info.Nodes[0].Location)); } catch (FormatException f) @@ -156,9 +183,9 @@ namespace OpenRA.Graphics return ShadowStart >= 0 ? GetSprite(ShadowStart, frame, facing) : null; } - Sprite GetSprite(int start, int frame, int facing) + protected virtual Sprite GetSprite(int start, int frame, int facing) { - var f = Traits.Util.QuantizeFacing(facing, Facings); + var f = OpenRA.Traits.Util.QuantizeFacing(facing, Facings); if (reverseFacings) f = (Facings - f) % Facings; diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index 4010600547..c691fb8526 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -577,6 +577,7 @@ + diff --git a/mods/cnc/mod.yaml b/mods/cnc/mod.yaml index 0cfff7afb0..33d5238b0c 100644 --- a/mods/cnc/mod.yaml +++ b/mods/cnc/mod.yaml @@ -203,3 +203,5 @@ Missions: SupportsMapsFrom: cnc SpriteFormats: ShpTD, TmpTD, ShpTS, TmpRA + +SpriteSequenceFormat: DefaultSpriteSequence diff --git a/mods/d2k/mod.yaml b/mods/d2k/mod.yaml index 7e1fcd2fcf..b7936e89e0 100644 --- a/mods/d2k/mod.yaml +++ b/mods/d2k/mod.yaml @@ -178,3 +178,5 @@ Fonts: SupportsMapsFrom: d2k SpriteFormats: R8, ShpTD, TmpRA + +SpriteSequenceFormat: DefaultSpriteSequence diff --git a/mods/modchooser/mod.yaml b/mods/modchooser/mod.yaml index 4be6107424..3309f74d2d 100644 --- a/mods/modchooser/mod.yaml +++ b/mods/modchooser/mod.yaml @@ -51,4 +51,6 @@ Fonts: LobbyDefaults: -SpriteFormats: ShpTD \ No newline at end of file +SpriteFormats: ShpTD + +SpriteSequenceFormat: DefaultSpriteSequence diff --git a/mods/ra/mod.yaml b/mods/ra/mod.yaml index 25680a7853..f61fad5276 100644 --- a/mods/ra/mod.yaml +++ b/mods/ra/mod.yaml @@ -201,3 +201,5 @@ Missions: SupportsMapsFrom: ra SpriteFormats: ShpTD, TmpRA, TmpTD, ShpTS + +SpriteSequenceFormat: DefaultSpriteSequence diff --git a/mods/ts/mod.yaml b/mods/ts/mod.yaml index fadf21d7a0..8e2a31618f 100644 --- a/mods/ts/mod.yaml +++ b/mods/ts/mod.yaml @@ -219,3 +219,5 @@ Fonts: SupportsMapsFrom: ts SpriteFormats: ShpTS, TmpTS, ShpTD + +SpriteSequenceFormat: DefaultSpriteSequence