Unhardcoded SpriteSequence properties

To prepare them for documentation generation.
Also added descriptions to SpriteSequence implementations and their properties.
Also made a few code style fixes.
This commit is contained in:
penev92
2022-03-11 16:11:18 +02:00
committed by Paul Chote
parent d1f7fb8fb8
commit c3c5dbfa35
5 changed files with 195 additions and 90 deletions

View File

@@ -26,24 +26,28 @@ namespace OpenRA.Mods.Cnc.Graphics
}
}
[Desc("A sprite sequence that has the oddities that come with first-generation Westwood titles.")]
public class ClassicSpriteSequence : DefaultSpriteSequence
{
readonly bool useClassicFacings;
// This needs to be a public property for the documentation generation to work.
[Desc("Incorporate a compensation factor due to the distortion caused by 3D-Studio " +
"when it tried to render 45% angles which was used by Westwood Studios at that time.")]
public bool UseClassicFacings { get; }
public ClassicSpriteSequence(ModData modData, string tileSet, SpriteCache cache, ISpriteSequenceLoader loader, string sequence, string animation, MiniYaml info)
: base(modData, tileSet, cache, loader, sequence, animation, info)
{
var d = info.ToDictionary();
useClassicFacings = LoadField(d, "UseClassicFacings", false);
UseClassicFacings = LoadField(d, nameof(UseClassicFacings), UseClassicFacings);
if (useClassicFacings && Facings != 32)
if (UseClassicFacings && Facings != 32)
throw new InvalidOperationException(
$"{info.Nodes[0].Location}: Sequence {sequence}.{animation}: UseClassicFacings is only valid for 32 facings");
}
protected override int GetFacingFrameOffset(WAngle facing)
{
return useClassicFacings ? Util.ClassicIndexFacing(facing, Facings) : Common.Util.IndexFacing(facing, Facings);
return UseClassicFacings ? Util.ClassicIndexFacing(facing, Facings) : Common.Util.IndexFacing(facing, Facings);
}
}
}

View File

@@ -41,14 +41,32 @@ namespace OpenRA.Mods.Cnc.Graphics
}
}
[Desc("A sprite sequence that can have tileset-specific variants and has the oddities " +
"that come with first-generation Westwood titles.")]
public class ClassicTilesetSpecificSpriteSequence : ClassicSpriteSequence
{
// These need to be public properties for the documentation generation to work.
[Desc("Dictionary of <string: string> with tileset name to override -> tileset name to use instead.")]
public static Dictionary<string, string> TilesetOverrides => null;
[Desc("Use `TilesetCodes` as defined in `mod.yaml` to add a letter as a second character " +
"into the sprite filename like the Westwood 2.5D titles did for tileset-specific variants.")]
public static bool UseTilesetCode => false;
[Desc("Append a tileset-specific extension to the file name " +
"- either as defined in `mod.yaml`'s `TilesetExtensions` (if `UseTilesetExtension` is used) " +
"or the default hardcoded one for this sequence type (.shp).")]
public static bool AddExtension => true;
[Desc("Whether `mod.yaml`'s `TilesetExtensions` should be used with the sequence's file name.")]
public static bool UseTilesetExtension { get; private set; }
public ClassicTilesetSpecificSpriteSequence(ModData modData, string tileSet, SpriteCache cache, ISpriteSequenceLoader loader, string sequence, string animation, MiniYaml info)
: base(modData, tileSet, cache, loader, sequence, animation, info) { }
string ResolveTilesetId(string tileSet, Dictionary<string, MiniYaml> d)
static string ResolveTilesetId(string tileSet, Dictionary<string, MiniYaml> d)
{
if (d.TryGetValue("TilesetOverrides", out var yaml))
if (d.TryGetValue(nameof(TilesetOverrides), out var yaml))
{
var tsNode = yaml.Nodes.FirstOrDefault(n => n.Key == tileSet);
if (tsNode != null)
@@ -64,17 +82,16 @@ namespace OpenRA.Mods.Cnc.Graphics
var spriteName = sprite ?? sequence;
if (LoadField(d, "UseTilesetCode", false))
if (LoadField(d, nameof(UseTilesetCode), UseTilesetCode))
{
if (loader.TilesetCodes.TryGetValue(ResolveTilesetId(tileSet, d), out var code))
spriteName = spriteName.Substring(0, 1) + code + spriteName.Substring(2, spriteName.Length - 2);
}
if (LoadField(d, "AddExtension", true))
if (LoadField(d, nameof(AddExtension), AddExtension))
{
var useTilesetExtension = LoadField(d, "UseTilesetExtension", false);
if (useTilesetExtension && loader.TilesetExtensions.TryGetValue(ResolveTilesetId(tileSet, d), out var tilesetExtension))
UseTilesetExtension = LoadField(d, nameof(UseTilesetExtension), UseTilesetExtension);
if (UseTilesetExtension && loader.TilesetExtensions.TryGetValue(ResolveTilesetId(tileSet, d), out var tilesetExtension))
return spriteName + tilesetExtension;
return spriteName + loader.DefaultSpriteExtension;

View File

@@ -122,32 +122,100 @@ namespace OpenRA.Mods.Common.Graphics
float ISpriteSequence.GetAlpha(int frame) { throw exception; }
}
[Desc("Generic sprite sequence implementation, mostly unencumbered with game- or artwork-specific logic.")]
public class DefaultSpriteSequence : ISpriteSequence
{
static readonly WDist DefaultShadowSpriteZOffset = new WDist(-5);
protected Sprite[] sprites;
readonly bool reverseFacings, transpose;
readonly string sequence;
readonly float[] alpha;
protected readonly ISpriteSequenceLoader Loader;
public string Name { get; }
public int Start { get; private set; }
public int Length { get; private set; }
public int Stride { get; private set; }
public int Facings { get; }
public int Tick { get; }
public int ZOffset { get; }
public float ZRamp { get; }
public int ShadowStart { get; }
public int ShadowZOffset { get; }
public int[] Frames { get; private set; }
public Rectangle Bounds { get; private set; }
public bool IgnoreWorldTint { get; }
public float Scale { get; }
public readonly uint[] EmbeddedPalette;
[Desc("Frame index to start from.")]
public int Start { get; private set; }
[Desc("Number of frames to use. Does not have to be the total amount the sprite sheet has.")]
public int Length { get; private set; } = 1;
[Desc("Multiplier for the number of facings.")]
public int Stride { get; private set; }
[Desc("The amount of directions the unit faces. Use negative values to rotate counter-clockwise.")]
public int Facings { get; } = 1;
[Desc("Time (in milliseconds) to wait until playing the next frame in the animation.")]
public int Tick { get; } = 40;
[Desc("Value controlling the Z-order. A higher values means rendering on top of other sprites at the same position. " +
"Use power of 2 values to avoid glitches.")]
public int ZOffset { get; }
[Desc("")]
public float ZRamp { get; }
[Desc("If the shadow is not part of the sprite, but baked into the same sprite sheet at a fixed offset, " +
"set this to the frame index where it starts.")]
public int ShadowStart { get; } = -1;
[Desc("Set Z-Offset for the separate shadow. Used by the later Westwood 2.5D titles. Defined in WDist units!")]
public int ShadowZOffset { get; }
[Desc("The individual frames to play instead of going through them sequentially from the `Start`.")]
public int[] Frames { get; private set; }
public Rectangle Bounds { get; }
[Desc("Don't apply terrain lighting or colored overlays.")]
public bool IgnoreWorldTint { get; }
[Desc("")]
public float Scale { get; } = 1f;
// These need to be public properties for the documentation generation to work.
[Desc("Play the sprite sequence back and forth.")]
public static bool Reverses => false;
[Desc("Support a frame order where each animation step is split per each direction.")]
public static bool Transpose => false;
[Desc("Mirror on the X axis.")]
public bool FlipX { get; }
[Desc("Mirror on the Y axis.")]
public bool FlipY { get; }
[Desc("Change the position in-game on X, Y, Z.")]
public float3 Offset { get; } = float3.Zero;
[Desc("Apply an OpenGL/Photoshop inspired blend mode.")]
public BlendMode BlendMode { get; } = BlendMode.Alpha;
[Desc("Allows to append multiple sequence definitions which are indented below this node " +
"like when offsets differ per frame or a sequence is spread across individual files.")]
public static object Combine => null;
[Desc("Sets transparency - use one value to set for all frames or provide a value for each frame.")]
public float[] Alpha { get; }
[Desc("Plays a fade out effect.")]
public static bool AlphaFade => false;
[Desc("Name of the file containing the depth data sprite.")]
public string DepthSprite { get; }
[Desc("Frame index containing the depth data.")]
public static int DepthSpriteFrame => 0;
[Desc("")]
public static float2 DepthSpriteOffset => float2.Zero;
[Desc("Use the palette embedded in the defined sprite. (Note: The name given here is actually irrelevant)")]
public static string EmbeddedPalette => null;
public readonly uint[] EmbeddedPaletteData;
protected virtual string GetSpriteSrc(ModData modData, string tileSet, string sequence, string animation, string sprite, Dictionary<string, MiniYaml> d)
{
@@ -181,39 +249,39 @@ namespace OpenRA.Mods.Common.Graphics
try
{
Start = LoadField(d, "Start", 0);
ShadowStart = LoadField(d, "ShadowStart", -1);
ShadowZOffset = LoadField(d, "ShadowZOffset", DefaultShadowSpriteZOffset).Length;
ZOffset = LoadField(d, "ZOffset", WDist.Zero).Length;
ZRamp = LoadField(d, "ZRamp", 0f);
Tick = LoadField(d, "Tick", 40);
transpose = LoadField(d, "Transpose", false);
Frames = LoadField<int[]>(d, "Frames", null);
IgnoreWorldTint = LoadField(d, "IgnoreWorldTint", false);
Scale = LoadField(d, "Scale", 1f);
Start = LoadField(d, nameof(Start), 0);
ShadowStart = LoadField(d, nameof(ShadowStart), ShadowStart);
ShadowZOffset = LoadField(d, nameof(ShadowZOffset), DefaultShadowSpriteZOffset).Length;
ZOffset = LoadField(d, nameof(ZOffset), WDist.Zero).Length;
ZRamp = LoadField(d, nameof(ZRamp), 0f);
Tick = LoadField(d, nameof(Tick), Tick);
transpose = LoadField(d, nameof(Transpose), false);
Frames = LoadField<int[]>(d, nameof(Frames), null);
IgnoreWorldTint = LoadField(d, nameof(IgnoreWorldTint), false);
Scale = LoadField(d, nameof(Scale), Scale);
var flipX = LoadField(d, "FlipX", false);
var flipY = LoadField(d, "FlipY", false);
FlipX = LoadField(d, nameof(FlipX), false);
FlipY = LoadField(d, nameof(FlipY), false);
Facings = LoadField(d, "Facings", 1);
Facings = LoadField(d, nameof(Facings), Facings);
if (Facings < 0)
{
reverseFacings = true;
Facings = -Facings;
}
var offset = LoadField(d, "Offset", float3.Zero);
var blendMode = LoadField(d, "BlendMode", BlendMode.Alpha);
Offset = LoadField(d, nameof(Offset), Offset);
BlendMode = LoadField(d, nameof(BlendMode), BlendMode);
Func<int, IEnumerable<int>> getUsedFrames = frameCount =>
{
if (d.TryGetValue("Length", out var length) && length.Value == "*")
Length = Frames != null ? Frames.Length : frameCount - Start;
if (d.TryGetValue(nameof(Length), out var length) && length.Value == "*")
Length = Frames?.Length ?? frameCount - Start;
else
Length = LoadField(d, "Length", 1);
Length = LoadField(d, nameof(Length), Length);
// Plays the animation forwards, and then in reverse
if (LoadField(d, "Reverses", false))
if (LoadField(d, nameof(Reverses), false))
{
var frames = Frames != null ? Frames.Skip(Start).Take(Length).ToArray() : Exts.MakeArray(Length, i => Start + i);
Frames = frames.Concat(frames.Skip(1).Take(Length - 2).Reverse()).ToArray();
@@ -221,7 +289,7 @@ namespace OpenRA.Mods.Common.Graphics
Start = 0;
}
Stride = LoadField(d, "Stride", Length);
Stride = LoadField(d, nameof(Stride), Length);
if (Length > Stride)
throw new YamlException($"Sequence {sequence}.{animation}: Length must be <= stride");
@@ -263,7 +331,7 @@ namespace OpenRA.Mods.Common.Graphics
return usedFrames;
};
if (d.TryGetValue("Combine", out var combine))
if (d.TryGetValue(nameof(Combine), out var combine))
{
var combined = Enumerable.Empty<Sprite>();
foreach (var sub in combine.Nodes)
@@ -271,19 +339,19 @@ namespace OpenRA.Mods.Common.Graphics
var sd = sub.Value.ToDictionary();
// Allow per-sprite offset, flipping, start, and length
var subStart = LoadField(sd, "Start", 0);
var subOffset = LoadField(sd, "Offset", float3.Zero);
var subFlipX = LoadField(sd, "FlipX", false);
var subFlipY = LoadField(sd, "FlipY", false);
var subFrames = LoadField<int[]>(sd, "Frames", null);
var subStart = LoadField(sd, nameof(Start), 0);
var subOffset = LoadField(sd, nameof(Offset), Offset);
var subFlipX = LoadField(sd, nameof(FlipX), false);
var subFlipY = LoadField(sd, nameof(FlipY), false);
var subFrames = LoadField<int[]>(sd, nameof(Frames), null);
var subLength = 0;
Func<int, IEnumerable<int>> subGetUsedFrames = subFrameCount =>
{
if (sd.TryGetValue("Length", out var subLengthYaml) && subLengthYaml.Value == "*")
if (sd.TryGetValue(nameof(Length), out var subLengthYaml) && subLengthYaml.Value == "*")
subLength = subFrames != null ? subFrames.Length : subFrameCount - subStart;
else
subLength = LoadField(sd, "Length", 1);
subLength = LoadField(sd, nameof(Length), Length);
return subFrames != null ? subFrames.Skip(subStart).Take(subLength) : Enumerable.Range(subStart, subLength);
};
@@ -295,11 +363,11 @@ namespace OpenRA.Mods.Common.Graphics
return null;
var bounds = FlipRectangle(s.Bounds, subFlipX, subFlipY);
var dx = subOffset.X + offset.X + (subFlipX ? -s.Offset.X : s.Offset.X);
var dy = subOffset.Y + offset.Y + (subFlipY ? -s.Offset.Y : s.Offset.Y);
var dz = subOffset.Z + offset.Z + s.Offset.Z + ZRamp * dy;
var dx = subOffset.X + Offset.X + (subFlipX ? -s.Offset.X : s.Offset.X);
var dy = subOffset.Y + Offset.Y + (subFlipY ? -s.Offset.Y : s.Offset.Y);
var dz = subOffset.Z + Offset.Z + s.Offset.Z + ZRamp * dy;
return new Sprite(s.Sheet, bounds, ZRamp, new float3(dx, dy, dz), s.Channel, blendMode);
return new Sprite(s.Sheet, bounds, ZRamp, new float3(dx, dy, dz), s.Channel, BlendMode);
}).ToList();
var frames = subFrames != null ? subFrames.Skip(subStart).Take(subLength).ToArray() : Exts.MakeArray(subLength, i => subStart + i);
@@ -319,39 +387,39 @@ namespace OpenRA.Mods.Common.Graphics
if (s == null)
return null;
var bounds = FlipRectangle(s.Bounds, flipX, flipY);
var dx = offset.X + (flipX ? -s.Offset.X : s.Offset.X);
var dy = offset.Y + (flipY ? -s.Offset.Y : s.Offset.Y);
var dz = offset.Z + s.Offset.Z + ZRamp * dy;
var bounds = FlipRectangle(s.Bounds, FlipX, FlipY);
var dx = Offset.X + (FlipX ? -s.Offset.X : s.Offset.X);
var dy = Offset.Y + (FlipY ? -s.Offset.Y : s.Offset.Y);
var dz = Offset.Z + s.Offset.Z + ZRamp * dy;
return new Sprite(s.Sheet, bounds, ZRamp, new float3(dx, dy, dz), s.Channel, blendMode);
return new Sprite(s.Sheet, bounds, ZRamp, new float3(dx, dy, dz), s.Channel, BlendMode);
}).ToArray();
}
alpha = LoadField(d, "Alpha", (float[])null);
if (alpha != null)
Alpha = LoadField(d, nameof(Alpha), (float[])null);
if (Alpha != null)
{
if (alpha.Length == 1)
alpha = Exts.MakeArray(Length, _ => alpha[0]);
else if (alpha.Length != Length)
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.");
}
if (LoadField(d, "AlphaFade", false))
if (LoadField(d, nameof(AlphaFade), false))
{
if (alpha != null)
if (Alpha != null)
throw new YamlException($"Sequence {sequence}.{animation} 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<string>(d, "DepthSprite", null);
if (!string.IsNullOrEmpty(depthSprite))
DepthSprite = LoadField<string>(d, nameof(DepthSprite), null);
if (!string.IsNullOrEmpty(DepthSprite))
{
var depthSpriteFrame = LoadField(d, "DepthSpriteFrame", 0);
var depthOffset = LoadField(d, "DepthSpriteOffset", float2.Zero);
Func<int, IEnumerable<int>> getDepthFrame = _ => new int[] { depthSpriteFrame };
var ds = cache[depthSprite, getDepthFrame][depthSpriteFrame];
var depthSpriteFrame = LoadField(d, nameof(DepthSpriteFrame), 0);
var depthOffset = LoadField(d, nameof(DepthSpriteOffset), DepthSpriteOffset);
IEnumerable<int> GetDepthFrame(int _) => new[] { depthSpriteFrame };
var ds = cache[DepthSprite, GetDepthFrame][depthSpriteFrame];
sprites = sprites.Select(s =>
{
@@ -368,15 +436,15 @@ namespace OpenRA.Mods.Common.Graphics
}).ToArray();
}
var exportPalette = LoadField<string>(d, "EmbeddedPalette", null);
var exportPalette = LoadField<string>(d, nameof(EmbeddedPalette), null);
if (exportPalette != null)
{
var src = GetSpriteSrc(modData, tileSet, sequence, animation, info.Value, d);
var metadata = cache.FrameMetadata(src);
var i = Frames != null ? Frames[0] : Start;
var palettes = metadata != null ? metadata.GetOrDefault<EmbeddedSpritePalette>() : null;
if (palettes == null || !palettes.TryGetPaletteForFrame(i, out EmbeddedPalette))
var palettes = metadata?.GetOrDefault<EmbeddedSpritePalette>();
if (palettes == null || !palettes.TryGetPaletteForFrame(i, out EmbeddedPaletteData))
throw new YamlException($"Cannot export palettes from {src}: frame {i} does not define an embedded palette");
}
@@ -449,7 +517,7 @@ namespace OpenRA.Mods.Common.Graphics
public virtual float GetAlpha(int frame)
{
return alpha?[frame] ?? 1f;
return Alpha?[frame] ?? 1f;
}
}
}

View File

@@ -41,14 +41,31 @@ namespace OpenRA.Mods.Common.Graphics
}
}
[Desc("A sprite sequence that can have tileset-specific variants.")]
public class TilesetSpecificSpriteSequence : DefaultSpriteSequence
{
// These need to be public properties for the documentation generation to work.
[Desc("Dictionary of <string: string> with tileset name to override -> tileset name to use instead.")]
public static Dictionary<string, string> TilesetOverrides => null;
[Desc("Use `TilesetCodes` as defined in `mod.yaml` to add a letter as a second character " +
"into the sprite filename like the Westwood 2.5D titles did for tileset-specific variants.")]
public static bool UseTilesetCode => false;
[Desc("Append a tileset-specific extension to the file name " +
"- either as defined in `mod.yaml`'s `TilesetExtensions` (if `UseTilesetExtension` is used) " +
"or the default hardcoded one for this sequence type (.shp).")]
public static bool AddExtension => true;
[Desc("Whether `mod.yaml`'s `TilesetExtensions` should be used with the sequence's file name.")]
public static bool UseTilesetExtension { get; private set; }
public TilesetSpecificSpriteSequence(ModData modData, string tileSet, SpriteCache cache, ISpriteSequenceLoader loader, string sequence, string animation, MiniYaml info)
: base(modData, tileSet, cache, loader, sequence, animation, info) { }
string ResolveTilesetId(string tileSet, Dictionary<string, MiniYaml> d)
static string ResolveTilesetId(string tileSet, Dictionary<string, MiniYaml> d)
{
if (d.TryGetValue("TilesetOverrides", out var yaml))
if (d.TryGetValue(nameof(TilesetOverrides), out var yaml))
{
var tsNode = yaml.Nodes.FirstOrDefault(n => n.Key == tileSet);
if (tsNode != null)
@@ -64,17 +81,16 @@ namespace OpenRA.Mods.Common.Graphics
var spriteName = sprite ?? sequence;
if (LoadField(d, "UseTilesetCode", false))
if (LoadField(d, nameof(UseTilesetCode), UseTilesetCode))
{
if (loader.TilesetCodes.TryGetValue(ResolveTilesetId(tileSet, d), out var code))
spriteName = spriteName.Substring(0, 1) + code + spriteName.Substring(2, spriteName.Length - 2);
}
if (LoadField(d, "AddExtension", true))
if (LoadField(d, nameof(AddExtension), AddExtension))
{
var useTilesetExtension = LoadField(d, "UseTilesetExtension", false);
if (useTilesetExtension && loader.TilesetExtensions.TryGetValue(ResolveTilesetId(tileSet, d), out var tilesetExtension))
UseTilesetExtension = LoadField(d, nameof(UseTilesetExtension), UseTilesetExtension);
if (UseTilesetExtension && loader.TilesetExtensions.TryGetValue(ResolveTilesetId(tileSet, d), out var tilesetExtension))
return spriteName + tilesetExtension;
return spriteName + loader.DefaultSpriteExtension;

View File

@@ -47,7 +47,7 @@ namespace OpenRA.Mods.Common.Traits
ImmutablePalette IProvidesCursorPaletteInfo.ReadPalette(IReadOnlyFileSystem fileSystem)
{
var sequence = (DefaultSpriteSequence)Game.ModData.DefaultSequences.Values.First().GetSequence(Image, Sequence);
return new ImmutablePalette(sequence.EmbeddedPalette);
return new ImmutablePalette(sequence.EmbeddedPaletteData);
}
}
@@ -59,7 +59,7 @@ namespace OpenRA.Mods.Common.Traits
public void LoadPalettes(WorldRenderer wr)
{
var sequence = (DefaultSpriteSequence)wr.World.Map.Rules.Sequences.GetSequence(info.Image, info.Sequence);
wr.AddPalette(info.Name, new ImmutablePalette(sequence.EmbeddedPalette), info.AllowModifiers);
wr.AddPalette(info.Name, new ImmutablePalette(sequence.EmbeddedPaletteData), info.AllowModifiers);
}
public IEnumerable<string> PaletteNames { get { yield return info.Name; } }