Reimplement sequence Defaults parsing:

The previous MiniYaml.Merge implementation interacted
poorly with yaml inheritance, making it complicated
(or impossible) to override certain keys from Defaults.

The new implementation is simpler: If a key is defined
it will be used. If it isn't, the default (if defined)
will be used. Defaults can be masked by making sure
the same key is defined (even with an empty value)
in the sequence.

This also fixes naming within the sequence code to
distinguish between images (a group of sequences),
sequences (defining a specific sprite/animation),
and filenames for a specific sprite/animation.
This commit is contained in:
Paul Chote
2022-12-24 13:23:26 +13:00
committed by Pavel Penev
parent 05c83a9dbb
commit 04c3cd6ec5
4 changed files with 279 additions and 299 deletions

View File

@@ -20,9 +20,9 @@ namespace OpenRA.Mods.Cnc.Graphics
public ClassicSpriteSequenceLoader(ModData modData)
: base(modData) { }
public override ISpriteSequence CreateSequence(ModData modData, string tileSet, SpriteCache cache, string sequence, string animation, MiniYaml info)
public override ISpriteSequence CreateSequence(ModData modData, string tileset, SpriteCache cache, string image, string sequence, MiniYaml data, MiniYaml defaults)
{
return new ClassicSpriteSequence(modData, tileSet, cache, this, sequence, animation, info);
return new ClassicSpriteSequence(modData, tileset, cache, this, image, sequence, data, defaults);
}
}
@@ -33,15 +33,13 @@ namespace OpenRA.Mods.Cnc.Graphics
static readonly SpriteSequenceField<bool> UseClassicFacings = new SpriteSequenceField<bool>(nameof(UseClassicFacings), false);
readonly bool useClassicFacings;
public ClassicSpriteSequence(ModData modData, string tileSet, SpriteCache cache, ISpriteSequenceLoader loader, string sequence, string animation, MiniYaml info)
: base(modData, tileSet, cache, loader, sequence, animation, info)
public ClassicSpriteSequence(ModData modData, string tileSet, SpriteCache cache, ISpriteSequenceLoader loader, string image, string sequence, MiniYaml data, MiniYaml defaults)
: base(modData, tileSet, cache, loader, image, sequence, data, defaults)
{
var d = info.ToDictionary();
useClassicFacings = LoadField(d, UseClassicFacings);
useClassicFacings = LoadField(UseClassicFacings, data, defaults);
if (useClassicFacings && facings != 32)
throw new InvalidOperationException(
$"{info.Nodes[0].Location}: Sequence {sequence}.{animation}: UseClassicFacings is only valid for 32 facings");
throw new InvalidOperationException($"Sequence {image}.{sequence}: UseClassicFacings is only valid for 32 facings");
}
protected override int GetFacingFrameOffset(WAngle facing)

View File

@@ -36,9 +36,9 @@ namespace OpenRA.Mods.Cnc.Graphics
TilesetCodes = yaml.ToDictionary(kv => kv.Value);
}
public override ISpriteSequence CreateSequence(ModData modData, string tileSet, SpriteCache cache, string sequence, string animation, MiniYaml info)
public override ISpriteSequence CreateSequence(ModData modData, string tileset, SpriteCache cache, string image, string sequence, MiniYaml data, MiniYaml defaults)
{
return new ClassicTilesetSpecificSpriteSequence(modData, tileSet, cache, this, sequence, animation, info);
return new ClassicTilesetSpecificSpriteSequence(modData, tileset, cache, this, image, sequence, data, defaults);
}
}
@@ -61,42 +61,34 @@ namespace OpenRA.Mods.Cnc.Graphics
[Desc("Whether `mod.yaml`'s `TilesetExtensions` should be used with the sequence's file name.")]
static readonly SpriteSequenceField<bool> UseTilesetExtension = new SpriteSequenceField<bool>(nameof(UseTilesetExtension), false);
public ClassicTilesetSpecificSpriteSequence(ModData modData, string tileSet, SpriteCache cache, ISpriteSequenceLoader loader, string sequence, string animation, MiniYaml info)
: base(modData, tileSet, cache, loader, sequence, animation, info) { }
public ClassicTilesetSpecificSpriteSequence(ModData modData, string tileset, SpriteCache cache, ISpriteSequenceLoader loader, string image, string sequence, MiniYaml data, MiniYaml defaults)
: base(modData, tileset, cache, loader, image, sequence, data, defaults) { }
static string ResolveTilesetId(string tileSet, Dictionary<string, MiniYaml> d)
static string ResolveTilesetId(string tileset, MiniYaml data, MiniYaml defaults)
{
if (d.TryGetValue(nameof(TilesetOverrides), out var yaml))
{
var tsNode = yaml.Nodes.FirstOrDefault(n => n.Key == tileSet);
if (tsNode != null)
tileSet = tsNode.Value.Value;
var node = data.Nodes.FirstOrDefault(n => n.Key == TilesetOverrides.Key) ?? defaults.Nodes.FirstOrDefault(n => n.Key == TilesetOverrides.Key);
var overrideNode = node?.Value.Nodes.FirstOrDefault(n => n.Key == tileset);
return overrideNode?.Value.Value ?? tileset;
}
return tileSet;
}
protected override string GetSpriteSrc(ModData modData, string tileSet, string sequence, string animation, string sprite, Dictionary<string, MiniYaml> d)
protected override string GetSpriteFilename(ModData modData, string tileset, string image, string sequence, MiniYaml data, MiniYaml defaults)
{
var loader = (ClassicTilesetSpecificSpriteSequenceLoader)Loader;
var filename = data.Value ?? defaults.Value ?? image;
if (LoadField(UseTilesetCode, data, defaults))
if (loader.TilesetCodes.TryGetValue(ResolveTilesetId(tileset, data, defaults), out var tilesetCode))
filename = filename.Substring(0, 1) + tilesetCode + filename.Substring(2, filename.Length - 2);
var spriteName = sprite ?? sequence;
if (LoadField(d, UseTilesetCode))
if (LoadField(AddExtension, data, defaults))
{
if (loader.TilesetCodes.TryGetValue(ResolveTilesetId(tileSet, d), out var code))
spriteName = spriteName.Substring(0, 1) + code + spriteName.Substring(2, spriteName.Length - 2);
if (LoadField(UseTilesetExtension, data, defaults))
if (loader.TilesetExtensions.TryGetValue(ResolveTilesetId(tileset, data, defaults), out var tilesetExtension))
return filename + tilesetExtension;
return filename + loader.DefaultSpriteExtension;
}
if (LoadField(d, AddExtension))
{
if (LoadField(d, UseTilesetExtension) && loader.TilesetExtensions.TryGetValue(ResolveTilesetId(tileSet, d), out var tilesetExtension))
return spriteName + tilesetExtension;
return spriteName + loader.DefaultSpriteExtension;
}
return spriteName;
return filename;
}
}
}

View File

@@ -41,49 +41,49 @@ namespace OpenRA.Mods.Common.Graphics
public class DefaultSpriteSequenceLoader : ISpriteSequenceLoader
{
static readonly MiniYaml NoData = new MiniYaml(null);
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "IDE0060:Remove unused parameter", Justification = "Load game API")]
public DefaultSpriteSequenceLoader(ModData modData) { }
public virtual ISpriteSequence CreateSequence(ModData modData, string tileSet, SpriteCache cache, string sequence, string animation, MiniYaml info)
public virtual ISpriteSequence CreateSequence(ModData modData, string tileset, SpriteCache cache, string image, string sequence, MiniYaml data, MiniYaml defaults)
{
return new DefaultSpriteSequence(modData, tileSet, cache, this, sequence, animation, info);
return new DefaultSpriteSequence(modData, tileset, cache, this, image, sequence, data, defaults);
}
public IReadOnlyDictionary<string, ISpriteSequence> ParseSequences(ModData modData, string tileSet, SpriteCache cache, MiniYamlNode node)
public IReadOnlyDictionary<string, ISpriteSequence> ParseSequences(ModData modData, string tileSet, SpriteCache cache, MiniYamlNode imageNode)
{
var sequences = new Dictionary<string, ISpriteSequence>();
var nodes = node.Value.ToDictionary();
MiniYaml defaults;
try
{
if (nodes.TryGetValue("Defaults", out var defaults))
{
nodes.Remove("Defaults");
foreach (var n in nodes)
{
n.Value.Nodes = MiniYaml.Merge(new[] { defaults.Nodes, n.Value.Nodes });
n.Value.Value = n.Value.Value ?? defaults.Value;
}
}
var node = imageNode.Value.Nodes.SingleOrDefault(n => n.Key == "Defaults");
defaults = node?.Value ?? NoData;
imageNode.Value.Nodes.Remove(node);
}
catch (Exception e)
{
throw new InvalidDataException($"Error occurred while parsing {node.Key}", e);
throw new InvalidDataException($"Error occurred while parsing {imageNode.Key}", e);
}
foreach (var kvp in nodes)
foreach (var sequenceNode in imageNode.Value.Nodes)
{
using (new Support.PerfTimer($"new Sequence(\"{node.Key}\")", 20))
using (new Support.PerfTimer($"new Sequence(\"{imageNode.Key}\")", 20))
{
try
{
sequences.Add(kvp.Key, CreateSequence(modData, tileSet, cache, node.Key, kvp.Key, kvp.Value));
var sequence = CreateSequence(modData, tileSet, cache, imageNode.Key, sequenceNode.Key, sequenceNode.Value, defaults);
sequences.Add(sequenceNode.Key, sequence);
}
catch (FileNotFoundException ex)
{
// Defer exception until something tries to access the sequence
// This allows the asset installer and OpenRA.Utility to load the game without having the actor assets
sequences.Add(kvp.Key, new FileNotFoundSequence(ex));
sequences.Add(sequenceNode.Key, new FileNotFoundSequence(ex));
}
catch (FormatException f)
{
throw new FormatException($"Failed to parse sequences for {imageNode.Key}.{sequenceNode.Key} at {imageNode.Value.Nodes[0].Location}:\n{f}");
}
}
}
@@ -139,9 +139,11 @@ namespace OpenRA.Mods.Common.Graphics
[Desc("Generic sprite sequence implementation, mostly unencumbered with game- or artwork-specific logic.")]
public class DefaultSpriteSequence : ISpriteSequence
{
static readonly MiniYaml NoData = new MiniYaml(null);
readonly string image;
protected Sprite[] sprites;
readonly bool reverseFacings, transpose;
readonly string sequence;
protected readonly ISpriteSequenceLoader Loader;
@@ -257,25 +259,27 @@ namespace OpenRA.Mods.Common.Graphics
public readonly uint[] EmbeddedPalette;
protected virtual string GetSpriteSrc(ModData modData, string tileSet, string sequence, string animation, string sprite, Dictionary<string, MiniYaml> d)
protected virtual string GetSpriteFilename(ModData modData, string tileset, string image, string sequence, MiniYaml data, MiniYaml defaults)
{
return sprite ?? sequence;
return data.Value ?? defaults.Value ?? image;
}
protected static T LoadField<T>(Dictionary<string, MiniYaml> d, string key, T fallback)
protected static T LoadField<T>(string key, T fallback, MiniYaml data, MiniYaml defaults)
{
if (d.TryGetValue(key, out var value))
return FieldLoader.GetValue<T>(key, value.Value);
var node = data.Nodes.FirstOrDefault(n => n.Key == key) ?? defaults.Nodes.FirstOrDefault(n => n.Key == key);
if (node == null)
return fallback;
return FieldLoader.GetValue<T>(key, node.Value.Value);
}
protected static T LoadField<T>(Dictionary<string, MiniYaml> d, SpriteSequenceField<T> field)
protected static T LoadField<T>(SpriteSequenceField<T> field, MiniYaml data, MiniYaml defaults)
{
if (d.TryGetValue(field.Key, out var value))
return FieldLoader.GetValue<T>(field.Key, value.Value);
var node = data.Nodes.FirstOrDefault(n => n.Key == field.Key) ?? defaults.Nodes.FirstOrDefault(n => n.Key == field.Key);
if (node == null)
return field.DefaultValue;
return FieldLoader.GetValue<T>(field.Key, node.Value.Value);
}
protected static Rectangle FlipRectangle(Rectangle rect, bool flipX, bool flipY)
@@ -288,31 +292,28 @@ namespace OpenRA.Mods.Common.Graphics
return Rectangle.FromLTRB(left, top, right, bottom);
}
public DefaultSpriteSequence(ModData modData, string tileSet, SpriteCache cache, ISpriteSequenceLoader loader, string sequence, string animation, MiniYaml info)
public DefaultSpriteSequence(ModData modData, string tileSet, SpriteCache cache, ISpriteSequenceLoader loader, string image, string sequence, MiniYaml data, MiniYaml defaults)
{
this.sequence = sequence;
Name = animation;
this.image = image;
Name = sequence;
Loader = loader;
var d = info.ToDictionary();
try
{
start = LoadField(d, Start);
shadowStart = LoadField(d, ShadowStart);
shadowZOffset = LoadField(d, ShadowZOffset).Length;
zOffset = LoadField(d, ZOffset).Length;
tick = LoadField(d, Tick);
transpose = LoadField(d, Transpose);
frames = LoadField(d, Frames);
ignoreWorldTint = LoadField(d, IgnoreWorldTint);
scale = LoadField(d, Scale);
start = LoadField(Start, data, defaults);
shadowStart = LoadField(ShadowStart, data, defaults);
shadowZOffset = LoadField(ShadowZOffset, data, defaults).Length;
zOffset = LoadField(ZOffset, data, defaults).Length;
tick = LoadField(Tick, data, defaults);
transpose = LoadField(Transpose, data, defaults);
frames = LoadField(Frames, data, defaults);
ignoreWorldTint = LoadField(IgnoreWorldTint, data, defaults);
scale = LoadField(Scale, data, defaults);
var flipX = LoadField(d, FlipX);
var flipY = LoadField(d, FlipY);
var zRamp = LoadField(d, ZRamp);
var flipX = LoadField(FlipX, data, defaults);
var flipY = LoadField(FlipY, data, defaults);
var zRamp = LoadField(ZRamp, data, defaults);
facings = LoadField(d, Facings);
interpolatedFacings = LoadField(d, nameof(InterpolatedFacings), -1);
facings = LoadField(Facings, data, defaults);
interpolatedFacings = LoadField(InterpolatedFacings.Key, -1, data, defaults);
if (interpolatedFacings != -1 && (interpolatedFacings <= 1 || interpolatedFacings <= Math.Abs(facings) || interpolatedFacings > 1024
|| !Exts.IsPowerOf2(interpolatedFacings)))
throw new YamlException($"InterpolatedFacings must be greater than Facings, within the range of 2 to 1024, and a power of 2.");
@@ -323,18 +324,18 @@ namespace OpenRA.Mods.Common.Graphics
facings = -facings;
}
var offset = LoadField(d, Offset);
var blendMode = LoadField(d, BlendMode);
var offset = LoadField(Offset, data, defaults);
var blendMode = LoadField(BlendMode, data, defaults);
Func<int, IEnumerable<int>> getUsedFrames = frameCount =>
{
if (d.TryGetValue(Length.Key, out var lengthYaml) && lengthYaml.Value == "*")
if (LoadField(Length.Key, "", data, defaults) == "*")
length = frames?.Length ?? frameCount - start;
else
length = LoadField(d, Length);
length = LoadField(Length, data, defaults);
// Plays the animation forwards, and then in reverse
if (LoadField(d, Reverses))
if (LoadField(Reverses, data, defaults))
{
var frames = this.frames != null ? this.frames.Skip(start).Take(length).ToArray() : Exts.MakeArray(length, i => start + i);
this.frames = frames.Concat(frames.Skip(1).Take(length - 2).Reverse()).ToArray();
@@ -343,29 +344,29 @@ namespace OpenRA.Mods.Common.Graphics
}
// Overrides Length with a custom stride
stride = LoadField(d, Stride.Key, length);
stride = LoadField(Stride.Key, length, data, defaults);
if (length > stride)
throw new YamlException($"Sequence {sequence}.{animation}: Length must be <= stride");
throw new YamlException($"Sequence {image}.{sequence}: Length must be <= stride");
if (frames != null && length > frames.Length)
throw new YamlException($"Sequence {sequence}.{animation}: Length must be <= Frames.Length");
throw new YamlException($"Sequence {image}.{sequence}: Length must be <= Frames.Length");
var end = start + (facings - 1) * stride + length - 1;
if (frames != null)
{
foreach (var f in frames)
if (f < 0 || f >= frameCount)
throw new YamlException($"Sequence {sequence}.{animation} defines a Frames override that references frame {f}, but only [{start}..{end}] actually exist");
throw new YamlException($"Sequence {image}.{sequence} defines a Frames override that references frame {f}, but only [{start}..{end}] actually exist");
if (start < 0 || end >= frames.Length)
throw new YamlException($"Sequence {sequence}.{animation} uses indices [{start}..{end}] of the Frames list, but only {frames.Length} frames are defined");
throw new YamlException($"Sequence {image}.{sequence} uses indices [{start}..{end}] of the Frames list, but only {frames.Length} frames are defined");
}
else if (start < 0 || end >= frameCount)
throw new YamlException($"Sequence {sequence}.{animation} uses frames [{start}..{end}], but only [0..{frameCount - 1}] actually exist");
throw new YamlException($"Sequence {image}.{sequence} uses frames [{start}..{end}], but only [0..{frameCount - 1}] actually exist");
if (shadowStart >= 0 && shadowStart + (facings - 1) * stride + length > frameCount)
throw new YamlException($"Sequence {sequence}.{animation}'s shadow frames use frames [{shadowStart}..{shadowStart + (facings - 1) * stride + length - 1}], but only [0..{frameCount - 1}] actually exist");
throw new YamlException($"Sequence {image}.{sequence}'s shadow frames use frames [{shadowStart}..{shadowStart + (facings - 1) * stride + length - 1}], but only [0..{frameCount - 1}] actually exist");
var usedFrames = new List<int>();
for (var facing = 0; facing < facings; facing++)
@@ -385,34 +386,36 @@ namespace OpenRA.Mods.Common.Graphics
return usedFrames;
};
if (d.TryGetValue(Combine.Key, out var combine))
var combineNode = data.Nodes.FirstOrDefault(n => n.Key == Combine.Key);
if (combineNode != null)
{
var combined = Enumerable.Empty<Sprite>();
foreach (var sub in combine.Nodes)
foreach (var combineSequenceNode in combineNode.Value.Nodes)
{
var sd = sub.Value.ToDictionary();
var combineData = combineSequenceNode.Value;
// Allow per-sprite offset, flipping, start, and length
// These shouldn't inherit Start/Offset/etc from the main definition
var subStart = LoadField(sd, Start);
var subOffset = LoadField(sd, Offset);
var subFlipX = LoadField(sd, FlipX);
var subFlipY = LoadField(sd, FlipY);
var subFrames = LoadField(sd, Frames);
var subStart = LoadField(Start, combineData, NoData);
var subOffset = LoadField(Offset, combineData, NoData);
var subFlipX = LoadField(FlipX, combineData, NoData);
var subFlipY = LoadField(FlipY, combineData, NoData);
var subFrames = LoadField(Frames, combineData, NoData);
var subLength = 0;
Func<int, IEnumerable<int>> subGetUsedFrames = subFrameCount =>
{
if (sd.TryGetValue(Length.Key, out var subLengthYaml) && subLengthYaml.Value == "*")
subLength = subFrames != null ? subFrames.Length : subFrameCount - subStart;
var combineLengthNode = combineData.Nodes.FirstOrDefault(n => n.Key == Length.Key);
if (combineLengthNode?.Value.Value == "*")
subLength = subFrames?.Length ?? subFrameCount - subStart;
else
subLength = LoadField(sd, Length);
subLength = LoadField(Length, combineData, NoData);
return subFrames != null ? subFrames.Skip(subStart).Take(subLength) : Enumerable.Range(subStart, subLength);
};
var subSrc = GetSpriteSrc(modData, tileSet, sequence, animation, sub.Key, sd);
var subSprites = cache[subSrc, subGetUsedFrames].Select(s =>
var subFilename = GetSpriteFilename(modData, tileSet, combineSequenceNode.Key, sequence, combineData, NoData);
var subSprites = cache[subFilename, subGetUsedFrames].Select(s =>
{
if (s == null)
return null;
@@ -436,8 +439,8 @@ namespace OpenRA.Mods.Common.Graphics
{
// Apply offset to each sprite in the sequence
// Different sequences may apply different offsets to the same frame
var src = GetSpriteSrc(modData, tileSet, sequence, animation, info.Value, d);
sprites = cache[src, getUsedFrames].Select(s =>
var filename = GetSpriteFilename(modData, tileSet, image, sequence, data, defaults);
sprites = cache[filename, getUsedFrames].Select(s =>
{
if (s == null)
return null;
@@ -451,28 +454,28 @@ namespace OpenRA.Mods.Common.Graphics
}).ToArray();
}
alpha = LoadField(d, Alpha);
alpha = LoadField(Alpha, data, defaults);
if (alpha != null)
{
if (alpha.Length == 1)
alpha = Exts.MakeArray(length, _ => alpha[0]);
else if (alpha.Length != length)
throw new YamlException($"Sequence {sequence}.{animation} must define either 1 or {length} Alpha values.");
throw new YamlException($"Sequence {image}.{sequence} must define either 1 or {length} Alpha values.");
}
if (LoadField(d, AlphaFade))
if (LoadField(AlphaFade, data, defaults))
{
if (alpha != null)
throw new YamlException($"Sequence {sequence}.{animation} cannot define both AlphaFade and Alpha.");
throw new YamlException($"Sequence {image}.{sequence} cannot define both AlphaFade and Alpha.");
alpha = Exts.MakeArray(length, i => float2.Lerp(1f, 0f, i / (length - 1f)));
}
var depthSprite = LoadField(d, DepthSprite);
var depthSprite = LoadField(DepthSprite, data, defaults);
if (!string.IsNullOrEmpty(depthSprite))
{
var depthSpriteFrame = LoadField(d, DepthSpriteFrame);
var depthOffset = LoadField(d, DepthSpriteOffset);
var depthSpriteFrame = LoadField(DepthSpriteFrame, data, defaults);
var depthOffset = LoadField(DepthSpriteOffset, data, defaults);
IEnumerable<int> GetDepthFrame(int _) => new[] { depthSpriteFrame };
var ds = cache[depthSprite, GetDepthFrame][depthSpriteFrame];
@@ -491,15 +494,14 @@ namespace OpenRA.Mods.Common.Graphics
}).ToArray();
}
if (LoadField(d, HasEmbeddedPalette))
if (LoadField(HasEmbeddedPalette, data, defaults))
{
var src = GetSpriteSrc(modData, tileSet, sequence, animation, info.Value, d);
var metadata = cache.FrameMetadata(src);
var filename = GetSpriteFilename(modData, tileSet, image, sequence, data, defaults);
var metadata = cache.FrameMetadata(filename);
var i = frames != null ? frames[0] : start;
var palettes = metadata?.GetOrDefault<EmbeddedSpritePalette>();
if (palettes == null || !palettes.TryGetPaletteForFrame(i, out EmbeddedPalette))
throw new YamlException($"Cannot export palette from {src}: frame {i} does not define an embedded palette");
throw new YamlException($"Cannot export palette from {filename}: frame {i} does not define an embedded palette");
}
var boundSprites = SpriteBounds(sprites, frames, start, facings, length, stride, transpose);
@@ -508,11 +510,6 @@ namespace OpenRA.Mods.Common.Graphics
Bounds = boundSprites.Union();
}
catch (FormatException f)
{
throw new FormatException($"Failed to parse sequences for {sequence}.{animation} at {info.Nodes[0].Location}:\n{f}");
}
}
/// <summary>Returns the bounds of all of the sprites that can appear in this animation</summary>
static IEnumerable<Rectangle> SpriteBounds(Sprite[] sprites, int[] frames, int start, int facings, int length, int stride, bool transpose)
@@ -570,7 +567,7 @@ namespace OpenRA.Mods.Common.Graphics
var j = frames != null ? frames[i] : start + i;
if (sprites[j] == null)
throw new InvalidOperationException($"Attempted to query unloaded sprite from {Name}.{sequence} start={start} frame={frame} facing={facing}");
throw new InvalidOperationException($"Attempted to query unloaded sprite from {image}.{Name} start={start} frame={frame} facing={facing}");
return sprites[j];
}

View File

@@ -35,9 +35,9 @@ namespace OpenRA.Mods.Common.Graphics
TilesetCodes = yaml.ToDictionary(kv => kv.Value);
}
public override ISpriteSequence CreateSequence(ModData modData, string tileSet, SpriteCache cache, string sequence, string animation, MiniYaml info)
public override ISpriteSequence CreateSequence(ModData modData, string tileSet, SpriteCache cache, string image, string sequence, MiniYaml data, MiniYaml defaults)
{
return new TilesetSpecificSpriteSequence(modData, tileSet, cache, this, sequence, animation, info);
return new TilesetSpecificSpriteSequence(modData, tileSet, cache, this, image, sequence, data, defaults);
}
}
@@ -59,42 +59,35 @@ namespace OpenRA.Mods.Common.Graphics
[Desc("Whether `mod.yaml`'s `TilesetExtensions` should be used with the sequence's file name.")]
static readonly SpriteSequenceField<bool> UseTilesetExtension = new SpriteSequenceField<bool>(nameof(UseTilesetExtension), false);
public TilesetSpecificSpriteSequence(ModData modData, string tileSet, SpriteCache cache, ISpriteSequenceLoader loader, string sequence, string animation, MiniYaml info)
: base(modData, tileSet, cache, loader, sequence, animation, info) { }
public TilesetSpecificSpriteSequence(ModData modData, string tileset, SpriteCache cache, ISpriteSequenceLoader loader, string image, string sequence, MiniYaml data, MiniYaml defaults)
: base(modData, tileset, cache, loader, image, sequence, data, defaults) { }
static string ResolveTilesetId(string tileSet, Dictionary<string, MiniYaml> d)
static string ResolveTilesetId(string tileset, MiniYaml data, MiniYaml defaults)
{
if (d.TryGetValue(nameof(TilesetOverrides), out var yaml))
{
var tsNode = yaml.Nodes.FirstOrDefault(n => n.Key == tileSet);
if (tsNode != null)
tileSet = tsNode.Value.Value;
var node = data.Nodes.FirstOrDefault(n => n.Key == TilesetOverrides.Key) ?? defaults.Nodes.FirstOrDefault(n => n.Key == TilesetOverrides.Key);
var overrideNode = node?.Value.Nodes.FirstOrDefault(n => n.Key == tileset);
return overrideNode?.Value.Value ?? tileset;
}
return tileSet;
}
protected override string GetSpriteSrc(ModData modData, string tileSet, string sequence, string animation, string sprite, Dictionary<string, MiniYaml> d)
protected override string GetSpriteFilename(ModData modData, string tileset, string image, string sequence, MiniYaml data, MiniYaml defaults)
{
var loader = (TilesetSpecificSpriteSequenceLoader)Loader;
var filename = data.Value ?? defaults.Value ?? image;
var spriteName = sprite ?? sequence;
if (LoadField(UseTilesetCode, data, defaults))
if (loader.TilesetCodes.TryGetValue(ResolveTilesetId(tileset, data, defaults), out var tilesetCode))
filename = filename.Substring(0, 1) + tilesetCode + filename.Substring(2, filename.Length - 2);
if (LoadField(d, UseTilesetCode))
if (LoadField(AddExtension, data, defaults))
{
if (loader.TilesetCodes.TryGetValue(ResolveTilesetId(tileSet, d), out var code))
spriteName = spriteName.Substring(0, 1) + code + spriteName.Substring(2, spriteName.Length - 2);
if (LoadField(UseTilesetExtension, data, defaults))
if (loader.TilesetExtensions.TryGetValue(ResolveTilesetId(tileset, data, defaults), out var tilesetExtension))
return filename + tilesetExtension;
return filename + loader.DefaultSpriteExtension;
}
if (LoadField(d, AddExtension))
{
if (LoadField(d, UseTilesetExtension) && loader.TilesetExtensions.TryGetValue(ResolveTilesetId(tileSet, d), out var tilesetExtension))
return spriteName + tilesetExtension;
return spriteName + loader.DefaultSpriteExtension;
}
return spriteName;
return filename;
}
}
}