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:
@@ -20,9 +20,9 @@ namespace OpenRA.Mods.Cnc.Graphics
|
|||||||
public ClassicSpriteSequenceLoader(ModData modData)
|
public ClassicSpriteSequenceLoader(ModData modData)
|
||||||
: base(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);
|
static readonly SpriteSequenceField<bool> UseClassicFacings = new SpriteSequenceField<bool>(nameof(UseClassicFacings), false);
|
||||||
readonly bool useClassicFacings;
|
readonly bool useClassicFacings;
|
||||||
|
|
||||||
public ClassicSpriteSequence(ModData modData, string tileSet, SpriteCache cache, ISpriteSequenceLoader loader, string sequence, string animation, MiniYaml info)
|
public ClassicSpriteSequence(ModData modData, string tileSet, SpriteCache cache, ISpriteSequenceLoader loader, string image, string sequence, MiniYaml data, MiniYaml defaults)
|
||||||
: base(modData, tileSet, cache, loader, sequence, animation, info)
|
: base(modData, tileSet, cache, loader, image, sequence, data, defaults)
|
||||||
{
|
{
|
||||||
var d = info.ToDictionary();
|
useClassicFacings = LoadField(UseClassicFacings, data, defaults);
|
||||||
useClassicFacings = LoadField(d, UseClassicFacings);
|
|
||||||
|
|
||||||
if (useClassicFacings && facings != 32)
|
if (useClassicFacings && facings != 32)
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException($"Sequence {image}.{sequence}: UseClassicFacings is only valid for 32 facings");
|
||||||
$"{info.Nodes[0].Location}: Sequence {sequence}.{animation}: UseClassicFacings is only valid for 32 facings");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override int GetFacingFrameOffset(WAngle facing)
|
protected override int GetFacingFrameOffset(WAngle facing)
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ namespace OpenRA.Mods.Cnc.Graphics
|
|||||||
TilesetCodes = yaml.ToDictionary(kv => kv.Value);
|
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.")]
|
[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);
|
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)
|
public ClassicTilesetSpecificSpriteSequence(ModData modData, string tileset, SpriteCache cache, ISpriteSequenceLoader loader, string image, string sequence, MiniYaml data, MiniYaml defaults)
|
||||||
: base(modData, tileSet, cache, loader, sequence, animation, info) { }
|
: 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 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);
|
||||||
var tsNode = yaml.Nodes.FirstOrDefault(n => n.Key == tileSet);
|
return overrideNode?.Value.Value ?? tileset;
|
||||||
if (tsNode != null)
|
|
||||||
tileSet = tsNode.Value.Value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return tileSet;
|
protected override string GetSpriteFilename(ModData modData, string tileset, string image, string sequence, MiniYaml data, MiniYaml defaults)
|
||||||
}
|
|
||||||
|
|
||||||
protected override string GetSpriteSrc(ModData modData, string tileSet, string sequence, string animation, string sprite, Dictionary<string, MiniYaml> d)
|
|
||||||
{
|
{
|
||||||
var loader = (ClassicTilesetSpecificSpriteSequenceLoader)Loader;
|
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(AddExtension, data, defaults))
|
||||||
|
|
||||||
if (LoadField(d, UseTilesetCode))
|
|
||||||
{
|
{
|
||||||
if (loader.TilesetCodes.TryGetValue(ResolveTilesetId(tileSet, d), out var code))
|
if (LoadField(UseTilesetExtension, data, defaults))
|
||||||
spriteName = spriteName.Substring(0, 1) + code + spriteName.Substring(2, spriteName.Length - 2);
|
if (loader.TilesetExtensions.TryGetValue(ResolveTilesetId(tileset, data, defaults), out var tilesetExtension))
|
||||||
|
return filename + tilesetExtension;
|
||||||
|
|
||||||
|
return filename + loader.DefaultSpriteExtension;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LoadField(d, AddExtension))
|
return filename;
|
||||||
{
|
|
||||||
if (LoadField(d, UseTilesetExtension) && loader.TilesetExtensions.TryGetValue(ResolveTilesetId(tileSet, d), out var tilesetExtension))
|
|
||||||
return spriteName + tilesetExtension;
|
|
||||||
|
|
||||||
return spriteName + loader.DefaultSpriteExtension;
|
|
||||||
}
|
|
||||||
|
|
||||||
return spriteName;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,49 +41,49 @@ namespace OpenRA.Mods.Common.Graphics
|
|||||||
|
|
||||||
public class DefaultSpriteSequenceLoader : ISpriteSequenceLoader
|
public class DefaultSpriteSequenceLoader : ISpriteSequenceLoader
|
||||||
{
|
{
|
||||||
|
static readonly MiniYaml NoData = new MiniYaml(null);
|
||||||
|
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "IDE0060:Remove unused parameter", Justification = "Load game API")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "IDE0060:Remove unused parameter", Justification = "Load game API")]
|
||||||
public DefaultSpriteSequenceLoader(ModData modData) { }
|
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 sequences = new Dictionary<string, ISpriteSequence>();
|
||||||
var nodes = node.Value.ToDictionary();
|
MiniYaml defaults;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (nodes.TryGetValue("Defaults", out var defaults))
|
var node = imageNode.Value.Nodes.SingleOrDefault(n => n.Key == "Defaults");
|
||||||
{
|
defaults = node?.Value ?? NoData;
|
||||||
nodes.Remove("Defaults");
|
imageNode.Value.Nodes.Remove(node);
|
||||||
foreach (var n in nodes)
|
|
||||||
{
|
|
||||||
n.Value.Nodes = MiniYaml.Merge(new[] { defaults.Nodes, n.Value.Nodes });
|
|
||||||
n.Value.Value = n.Value.Value ?? defaults.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
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
|
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)
|
catch (FileNotFoundException ex)
|
||||||
{
|
{
|
||||||
// Defer exception until something tries to access the sequence
|
// 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
|
// 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.")]
|
[Desc("Generic sprite sequence implementation, mostly unencumbered with game- or artwork-specific logic.")]
|
||||||
public class DefaultSpriteSequence : ISpriteSequence
|
public class DefaultSpriteSequence : ISpriteSequence
|
||||||
{
|
{
|
||||||
|
static readonly MiniYaml NoData = new MiniYaml(null);
|
||||||
|
|
||||||
|
readonly string image;
|
||||||
protected Sprite[] sprites;
|
protected Sprite[] sprites;
|
||||||
readonly bool reverseFacings, transpose;
|
readonly bool reverseFacings, transpose;
|
||||||
readonly string sequence;
|
|
||||||
|
|
||||||
protected readonly ISpriteSequenceLoader Loader;
|
protected readonly ISpriteSequenceLoader Loader;
|
||||||
|
|
||||||
@@ -257,25 +259,27 @@ namespace OpenRA.Mods.Common.Graphics
|
|||||||
|
|
||||||
public readonly uint[] EmbeddedPalette;
|
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))
|
var node = data.Nodes.FirstOrDefault(n => n.Key == key) ?? defaults.Nodes.FirstOrDefault(n => n.Key == key);
|
||||||
return FieldLoader.GetValue<T>(key, value.Value);
|
if (node == null)
|
||||||
|
|
||||||
return fallback;
|
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))
|
var node = data.Nodes.FirstOrDefault(n => n.Key == field.Key) ?? defaults.Nodes.FirstOrDefault(n => n.Key == field.Key);
|
||||||
return FieldLoader.GetValue<T>(field.Key, value.Value);
|
if (node == null)
|
||||||
|
|
||||||
return field.DefaultValue;
|
return field.DefaultValue;
|
||||||
|
|
||||||
|
return FieldLoader.GetValue<T>(field.Key, node.Value.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static Rectangle FlipRectangle(Rectangle rect, bool flipX, bool flipY)
|
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);
|
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;
|
this.image = image;
|
||||||
Name = animation;
|
Name = sequence;
|
||||||
Loader = loader;
|
Loader = loader;
|
||||||
var d = info.ToDictionary();
|
|
||||||
|
|
||||||
try
|
start = LoadField(Start, data, defaults);
|
||||||
{
|
shadowStart = LoadField(ShadowStart, data, defaults);
|
||||||
start = LoadField(d, Start);
|
shadowZOffset = LoadField(ShadowZOffset, data, defaults).Length;
|
||||||
shadowStart = LoadField(d, ShadowStart);
|
zOffset = LoadField(ZOffset, data, defaults).Length;
|
||||||
shadowZOffset = LoadField(d, ShadowZOffset).Length;
|
tick = LoadField(Tick, data, defaults);
|
||||||
zOffset = LoadField(d, ZOffset).Length;
|
transpose = LoadField(Transpose, data, defaults);
|
||||||
tick = LoadField(d, Tick);
|
frames = LoadField(Frames, data, defaults);
|
||||||
transpose = LoadField(d, Transpose);
|
ignoreWorldTint = LoadField(IgnoreWorldTint, data, defaults);
|
||||||
frames = LoadField(d, Frames);
|
scale = LoadField(Scale, data, defaults);
|
||||||
ignoreWorldTint = LoadField(d, IgnoreWorldTint);
|
|
||||||
scale = LoadField(d, Scale);
|
|
||||||
|
|
||||||
var flipX = LoadField(d, FlipX);
|
var flipX = LoadField(FlipX, data, defaults);
|
||||||
var flipY = LoadField(d, FlipY);
|
var flipY = LoadField(FlipY, data, defaults);
|
||||||
var zRamp = LoadField(d, ZRamp);
|
var zRamp = LoadField(ZRamp, data, defaults);
|
||||||
|
|
||||||
facings = LoadField(d, Facings);
|
facings = LoadField(Facings, data, defaults);
|
||||||
interpolatedFacings = LoadField(d, nameof(InterpolatedFacings), -1);
|
interpolatedFacings = LoadField(InterpolatedFacings.Key, -1, data, defaults);
|
||||||
if (interpolatedFacings != -1 && (interpolatedFacings <= 1 || interpolatedFacings <= Math.Abs(facings) || interpolatedFacings > 1024
|
if (interpolatedFacings != -1 && (interpolatedFacings <= 1 || interpolatedFacings <= Math.Abs(facings) || interpolatedFacings > 1024
|
||||||
|| !Exts.IsPowerOf2(interpolatedFacings)))
|
|| !Exts.IsPowerOf2(interpolatedFacings)))
|
||||||
throw new YamlException($"InterpolatedFacings must be greater than Facings, within the range of 2 to 1024, and a power of 2.");
|
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;
|
facings = -facings;
|
||||||
}
|
}
|
||||||
|
|
||||||
var offset = LoadField(d, Offset);
|
var offset = LoadField(Offset, data, defaults);
|
||||||
var blendMode = LoadField(d, BlendMode);
|
var blendMode = LoadField(BlendMode, data, defaults);
|
||||||
|
|
||||||
Func<int, IEnumerable<int>> getUsedFrames = frameCount =>
|
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;
|
length = frames?.Length ?? frameCount - start;
|
||||||
else
|
else
|
||||||
length = LoadField(d, Length);
|
length = LoadField(Length, data, defaults);
|
||||||
|
|
||||||
// Plays the animation forwards, and then in reverse
|
// 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);
|
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();
|
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
|
// Overrides Length with a custom stride
|
||||||
stride = LoadField(d, Stride.Key, length);
|
stride = LoadField(Stride.Key, length, data, defaults);
|
||||||
|
|
||||||
if (length > stride)
|
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)
|
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;
|
var end = start + (facings - 1) * stride + length - 1;
|
||||||
if (frames != null)
|
if (frames != null)
|
||||||
{
|
{
|
||||||
foreach (var f in frames)
|
foreach (var f in frames)
|
||||||
if (f < 0 || f >= frameCount)
|
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)
|
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)
|
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)
|
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>();
|
var usedFrames = new List<int>();
|
||||||
for (var facing = 0; facing < facings; facing++)
|
for (var facing = 0; facing < facings; facing++)
|
||||||
@@ -385,34 +386,36 @@ namespace OpenRA.Mods.Common.Graphics
|
|||||||
return usedFrames;
|
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>();
|
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
|
// Allow per-sprite offset, flipping, start, and length
|
||||||
// These shouldn't inherit Start/Offset/etc from the main definition
|
// These shouldn't inherit Start/Offset/etc from the main definition
|
||||||
var subStart = LoadField(sd, Start);
|
var subStart = LoadField(Start, combineData, NoData);
|
||||||
var subOffset = LoadField(sd, Offset);
|
var subOffset = LoadField(Offset, combineData, NoData);
|
||||||
var subFlipX = LoadField(sd, FlipX);
|
var subFlipX = LoadField(FlipX, combineData, NoData);
|
||||||
var subFlipY = LoadField(sd, FlipY);
|
var subFlipY = LoadField(FlipY, combineData, NoData);
|
||||||
var subFrames = LoadField(sd, Frames);
|
var subFrames = LoadField(Frames, combineData, NoData);
|
||||||
var subLength = 0;
|
var subLength = 0;
|
||||||
|
|
||||||
Func<int, IEnumerable<int>> subGetUsedFrames = subFrameCount =>
|
Func<int, IEnumerable<int>> subGetUsedFrames = subFrameCount =>
|
||||||
{
|
{
|
||||||
if (sd.TryGetValue(Length.Key, out var subLengthYaml) && subLengthYaml.Value == "*")
|
var combineLengthNode = combineData.Nodes.FirstOrDefault(n => n.Key == Length.Key);
|
||||||
subLength = subFrames != null ? subFrames.Length : subFrameCount - subStart;
|
if (combineLengthNode?.Value.Value == "*")
|
||||||
|
subLength = subFrames?.Length ?? subFrameCount - subStart;
|
||||||
else
|
else
|
||||||
subLength = LoadField(sd, Length);
|
subLength = LoadField(Length, combineData, NoData);
|
||||||
|
|
||||||
return subFrames != null ? subFrames.Skip(subStart).Take(subLength) : Enumerable.Range(subStart, subLength);
|
return subFrames != null ? subFrames.Skip(subStart).Take(subLength) : Enumerable.Range(subStart, subLength);
|
||||||
};
|
};
|
||||||
|
|
||||||
var subSrc = GetSpriteSrc(modData, tileSet, sequence, animation, sub.Key, sd);
|
var subFilename = GetSpriteFilename(modData, tileSet, combineSequenceNode.Key, sequence, combineData, NoData);
|
||||||
var subSprites = cache[subSrc, subGetUsedFrames].Select(s =>
|
var subSprites = cache[subFilename, subGetUsedFrames].Select(s =>
|
||||||
{
|
{
|
||||||
if (s == null)
|
if (s == null)
|
||||||
return null;
|
return null;
|
||||||
@@ -436,8 +439,8 @@ namespace OpenRA.Mods.Common.Graphics
|
|||||||
{
|
{
|
||||||
// Apply offset to each sprite in the sequence
|
// Apply offset to each sprite in the sequence
|
||||||
// Different sequences may apply different offsets to the same frame
|
// Different sequences may apply different offsets to the same frame
|
||||||
var src = GetSpriteSrc(modData, tileSet, sequence, animation, info.Value, d);
|
var filename = GetSpriteFilename(modData, tileSet, image, sequence, data, defaults);
|
||||||
sprites = cache[src, getUsedFrames].Select(s =>
|
sprites = cache[filename, getUsedFrames].Select(s =>
|
||||||
{
|
{
|
||||||
if (s == null)
|
if (s == null)
|
||||||
return null;
|
return null;
|
||||||
@@ -451,28 +454,28 @@ namespace OpenRA.Mods.Common.Graphics
|
|||||||
}).ToArray();
|
}).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
alpha = LoadField(d, Alpha);
|
alpha = LoadField(Alpha, data, defaults);
|
||||||
if (alpha != null)
|
if (alpha != null)
|
||||||
{
|
{
|
||||||
if (alpha.Length == 1)
|
if (alpha.Length == 1)
|
||||||
alpha = Exts.MakeArray(length, _ => alpha[0]);
|
alpha = Exts.MakeArray(length, _ => alpha[0]);
|
||||||
else if (alpha.Length != length)
|
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)
|
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)));
|
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))
|
if (!string.IsNullOrEmpty(depthSprite))
|
||||||
{
|
{
|
||||||
var depthSpriteFrame = LoadField(d, DepthSpriteFrame);
|
var depthSpriteFrame = LoadField(DepthSpriteFrame, data, defaults);
|
||||||
var depthOffset = LoadField(d, DepthSpriteOffset);
|
var depthOffset = LoadField(DepthSpriteOffset, data, defaults);
|
||||||
IEnumerable<int> GetDepthFrame(int _) => new[] { depthSpriteFrame };
|
IEnumerable<int> GetDepthFrame(int _) => new[] { depthSpriteFrame };
|
||||||
var ds = cache[depthSprite, GetDepthFrame][depthSpriteFrame];
|
var ds = cache[depthSprite, GetDepthFrame][depthSpriteFrame];
|
||||||
|
|
||||||
@@ -491,15 +494,14 @@ namespace OpenRA.Mods.Common.Graphics
|
|||||||
}).ToArray();
|
}).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LoadField(d, HasEmbeddedPalette))
|
if (LoadField(HasEmbeddedPalette, data, defaults))
|
||||||
{
|
{
|
||||||
var src = GetSpriteSrc(modData, tileSet, sequence, animation, info.Value, d);
|
var filename = GetSpriteFilename(modData, tileSet, image, sequence, data, defaults);
|
||||||
|
var metadata = cache.FrameMetadata(filename);
|
||||||
var metadata = cache.FrameMetadata(src);
|
|
||||||
var i = frames != null ? frames[0] : start;
|
var i = frames != null ? frames[0] : start;
|
||||||
var palettes = metadata?.GetOrDefault<EmbeddedSpritePalette>();
|
var palettes = metadata?.GetOrDefault<EmbeddedSpritePalette>();
|
||||||
if (palettes == null || !palettes.TryGetPaletteForFrame(i, out EmbeddedPalette))
|
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);
|
var boundSprites = SpriteBounds(sprites, frames, start, facings, length, stride, transpose);
|
||||||
@@ -508,11 +510,6 @@ namespace OpenRA.Mods.Common.Graphics
|
|||||||
|
|
||||||
Bounds = boundSprites.Union();
|
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>
|
/// <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)
|
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;
|
var j = frames != null ? frames[i] : start + i;
|
||||||
if (sprites[j] == null)
|
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];
|
return sprites[j];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ namespace OpenRA.Mods.Common.Graphics
|
|||||||
TilesetCodes = yaml.ToDictionary(kv => kv.Value);
|
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.")]
|
[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);
|
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)
|
public TilesetSpecificSpriteSequence(ModData modData, string tileset, SpriteCache cache, ISpriteSequenceLoader loader, string image, string sequence, MiniYaml data, MiniYaml defaults)
|
||||||
: base(modData, tileSet, cache, loader, sequence, animation, info) { }
|
: 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 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);
|
||||||
var tsNode = yaml.Nodes.FirstOrDefault(n => n.Key == tileSet);
|
return overrideNode?.Value.Value ?? tileset;
|
||||||
if (tsNode != null)
|
|
||||||
tileSet = tsNode.Value.Value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return tileSet;
|
protected override string GetSpriteFilename(ModData modData, string tileset, string image, string sequence, MiniYaml data, MiniYaml defaults)
|
||||||
}
|
|
||||||
|
|
||||||
protected override string GetSpriteSrc(ModData modData, string tileSet, string sequence, string animation, string sprite, Dictionary<string, MiniYaml> d)
|
|
||||||
{
|
{
|
||||||
var loader = (TilesetSpecificSpriteSequenceLoader)Loader;
|
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))
|
if (LoadField(UseTilesetExtension, data, defaults))
|
||||||
spriteName = spriteName.Substring(0, 1) + code + spriteName.Substring(2, spriteName.Length - 2);
|
if (loader.TilesetExtensions.TryGetValue(ResolveTilesetId(tileset, data, defaults), out var tilesetExtension))
|
||||||
|
return filename + tilesetExtension;
|
||||||
|
|
||||||
|
return filename + loader.DefaultSpriteExtension;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LoadField(d, AddExtension))
|
return filename;
|
||||||
{
|
|
||||||
if (LoadField(d, UseTilesetExtension) && loader.TilesetExtensions.TryGetValue(ResolveTilesetId(tileSet, d), out var tilesetExtension))
|
|
||||||
return spriteName + tilesetExtension;
|
|
||||||
|
|
||||||
return spriteName + loader.DefaultSpriteExtension;
|
|
||||||
}
|
|
||||||
|
|
||||||
return spriteName;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user