Rewrite sequence loading logic.
Multiple layers of Lazy<T>ness are replaced with an explicit two-part loading scheme. Sequences are parsed immediately, without the need for the sprite assets, and tell the SpriteCache which frames they need. Use-cases that want the actual sprites can then tell the SpriteCache to load the frames and the sequences to resolve the sprites.
This commit is contained in:
@@ -15,7 +15,6 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA
|
||||
@@ -28,7 +27,6 @@ namespace OpenRA
|
||||
public readonly IReadOnlyDictionary<string, SoundInfo> Notifications;
|
||||
public readonly IReadOnlyDictionary<string, MusicInfo> Music;
|
||||
public readonly ITerrainInfo TerrainInfo;
|
||||
public readonly SequenceProvider Sequences;
|
||||
public readonly IReadOnlyDictionary<string, MiniYamlNode> ModelSequences;
|
||||
|
||||
public Ruleset(
|
||||
@@ -38,7 +36,6 @@ namespace OpenRA
|
||||
IReadOnlyDictionary<string, SoundInfo> notifications,
|
||||
IReadOnlyDictionary<string, MusicInfo> music,
|
||||
ITerrainInfo terrainInfo,
|
||||
SequenceProvider sequences,
|
||||
IReadOnlyDictionary<string, MiniYamlNode> modelSequences)
|
||||
{
|
||||
Actors = new ActorInfoDictionary(actors);
|
||||
@@ -47,7 +44,6 @@ namespace OpenRA
|
||||
Notifications = notifications;
|
||||
Music = music;
|
||||
TerrainInfo = terrainInfo;
|
||||
Sequences = sequences;
|
||||
ModelSequences = modelSequences;
|
||||
|
||||
foreach (var a in Actors.Values)
|
||||
@@ -145,8 +141,8 @@ namespace OpenRA
|
||||
var modelSequences = MergeOrDefault("Manifest,ModelSequences", fs, m.ModelSequences, null, null,
|
||||
k => k);
|
||||
|
||||
// The default ruleset does not include a preferred tileset or sequence set
|
||||
ruleset = new Ruleset(actors, weapons, voices, notifications, music, null, null, modelSequences);
|
||||
// The default ruleset does not include a preferred tileset
|
||||
ruleset = new Ruleset(actors, weapons, voices, notifications, music, null, modelSequences);
|
||||
}
|
||||
|
||||
if (modData.IsOnMainThread)
|
||||
@@ -170,14 +166,13 @@ namespace OpenRA
|
||||
{
|
||||
var dr = modData.DefaultRules;
|
||||
var terrainInfo = modData.DefaultTerrainInfo[tileSet];
|
||||
var sequences = modData.DefaultSequences[tileSet];
|
||||
|
||||
return new Ruleset(dr.Actors, dr.Weapons, dr.Voices, dr.Notifications, dr.Music, terrainInfo, sequences, dr.ModelSequences);
|
||||
return new Ruleset(dr.Actors, dr.Weapons, dr.Voices, dr.Notifications, dr.Music, terrainInfo, dr.ModelSequences);
|
||||
}
|
||||
|
||||
public static Ruleset Load(ModData modData, IReadOnlyFileSystem fileSystem, string tileSet,
|
||||
MiniYaml mapRules, MiniYaml mapWeapons, MiniYaml mapVoices, MiniYaml mapNotifications,
|
||||
MiniYaml mapMusic, MiniYaml mapSequences, MiniYaml mapModelSequences)
|
||||
MiniYaml mapMusic, MiniYaml mapModelSequences)
|
||||
{
|
||||
var m = modData.Manifest;
|
||||
var dr = modData.DefaultRules;
|
||||
@@ -204,16 +199,12 @@ namespace OpenRA
|
||||
// TODO: Add support for merging custom terrain modifications
|
||||
var terrainInfo = modData.DefaultTerrainInfo[tileSet];
|
||||
|
||||
// TODO: Top-level dictionary should be moved into the Ruleset instead of in its own object
|
||||
var sequences = mapSequences == null ? modData.DefaultSequences[tileSet] :
|
||||
new SequenceProvider(fileSystem, modData, tileSet, mapSequences);
|
||||
|
||||
var modelSequences = dr.ModelSequences;
|
||||
if (mapModelSequences != null)
|
||||
modelSequences = MergeOrDefault("ModelSequences", fileSystem, m.ModelSequences, mapModelSequences, dr.ModelSequences,
|
||||
k => k);
|
||||
|
||||
ruleset = new Ruleset(actors, weapons, voices, notifications, music, terrainInfo, sequences, modelSequences);
|
||||
ruleset = new Ruleset(actors, weapons, voices, notifications, music, terrainInfo, modelSequences);
|
||||
}
|
||||
|
||||
if (modData.IsOnMainThread)
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace OpenRA.Graphics
|
||||
public string Name { get; private set; }
|
||||
public bool IsDecoration { get; set; }
|
||||
|
||||
readonly SequenceProvider sequences;
|
||||
readonly SequenceSet sequences;
|
||||
readonly Func<WAngle> facingFunc;
|
||||
readonly Func<bool> paused;
|
||||
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
using Sequences = IReadOnlyDictionary<string, Lazy<IReadOnlyDictionary<string, ISpriteSequence>>>;
|
||||
using UnitSequences = Lazy<IReadOnlyDictionary<string, ISpriteSequence>>;
|
||||
|
||||
public interface ISpriteSequence
|
||||
{
|
||||
string Name { get; }
|
||||
int Length { get; }
|
||||
int Facings { get; }
|
||||
int Tick { get; }
|
||||
int ZOffset { get; }
|
||||
int ShadowZOffset { get; }
|
||||
Rectangle Bounds { get; }
|
||||
bool IgnoreWorldTint { get; }
|
||||
float Scale { get; }
|
||||
|
||||
Sprite GetSprite(int frame);
|
||||
Sprite GetSprite(int frame, WAngle facing);
|
||||
(Sprite, WAngle) GetSpriteWithRotation(int frame, WAngle facing);
|
||||
Sprite GetShadow(int frame, WAngle facing);
|
||||
float GetAlpha(int frame);
|
||||
}
|
||||
|
||||
public interface ISpriteSequenceLoader
|
||||
{
|
||||
IReadOnlyDictionary<string, ISpriteSequence> ParseSequences(ModData modData, string tileSet, SpriteCache cache, MiniYamlNode node);
|
||||
}
|
||||
|
||||
public class SequenceProvider : IDisposable
|
||||
{
|
||||
readonly ModData modData;
|
||||
readonly string tileSet;
|
||||
readonly Lazy<Sequences> sequences;
|
||||
readonly Lazy<SpriteCache> spriteCache;
|
||||
public SpriteCache SpriteCache => spriteCache.Value;
|
||||
|
||||
readonly Dictionary<string, UnitSequences> sequenceCache = new Dictionary<string, UnitSequences>();
|
||||
|
||||
public SequenceProvider(IReadOnlyFileSystem fileSystem, ModData modData, string tileSet, MiniYaml additionalSequences)
|
||||
{
|
||||
this.modData = modData;
|
||||
this.tileSet = tileSet;
|
||||
sequences = Exts.Lazy(() =>
|
||||
{
|
||||
using (new Support.PerfTimer("LoadSequences"))
|
||||
return Load(fileSystem, additionalSequences);
|
||||
});
|
||||
|
||||
spriteCache = Exts.Lazy(() => new SpriteCache(fileSystem, modData.SpriteLoaders));
|
||||
}
|
||||
|
||||
public ISpriteSequence GetSequence(string unitName, string sequenceName)
|
||||
{
|
||||
if (!sequences.Value.TryGetValue(unitName, out var unitSeq))
|
||||
throw new InvalidOperationException($"Unit `{unitName}` does not have any sequences defined.");
|
||||
|
||||
if (!unitSeq.Value.TryGetValue(sequenceName, out var seq))
|
||||
throw new InvalidOperationException($"Unit `{unitName}` does not have a sequence named `{sequenceName}`");
|
||||
|
||||
return seq;
|
||||
}
|
||||
|
||||
public IEnumerable<string> Images => sequences.Value.Keys;
|
||||
|
||||
public bool HasSequence(string unitName)
|
||||
{
|
||||
return sequences.Value.ContainsKey(unitName);
|
||||
}
|
||||
|
||||
public bool HasSequence(string unitName, string sequenceName)
|
||||
{
|
||||
if (!sequences.Value.TryGetValue(unitName, out var unitSeq))
|
||||
throw new InvalidOperationException($"Unit `{unitName}` does not have any sequences defined.");
|
||||
|
||||
return unitSeq.Value.ContainsKey(sequenceName);
|
||||
}
|
||||
|
||||
public IEnumerable<string> Sequences(string unitName)
|
||||
{
|
||||
if (!sequences.Value.TryGetValue(unitName, out var unitSeq))
|
||||
throw new InvalidOperationException($"Unit `{unitName}` does not have any sequences defined.");
|
||||
|
||||
return unitSeq.Value.Keys;
|
||||
}
|
||||
|
||||
Sequences Load(IReadOnlyFileSystem fileSystem, MiniYaml additionalSequences)
|
||||
{
|
||||
var nodes = MiniYaml.Load(fileSystem, modData.Manifest.Sequences, additionalSequences);
|
||||
var items = new Dictionary<string, UnitSequences>();
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
// Nodes starting with ^ are inheritable but never loaded directly
|
||||
if (node.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal))
|
||||
continue;
|
||||
|
||||
var key = node.Value.ToLines(node.Key).JoinWith("|");
|
||||
|
||||
if (sequenceCache.TryGetValue(key, out var t))
|
||||
items.Add(node.Key, t);
|
||||
else
|
||||
{
|
||||
t = Exts.Lazy(() => modData.SpriteSequenceLoader.ParseSequences(modData, tileSet, SpriteCache, node));
|
||||
sequenceCache.Add(key, t);
|
||||
items.Add(node.Key, t);
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public void Preload()
|
||||
{
|
||||
foreach (var sb in SpriteCache.SheetBuilders.Values)
|
||||
sb.Current.CreateBuffer();
|
||||
|
||||
foreach (var unitSeq in sequences.Value.Values)
|
||||
foreach (var seq in unitSeq.Value.Values) { }
|
||||
|
||||
foreach (var sb in SpriteCache.SheetBuilders.Values)
|
||||
sb.Current.ReleaseBuffer();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (spriteCache.IsValueCreated)
|
||||
foreach (var sb in SpriteCache.SheetBuilders.Values)
|
||||
sb.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
119
OpenRA.Game/Graphics/SequenceSet.cs
Normal file
119
OpenRA.Game/Graphics/SequenceSet.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public interface ISpriteSequence
|
||||
{
|
||||
string Name { get; }
|
||||
int Length { get; }
|
||||
int Facings { get; }
|
||||
int Tick { get; }
|
||||
int ZOffset { get; }
|
||||
int ShadowZOffset { get; }
|
||||
Rectangle Bounds { get; }
|
||||
bool IgnoreWorldTint { get; }
|
||||
float Scale { get; }
|
||||
void ResolveSprites(SpriteCache cache);
|
||||
Sprite GetSprite(int frame);
|
||||
Sprite GetSprite(int frame, WAngle facing);
|
||||
(Sprite, WAngle) GetSpriteWithRotation(int frame, WAngle facing);
|
||||
Sprite GetShadow(int frame, WAngle facing);
|
||||
float GetAlpha(int frame);
|
||||
}
|
||||
|
||||
public interface ISpriteSequenceLoader
|
||||
{
|
||||
int BgraSheetSize { get; }
|
||||
int IndexedSheetSize { get; }
|
||||
IReadOnlyDictionary<string, ISpriteSequence> ParseSequences(ModData modData, string tileSet, SpriteCache cache, MiniYamlNode node);
|
||||
}
|
||||
|
||||
public sealed class SequenceSet : IDisposable
|
||||
{
|
||||
readonly ModData modData;
|
||||
readonly string tileSet;
|
||||
readonly IReadOnlyDictionary<string, IReadOnlyDictionary<string, ISpriteSequence>> images;
|
||||
public SpriteCache SpriteCache { get; }
|
||||
|
||||
public SequenceSet(IReadOnlyFileSystem fileSystem, ModData modData, string tileSet, MiniYaml additionalSequences)
|
||||
{
|
||||
this.modData = modData;
|
||||
this.tileSet = tileSet;
|
||||
SpriteCache = new SpriteCache(fileSystem, modData.SpriteLoaders, modData.SpriteSequenceLoader.BgraSheetSize, modData.SpriteSequenceLoader.IndexedSheetSize);
|
||||
using (new Support.PerfTimer("LoadSequences"))
|
||||
images = Load(fileSystem, additionalSequences);
|
||||
}
|
||||
|
||||
public ISpriteSequence GetSequence(string image, string sequence)
|
||||
{
|
||||
if (!images.TryGetValue(image, out var sequences))
|
||||
throw new InvalidOperationException($"Image `{image}` does not have any sequences defined.");
|
||||
|
||||
if (!sequences.TryGetValue(sequence, out var seq))
|
||||
throw new InvalidOperationException($"Image `{image}` does not have a sequence named `{sequence}`.");
|
||||
|
||||
return seq;
|
||||
}
|
||||
|
||||
public IEnumerable<string> Images => images.Keys;
|
||||
|
||||
public bool HasSequence(string image, string sequence)
|
||||
{
|
||||
if (!images.TryGetValue(image, out var sequences))
|
||||
throw new InvalidOperationException($"Image `{image}` does not have any sequences defined.");
|
||||
|
||||
return sequences.ContainsKey(sequence);
|
||||
}
|
||||
|
||||
public IEnumerable<string> Sequences(string image)
|
||||
{
|
||||
if (!images.TryGetValue(image, out var sequences))
|
||||
throw new InvalidOperationException($"Image `{image}` does not have any sequences defined.");
|
||||
|
||||
return sequences.Keys;
|
||||
}
|
||||
|
||||
IReadOnlyDictionary<string, IReadOnlyDictionary<string, ISpriteSequence>> Load(IReadOnlyFileSystem fileSystem, MiniYaml additionalSequences)
|
||||
{
|
||||
var nodes = MiniYaml.Load(fileSystem, modData.Manifest.Sequences, additionalSequences);
|
||||
var images = new Dictionary<string, IReadOnlyDictionary<string, ISpriteSequence>>();
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
// Nodes starting with ^ are inheritable but never loaded directly
|
||||
if (node.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal))
|
||||
continue;
|
||||
|
||||
images[node.Key] = modData.SpriteSequenceLoader.ParseSequences(modData, tileSet, SpriteCache, node);
|
||||
}
|
||||
|
||||
return images;
|
||||
}
|
||||
|
||||
public void LoadSprites()
|
||||
{
|
||||
SpriteCache.LoadReservations(modData);
|
||||
foreach (var sequences in images.Values)
|
||||
foreach (var sequence in sequences)
|
||||
sequence.Value.ResolveSprites(SpriteCache);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
SpriteCache.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
174
OpenRA.Game/Graphics/SpriteCache.cs
Normal file
174
OpenRA.Game/Graphics/SpriteCache.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public class SpriteCache : IDisposable
|
||||
{
|
||||
public readonly Dictionary<SheetType, SheetBuilder> SheetBuilders;
|
||||
readonly ISpriteLoader[] loaders;
|
||||
readonly IReadOnlyFileSystem fileSystem;
|
||||
|
||||
readonly Dictionary<int, (int[] Frames, MiniYamlNode.SourceLocation Location)> spriteReservations = new Dictionary<int, (int[], MiniYamlNode.SourceLocation)>();
|
||||
readonly Dictionary<int, (int[] Frames, MiniYamlNode.SourceLocation Location)> frameReservations = new Dictionary<int, (int[], MiniYamlNode.SourceLocation)>();
|
||||
readonly Dictionary<string, List<int>> reservationsByFilename = new Dictionary<string, List<int>>();
|
||||
|
||||
readonly Dictionary<int, ISpriteFrame[]> resolvedFrames = new Dictionary<int, ISpriteFrame[]>();
|
||||
readonly Dictionary<int, Sprite[]> resolvedSprites = new Dictionary<int, Sprite[]>();
|
||||
|
||||
readonly Dictionary<int, (string Filename, MiniYamlNode.SourceLocation Location)> missingFiles = new Dictionary<int, (string, MiniYamlNode.SourceLocation)>();
|
||||
|
||||
int nextReservationToken = 1;
|
||||
|
||||
public SpriteCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders, int bgraSheetSize, int indexedSheetSize, int bgraSheetMargin = 1, int indexedSheetMargin = 1)
|
||||
{
|
||||
SheetBuilders = new Dictionary<SheetType, SheetBuilder>
|
||||
{
|
||||
{ SheetType.Indexed, new SheetBuilder(SheetType.Indexed, indexedSheetSize, indexedSheetMargin) },
|
||||
{ SheetType.BGRA, new SheetBuilder(SheetType.BGRA, bgraSheetSize, bgraSheetMargin) }
|
||||
};
|
||||
|
||||
this.fileSystem = fileSystem;
|
||||
this.loaders = loaders;
|
||||
}
|
||||
|
||||
public int ReserveSprites(string filename, IEnumerable<int> frames, MiniYamlNode.SourceLocation location)
|
||||
{
|
||||
var token = nextReservationToken++;
|
||||
spriteReservations[token] = (frames?.ToArray(), location);
|
||||
reservationsByFilename.GetOrAdd(filename, _ => new List<int>()).Add(token);
|
||||
return token;
|
||||
}
|
||||
|
||||
public int ReserveFrames(string filename, IEnumerable<int> frames, MiniYamlNode.SourceLocation location)
|
||||
{
|
||||
var token = nextReservationToken++;
|
||||
frameReservations[token] = (frames?.ToArray(), location);
|
||||
reservationsByFilename.GetOrAdd(filename, _ => new List<int>()).Add(token);
|
||||
return token;
|
||||
}
|
||||
|
||||
static ISpriteFrame[] GetFrames(IReadOnlyFileSystem fileSystem, string filename, ISpriteLoader[] loaders, out TypeDictionary metadata)
|
||||
{
|
||||
metadata = null;
|
||||
if (!fileSystem.TryOpen(filename, out var stream))
|
||||
return null;
|
||||
|
||||
using (stream)
|
||||
{
|
||||
foreach (var loader in loaders)
|
||||
if (loader.TryParseSprite(stream, filename, out var frames, out metadata))
|
||||
return frames;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadReservations(ModData modData)
|
||||
{
|
||||
foreach (var sb in SheetBuilders.Values)
|
||||
sb.Current.CreateBuffer();
|
||||
|
||||
var spriteCache = new Dictionary<int, Sprite>();
|
||||
foreach (var (filename, tokens) in reservationsByFilename)
|
||||
{
|
||||
modData.LoadScreen?.Display();
|
||||
var loadedFrames = GetFrames(fileSystem, filename, loaders, out _);
|
||||
foreach (var token in tokens)
|
||||
{
|
||||
if (frameReservations.TryGetValue(token, out var r))
|
||||
{
|
||||
if (loadedFrames != null)
|
||||
{
|
||||
if (r.Frames != null)
|
||||
{
|
||||
var resolved = new ISpriteFrame[loadedFrames.Length];
|
||||
foreach (var i in r.Frames)
|
||||
resolved[i] = loadedFrames[i];
|
||||
resolvedFrames[token] = resolved;
|
||||
}
|
||||
else
|
||||
resolvedFrames[token] = loadedFrames;
|
||||
}
|
||||
else
|
||||
{
|
||||
resolvedFrames[token] = null;
|
||||
missingFiles[token] = (filename, r.Location);
|
||||
}
|
||||
}
|
||||
|
||||
if (spriteReservations.TryGetValue(token, out r))
|
||||
{
|
||||
if (loadedFrames != null)
|
||||
{
|
||||
var resolved = new Sprite[loadedFrames.Length];
|
||||
var frames = r.Frames ?? Enumerable.Range(0, loadedFrames.Length);
|
||||
foreach (var i in frames)
|
||||
resolved[i] = spriteCache.GetOrAdd(i,
|
||||
f => SheetBuilders[SheetBuilder.FrameTypeToSheetType(loadedFrames[f].Type)].Add(loadedFrames[f]));
|
||||
|
||||
resolvedSprites[token] = resolved;
|
||||
}
|
||||
else
|
||||
{
|
||||
resolvedSprites[token] = null;
|
||||
missingFiles[token] = (filename, r.Location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spriteCache.Clear();
|
||||
}
|
||||
|
||||
spriteReservations.Clear();
|
||||
frameReservations.Clear();
|
||||
reservationsByFilename.Clear();
|
||||
|
||||
foreach (var sb in SheetBuilders.Values)
|
||||
sb.Current.ReleaseBuffer();
|
||||
}
|
||||
|
||||
public Sprite[] ResolveSprites(int token)
|
||||
{
|
||||
var resolved = resolvedSprites[token];
|
||||
resolvedSprites.Remove(token);
|
||||
if (missingFiles.TryGetValue(token, out var r))
|
||||
throw new FileNotFoundException($"{r.Location}: {r.Filename} not found", r.Filename);
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
public ISpriteFrame[] ResolveFrames(int token)
|
||||
{
|
||||
var resolved = resolvedFrames[token];
|
||||
resolvedFrames.Remove(token);
|
||||
if (missingFiles.TryGetValue(token, out var r))
|
||||
throw new FileNotFoundException($"{r.Location}: {r.Filename} not found", r.Filename);
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
public IEnumerable<(string Filename, MiniYamlNode.SourceLocation Location)> MissingFiles => missingFiles.Values.ToHashSet();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var sb in SheetBuilders.Values)
|
||||
sb.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
@@ -67,94 +64,6 @@ namespace OpenRA.Graphics
|
||||
bool DisableExportPadding { get; }
|
||||
}
|
||||
|
||||
public class SpriteCache
|
||||
{
|
||||
public readonly Cache<SheetType, SheetBuilder> SheetBuilders;
|
||||
readonly ISpriteLoader[] loaders;
|
||||
readonly IReadOnlyFileSystem fileSystem;
|
||||
|
||||
readonly Dictionary<string, List<Sprite[]>> sprites = new Dictionary<string, List<Sprite[]>>();
|
||||
readonly Dictionary<string, ISpriteFrame[]> unloadedFrames = new Dictionary<string, ISpriteFrame[]>();
|
||||
readonly Dictionary<string, TypeDictionary> metadata = new Dictionary<string, TypeDictionary>();
|
||||
|
||||
public SpriteCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders)
|
||||
{
|
||||
SheetBuilders = new Cache<SheetType, SheetBuilder>(t => new SheetBuilder(t));
|
||||
|
||||
this.fileSystem = fileSystem;
|
||||
this.loaders = loaders;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the first set of sprites with the given filename.
|
||||
/// If getUsedFrames is defined then the indices returned by the function call
|
||||
/// are guaranteed to be loaded. The value of other indices in the returned
|
||||
/// array are undefined and should never be accessed.
|
||||
/// </summary>
|
||||
public Sprite[] this[string filename, Func<int, IEnumerable<int>> getUsedFrames = null]
|
||||
{
|
||||
get
|
||||
{
|
||||
var allSprites = sprites.GetOrAdd(filename);
|
||||
var sprite = allSprites.FirstOrDefault();
|
||||
|
||||
if (!unloadedFrames.TryGetValue(filename, out var unloaded))
|
||||
unloaded = null;
|
||||
|
||||
// This is the first time that the file has been requested
|
||||
// Load all of the frames into the unused buffer and initialize
|
||||
// the loaded cache (initially empty)
|
||||
if (sprite == null)
|
||||
{
|
||||
unloaded = FrameLoader.GetFrames(fileSystem, filename, loaders, out var fileMetadata);
|
||||
unloadedFrames[filename] = unloaded;
|
||||
metadata[filename] = fileMetadata;
|
||||
|
||||
sprite = new Sprite[unloaded.Length];
|
||||
allSprites.Add(sprite);
|
||||
}
|
||||
|
||||
// HACK: The sequence code relies on side-effects from getUsedFrames
|
||||
var indices = getUsedFrames != null ? getUsedFrames(sprite.Length) :
|
||||
Enumerable.Range(0, sprite.Length);
|
||||
|
||||
// Load any unused frames into the SheetBuilder
|
||||
if (unloaded != null)
|
||||
{
|
||||
foreach (var i in indices)
|
||||
{
|
||||
if (unloaded[i] != null)
|
||||
{
|
||||
sprite[i] = SheetBuilders[SheetBuilder.FrameTypeToSheetType(unloaded[i].Type)].Add(unloaded[i]);
|
||||
unloaded[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
// All frames have been loaded
|
||||
if (unloaded.All(f => f == null))
|
||||
unloadedFrames.Remove(filename);
|
||||
}
|
||||
|
||||
return sprite;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a TypeDictionary containing any metadata defined by the frame
|
||||
/// or null if the frame does not define metadata.
|
||||
/// </summary>
|
||||
public TypeDictionary FrameMetadata(string filename)
|
||||
{
|
||||
if (!metadata.TryGetValue(filename, out var fileMetadata))
|
||||
{
|
||||
FrameLoader.GetFrames(fileSystem, filename, loaders, out fileMetadata);
|
||||
metadata[filename] = fileMetadata;
|
||||
}
|
||||
|
||||
return fileMetadata;
|
||||
}
|
||||
}
|
||||
|
||||
public class FrameCache
|
||||
{
|
||||
readonly Cache<string, ISpriteFrame[]> frames;
|
||||
|
||||
@@ -218,7 +218,7 @@ namespace OpenRA
|
||||
public string Uid { get; private set; }
|
||||
|
||||
public Ruleset Rules { get; private set; }
|
||||
public SequenceProvider Sequences => Rules.Sequences;
|
||||
public SequenceSet Sequences { get; private set; }
|
||||
|
||||
public bool InvalidCustomRules { get; private set; }
|
||||
public Exception InvalidCustomRulesException { get; private set; }
|
||||
@@ -439,7 +439,7 @@ namespace OpenRA
|
||||
try
|
||||
{
|
||||
Rules = Ruleset.Load(modData, this, Tileset, RuleDefinitions, WeaponDefinitions,
|
||||
VoiceDefinitions, NotificationDefinitions, MusicDefinitions, SequenceDefinitions, ModelSequenceDefinitions);
|
||||
VoiceDefinitions, NotificationDefinitions, MusicDefinitions, ModelSequenceDefinitions);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -449,8 +449,7 @@ namespace OpenRA
|
||||
Rules = Ruleset.LoadDefaultsForTileSet(modData, Tileset);
|
||||
}
|
||||
|
||||
Rules.Sequences.Preload();
|
||||
|
||||
Sequences = new SequenceSet(this, modData, Tileset, SequenceDefinitions);
|
||||
Translation = new Translation(Game.Settings.Player.Language, Translations, this);
|
||||
|
||||
var tl = new MPos(0, 0).ToCPos(this);
|
||||
|
||||
@@ -226,7 +226,7 @@ namespace OpenRA
|
||||
{
|
||||
return Ruleset.Load(modData, this, TileSet, innerData.RuleDefinitions,
|
||||
innerData.WeaponDefinitions, innerData.VoiceDefinitions, innerData.NotificationDefinitions,
|
||||
innerData.MusicDefinitions, innerData.SequenceDefinitions, innerData.ModelSequenceDefinitions);
|
||||
innerData.MusicDefinitions, innerData.ModelSequenceDefinitions);
|
||||
}
|
||||
|
||||
public MapPreview(ModData modData, string uid, MapGridType gridType, MapCache cache)
|
||||
|
||||
@@ -48,9 +48,6 @@ namespace OpenRA
|
||||
readonly Lazy<IReadOnlyDictionary<string, ITerrainInfo>> defaultTerrainInfo;
|
||||
public IReadOnlyDictionary<string, ITerrainInfo> DefaultTerrainInfo => defaultTerrainInfo.Value;
|
||||
|
||||
readonly Lazy<IReadOnlyDictionary<string, SequenceProvider>> defaultSequences;
|
||||
public IReadOnlyDictionary<string, SequenceProvider> DefaultSequences => defaultSequences.Value;
|
||||
|
||||
public ModData(Manifest mod, InstalledMods mods, bool useLoadScreen = false)
|
||||
{
|
||||
Languages = Array.Empty<string>();
|
||||
@@ -121,12 +118,6 @@ namespace OpenRA
|
||||
return (IReadOnlyDictionary<string, ITerrainInfo>)new ReadOnlyDictionary<string, ITerrainInfo>(items);
|
||||
});
|
||||
|
||||
defaultSequences = Exts.Lazy(() =>
|
||||
{
|
||||
var items = DefaultTerrainInfo.ToDictionary(t => t.Key, t => new SequenceProvider(DefaultFileSystem, this, t.Key, null));
|
||||
return (IReadOnlyDictionary<string, SequenceProvider>)new ReadOnlyDictionary<string, SequenceProvider>(items);
|
||||
});
|
||||
|
||||
initialThreadId = Environment.CurrentManagedThreadId;
|
||||
}
|
||||
|
||||
@@ -167,6 +158,7 @@ namespace OpenRA
|
||||
|
||||
// Reinitialize all our assets
|
||||
InitializeLoaders(map);
|
||||
map.Sequences.LoadSprites();
|
||||
|
||||
// Load music with map assets mounted
|
||||
using (new Support.PerfTimer("Map.Music"))
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace OpenRA.Mods.Cnc.Graphics
|
||||
|
||||
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, image, sequence, data, defaults);
|
||||
return new ClassicSpriteSequence(cache, this, image, sequence, data, defaults);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,8 @@ 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 image, string sequence, MiniYaml data, MiniYaml defaults)
|
||||
: base(modData, tileSet, cache, loader, image, sequence, data, defaults)
|
||||
public ClassicSpriteSequence(SpriteCache cache, ISpriteSequenceLoader loader, string image, string sequence, MiniYaml data, MiniYaml defaults)
|
||||
: base(cache, loader, image, sequence, data, defaults)
|
||||
{
|
||||
useClassicFacings = LoadField(UseClassicFacings, data, defaults);
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace OpenRA.Mods.Cnc.Graphics
|
||||
|
||||
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, image, sequence, data, defaults);
|
||||
return new ClassicTilesetSpecificSpriteSequence(cache, this, image, sequence, data, defaults);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,20 +34,56 @@ namespace OpenRA.Mods.Cnc.Graphics
|
||||
[Desc("Dictionary of <tileset name>: filename to override the Filename key.")]
|
||||
static readonly SpriteSequenceField<Dictionary<string, string>> TilesetFilenames = new SpriteSequenceField<Dictionary<string, string>>(nameof(TilesetFilenames), null);
|
||||
|
||||
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) { }
|
||||
public ClassicTilesetSpecificSpriteSequence(SpriteCache cache, ISpriteSequenceLoader loader, string image, string sequence, MiniYaml data, MiniYaml defaults)
|
||||
: base(cache, loader, image, sequence, data, defaults) { }
|
||||
|
||||
protected override string GetSpriteFilename(ModData modData, string tileset, string image, string sequence, MiniYaml data, MiniYaml defaults)
|
||||
protected override IEnumerable<ReservationInfo> ParseFilenames(ModData modData, string tileset, int[] frames, MiniYaml data, MiniYaml defaults)
|
||||
{
|
||||
var node = data.Nodes.FirstOrDefault(n => n.Key == TilesetFilenames.Key) ?? defaults.Nodes.FirstOrDefault(n => n.Key == TilesetFilenames.Key);
|
||||
if (node != null)
|
||||
{
|
||||
var tilesetNode = node.Value.Nodes.FirstOrDefault(n => n.Key == tileset);
|
||||
if (tilesetNode != null)
|
||||
return tilesetNode.Value.Value;
|
||||
{
|
||||
// Only request the subset of frames that we actually need
|
||||
int[] loadFrames = null;
|
||||
if (length != null)
|
||||
{
|
||||
loadFrames = CalculateFrameIndices(start, length.Value, stride ?? length.Value, facings, frames, transpose, reverseFacings);
|
||||
if (shadowStart >= 0)
|
||||
loadFrames = loadFrames.Concat(loadFrames.Select(i => i + shadowStart - start)).ToArray();
|
||||
}
|
||||
|
||||
return base.GetSpriteFilename(modData, tileset, image, sequence, data, defaults);
|
||||
return new[] { new ReservationInfo(tilesetNode.Value.Value, loadFrames, frames, tilesetNode.Location) };
|
||||
}
|
||||
}
|
||||
|
||||
return base.ParseFilenames(modData, tileset, frames, data, defaults);
|
||||
}
|
||||
|
||||
protected override IEnumerable<ReservationInfo> ParseCombineFilenames(ModData modData, string tileset, int[] frames, MiniYaml data)
|
||||
{
|
||||
var node = data.Nodes.FirstOrDefault(n => n.Key == TilesetFilenames.Key);
|
||||
if (node != null)
|
||||
{
|
||||
var tilesetNode = node.Value.Nodes.FirstOrDefault(n => n.Key == tileset);
|
||||
if (tilesetNode != null)
|
||||
{
|
||||
if (frames == null)
|
||||
{
|
||||
if (LoadField<string>("Length", null, data) != "*")
|
||||
{
|
||||
var subStart = LoadField("Start", 0, data);
|
||||
var subLength = LoadField("Length", 1, data);
|
||||
frames = Exts.MakeArray(subLength, i => subStart + i);
|
||||
}
|
||||
}
|
||||
|
||||
return new[] { new ReservationInfo(tilesetNode.Value.Value, frames, frames, tilesetNode.Location) };
|
||||
}
|
||||
}
|
||||
|
||||
return base.ParseCombineFilenames(modData, tileset, frames, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,29 +21,35 @@ namespace OpenRA.Mods.Common.Graphics
|
||||
{
|
||||
public class DefaultSpriteSequenceLoader : ISpriteSequenceLoader
|
||||
{
|
||||
public readonly int BgraSheetSize = 2048;
|
||||
public readonly int IndexedSheetSize = 2048;
|
||||
|
||||
static readonly MiniYaml NoData = new MiniYaml(null);
|
||||
|
||||
public DefaultSpriteSequenceLoader(ModData modData) { }
|
||||
public DefaultSpriteSequenceLoader(ModData modData)
|
||||
{
|
||||
var metadata = modData.Manifest.Get<SpriteSequenceFormat>().Metadata;
|
||||
if (metadata.TryGetValue("BgraSheetSize", out var yaml))
|
||||
BgraSheetSize = FieldLoader.GetValue<int>("BgraSheetSize", yaml.Value);
|
||||
|
||||
if (metadata.TryGetValue("IndexedSheetSize", out yaml))
|
||||
IndexedSheetSize = FieldLoader.GetValue<int>("IndexedSheetSize", yaml.Value);
|
||||
}
|
||||
|
||||
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, image, sequence, data, defaults);
|
||||
return new DefaultSpriteSequence(cache, this, image, sequence, data, defaults);
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, ISpriteSequence> ParseSequences(ModData modData, string tileSet, SpriteCache cache, MiniYamlNode imageNode)
|
||||
int ISpriteSequenceLoader.BgraSheetSize => BgraSheetSize;
|
||||
int ISpriteSequenceLoader.IndexedSheetSize => IndexedSheetSize;
|
||||
|
||||
IReadOnlyDictionary<string, ISpriteSequence> ISpriteSequenceLoader.ParseSequences(ModData modData, string tileset, SpriteCache cache, MiniYamlNode imageNode)
|
||||
{
|
||||
var sequences = new Dictionary<string, ISpriteSequence>();
|
||||
MiniYaml defaults;
|
||||
try
|
||||
{
|
||||
var node = imageNode.Value.Nodes.SingleOrDefault(n => n.Key == "Defaults");
|
||||
defaults = node?.Value ?? NoData;
|
||||
var defaults = node?.Value ?? NoData;
|
||||
imageNode.Value.Nodes.Remove(node);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidDataException($"Error occurred while parsing {imageNode.Key}", e);
|
||||
}
|
||||
|
||||
foreach (var sequenceNode in imageNode.Value.Nodes)
|
||||
{
|
||||
@@ -51,18 +57,13 @@ namespace OpenRA.Mods.Common.Graphics
|
||||
{
|
||||
try
|
||||
{
|
||||
var sequence = CreateSequence(modData, tileSet, cache, imageNode.Key, sequenceNode.Key, sequenceNode.Value, defaults);
|
||||
var sequence = CreateSequence(modData, tileset, cache, imageNode.Key, sequenceNode.Key, sequenceNode.Value, defaults);
|
||||
((DefaultSpriteSequence)sequence).ReserveSprites(modData, tileset, cache, sequenceNode.Value, defaults);
|
||||
sequences.Add(sequenceNode.Key, sequence);
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
catch (Exception e)
|
||||
{
|
||||
// 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(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}");
|
||||
throw new InvalidDataException($"Failed to parse sequences for {imageNode.Key}.{sequenceNode.Key} at {imageNode.Value.Nodes[0].Location}:\n{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,33 +72,6 @@ namespace OpenRA.Mods.Common.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
public class FileNotFoundSequence : ISpriteSequence
|
||||
{
|
||||
readonly FileNotFoundException exception;
|
||||
|
||||
public FileNotFoundSequence(FileNotFoundException exception)
|
||||
{
|
||||
this.exception = exception;
|
||||
}
|
||||
|
||||
public string Filename => exception.FileName;
|
||||
|
||||
string ISpriteSequence.Name => throw exception;
|
||||
int ISpriteSequence.Length => throw exception;
|
||||
int ISpriteSequence.Facings => throw exception;
|
||||
int ISpriteSequence.Tick => throw exception;
|
||||
int ISpriteSequence.ZOffset => throw exception;
|
||||
int ISpriteSequence.ShadowZOffset => throw exception;
|
||||
Rectangle ISpriteSequence.Bounds => throw exception;
|
||||
bool ISpriteSequence.IgnoreWorldTint => throw exception;
|
||||
float ISpriteSequence.Scale => throw exception;
|
||||
Sprite ISpriteSequence.GetSprite(int frame) { throw exception; }
|
||||
Sprite ISpriteSequence.GetSprite(int frame, WAngle facing) { throw exception; }
|
||||
(Sprite, WAngle) ISpriteSequence.GetSpriteWithRotation(int frame, WAngle facing) { throw exception; }
|
||||
Sprite ISpriteSequence.GetShadow(int frame, WAngle facing) { throw exception; }
|
||||
float ISpriteSequence.GetAlpha(int frame) { throw exception; }
|
||||
}
|
||||
|
||||
public struct SpriteSequenceField<T>
|
||||
{
|
||||
public string Key;
|
||||
@@ -113,139 +87,200 @@ 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);
|
||||
protected class SpriteReservation
|
||||
{
|
||||
public int Token;
|
||||
public float3 Offset;
|
||||
public bool FlipX;
|
||||
public bool FlipY;
|
||||
public float ZRamp;
|
||||
public BlendMode BlendMode;
|
||||
public int[] Frames;
|
||||
}
|
||||
|
||||
readonly string image;
|
||||
protected Sprite[] sprites;
|
||||
readonly bool reverseFacings, transpose;
|
||||
protected readonly struct ReservationInfo
|
||||
{
|
||||
public readonly string Filename;
|
||||
public readonly int[] LoadFrames;
|
||||
public readonly int[] Frames;
|
||||
public readonly MiniYamlNode.SourceLocation Location;
|
||||
|
||||
protected readonly ISpriteSequenceLoader Loader;
|
||||
|
||||
public Rectangle Bounds { get; }
|
||||
|
||||
public string Name { get; }
|
||||
public ReservationInfo(string filename, int[] loadFrames, int[] frames, MiniYamlNode.SourceLocation location)
|
||||
{
|
||||
Filename = filename;
|
||||
LoadFrames = loadFrames;
|
||||
Frames = frames;
|
||||
Location = location;
|
||||
}
|
||||
}
|
||||
|
||||
[Desc("File name of the sprite to use for this sequence.")]
|
||||
static readonly SpriteSequenceField<string> Filename = new SpriteSequenceField<string>(nameof(Filename), null);
|
||||
protected static readonly SpriteSequenceField<string> Filename = new SpriteSequenceField<string>(nameof(Filename), null);
|
||||
|
||||
[Desc("Frame index to start from.")]
|
||||
static readonly SpriteSequenceField<int> Start = new SpriteSequenceField<int>(nameof(Start), 0);
|
||||
int start;
|
||||
protected static readonly SpriteSequenceField<int> Start = new SpriteSequenceField<int>(nameof(Start), 0);
|
||||
|
||||
[Desc("Number of frames to use. Does not have to be the total amount the sprite sheet has.")]
|
||||
static readonly SpriteSequenceField<int> Length = new SpriteSequenceField<int>(nameof(Length), 1);
|
||||
int ISpriteSequence.Length => length;
|
||||
int length;
|
||||
protected static readonly SpriteSequenceField<int> Length = new SpriteSequenceField<int>(nameof(Length), 1);
|
||||
|
||||
[Desc("Overrides Length if a different number of frames is defined between facings.")]
|
||||
static readonly SpriteSequenceField<int> Stride = new SpriteSequenceField<int>(nameof(Stride), -1);
|
||||
int stride;
|
||||
protected static readonly SpriteSequenceField<int> Stride = new SpriteSequenceField<int>(nameof(Stride), -1);
|
||||
|
||||
[Desc("The amount of directions the unit faces. Use negative values to rotate counter-clockwise.")]
|
||||
static readonly SpriteSequenceField<int> Facings = new SpriteSequenceField<int>(nameof(Facings), 1);
|
||||
int ISpriteSequence.Facings => interpolatedFacings ?? facings;
|
||||
protected int facings;
|
||||
[Desc("The number of facings that are provided by sprite frames. Use negative values to rotate counter-clockwise.")]
|
||||
protected static readonly SpriteSequenceField<int> Facings = new SpriteSequenceField<int>(nameof(Facings), 1);
|
||||
|
||||
[Desc("The amount of directions the unit faces. Use negative values to rotate counter-clockwise.")]
|
||||
static readonly SpriteSequenceField<int?> InterpolatedFacings = new SpriteSequenceField<int?>(nameof(InterpolatedFacings), null);
|
||||
protected int? interpolatedFacings;
|
||||
[Desc("The total number of facings for the sequence. If >Facings, the closest facing sprite will be rotated to match. Use negative values to rotate counter-clockwise.")]
|
||||
protected static readonly SpriteSequenceField<int?> InterpolatedFacings = new SpriteSequenceField<int?>(nameof(InterpolatedFacings), null);
|
||||
|
||||
[Desc("Time (in milliseconds at default game speed) to wait until playing the next frame in the animation.")]
|
||||
static readonly SpriteSequenceField<int> Tick = new SpriteSequenceField<int>(nameof(Tick), 40);
|
||||
int ISpriteSequence.Tick => tick;
|
||||
readonly int tick;
|
||||
protected static readonly SpriteSequenceField<int> Tick = new SpriteSequenceField<int>(nameof(Tick), 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.")]
|
||||
static readonly SpriteSequenceField<WDist> ZOffset = new SpriteSequenceField<WDist>(nameof(ZOffset), WDist.Zero);
|
||||
int ISpriteSequence.ZOffset => zOffset;
|
||||
readonly int zOffset;
|
||||
protected static readonly SpriteSequenceField<WDist> ZOffset = new SpriteSequenceField<WDist>(nameof(ZOffset), WDist.Zero);
|
||||
|
||||
[Desc("Additional sprite depth Z offset to apply as a function of sprite Y (0: vertical, 1: flat on terrain)")]
|
||||
static readonly SpriteSequenceField<int> ZRamp = new SpriteSequenceField<int>(nameof(ZRamp), 0);
|
||||
protected static readonly SpriteSequenceField<int> ZRamp = new SpriteSequenceField<int>(nameof(ZRamp), 0);
|
||||
|
||||
[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.")]
|
||||
static readonly SpriteSequenceField<int> ShadowStart = new SpriteSequenceField<int>(nameof(ShadowStart), -1);
|
||||
readonly int shadowStart;
|
||||
protected static readonly SpriteSequenceField<int> ShadowStart = new SpriteSequenceField<int>(nameof(ShadowStart), -1);
|
||||
|
||||
[Desc("Set Z-Offset for the separate shadow. Used by the later Westwood 2.5D titles.")]
|
||||
static readonly SpriteSequenceField<WDist> ShadowZOffset = new SpriteSequenceField<WDist>(nameof(ShadowZOffset), new WDist(-5));
|
||||
int ISpriteSequence.ShadowZOffset => shadowZOffset;
|
||||
readonly int shadowZOffset;
|
||||
protected static readonly SpriteSequenceField<WDist> ShadowZOffset = new SpriteSequenceField<WDist>(nameof(ShadowZOffset), new WDist(-5));
|
||||
|
||||
[Desc("The individual frames to play instead of going through them sequentially from the `Start`.")]
|
||||
static readonly SpriteSequenceField<int[]> Frames = new SpriteSequenceField<int[]>(nameof(Frames), null);
|
||||
int[] frames;
|
||||
protected static readonly SpriteSequenceField<int[]> Frames = new SpriteSequenceField<int[]>(nameof(Frames), null);
|
||||
|
||||
[Desc("Don't apply terrain lighting or colored overlays.")]
|
||||
static readonly SpriteSequenceField<bool> IgnoreWorldTint = new SpriteSequenceField<bool>(nameof(IgnoreWorldTint), false);
|
||||
bool ISpriteSequence.IgnoreWorldTint => ignoreWorldTint;
|
||||
readonly bool ignoreWorldTint;
|
||||
protected static readonly SpriteSequenceField<bool> IgnoreWorldTint = new SpriteSequenceField<bool>(nameof(IgnoreWorldTint), false);
|
||||
|
||||
[Desc("Adjusts the rendered size of the sprite")]
|
||||
static readonly SpriteSequenceField<float> Scale = new SpriteSequenceField<float>(nameof(Scale), 1);
|
||||
float ISpriteSequence.Scale => scale;
|
||||
readonly float scale;
|
||||
protected static readonly SpriteSequenceField<float> Scale = new SpriteSequenceField<float>(nameof(Scale), 1);
|
||||
|
||||
[Desc("Play the sprite sequence back and forth.")]
|
||||
static readonly SpriteSequenceField<bool> Reverses = new SpriteSequenceField<bool>(nameof(Reverses), false);
|
||||
protected static readonly SpriteSequenceField<bool> Reverses = new SpriteSequenceField<bool>(nameof(Reverses), false);
|
||||
|
||||
[Desc("Support a frame order where each animation step is split per each direction.")]
|
||||
static readonly SpriteSequenceField<bool> Transpose = new SpriteSequenceField<bool>(nameof(Transpose), false);
|
||||
protected static readonly SpriteSequenceField<bool> Transpose = new SpriteSequenceField<bool>(nameof(Transpose), false);
|
||||
|
||||
[Desc("Mirror on the X axis.")]
|
||||
static readonly SpriteSequenceField<bool> FlipX = new SpriteSequenceField<bool>(nameof(FlipX), false);
|
||||
protected static readonly SpriteSequenceField<bool> FlipX = new SpriteSequenceField<bool>(nameof(FlipX), false);
|
||||
|
||||
[Desc("Mirror on the Y axis.")]
|
||||
static readonly SpriteSequenceField<bool> FlipY = new SpriteSequenceField<bool>(nameof(FlipY), false);
|
||||
protected static readonly SpriteSequenceField<bool> FlipY = new SpriteSequenceField<bool>(nameof(FlipY), false);
|
||||
|
||||
[Desc("Change the position in-game on X, Y, Z.")]
|
||||
static readonly SpriteSequenceField<float3> Offset = new SpriteSequenceField<float3>(nameof(Offset), float3.Zero);
|
||||
protected static readonly SpriteSequenceField<float3> Offset = new SpriteSequenceField<float3>(nameof(Offset), float3.Zero);
|
||||
|
||||
[Desc("Apply an OpenGL/Photoshop inspired blend mode.")]
|
||||
static readonly SpriteSequenceField<BlendMode> BlendMode = new SpriteSequenceField<BlendMode>(nameof(BlendMode), OpenRA.BlendMode.Alpha);
|
||||
protected static readonly SpriteSequenceField<BlendMode> BlendMode = new SpriteSequenceField<BlendMode>(nameof(BlendMode), OpenRA.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.")]
|
||||
static readonly SpriteSequenceField<MiniYaml> Combine = new SpriteSequenceField<MiniYaml>(nameof(Combine), null);
|
||||
[Desc("Create a virtual sprite file by concatenating one or more frames from multiple files, with optional transformations applied. " +
|
||||
"All defined frames will be loaded into memory, even if unused, so use this property with care.")]
|
||||
protected static readonly SpriteSequenceField<MiniYaml> Combine = new SpriteSequenceField<MiniYaml>(nameof(Combine), null);
|
||||
|
||||
[Desc("Sets transparency - use one value to set for all frames or provide a value for each frame.")]
|
||||
static readonly SpriteSequenceField<float[]> Alpha = new SpriteSequenceField<float[]>(nameof(Alpha), null);
|
||||
readonly float[] alpha;
|
||||
protected static readonly SpriteSequenceField<float[]> Alpha = new SpriteSequenceField<float[]>(nameof(Alpha), null);
|
||||
|
||||
[Desc("Fade the animation from fully opaque on the first frame to fully transparent after the last frame.")]
|
||||
static readonly SpriteSequenceField<bool> AlphaFade = new SpriteSequenceField<bool>(nameof(AlphaFade), false);
|
||||
protected static readonly SpriteSequenceField<bool> AlphaFade = new SpriteSequenceField<bool>(nameof(AlphaFade), false);
|
||||
|
||||
[Desc("Name of the file containing the depth data sprite.")]
|
||||
static readonly SpriteSequenceField<string> DepthSprite = new SpriteSequenceField<string>(nameof(DepthSprite), null);
|
||||
protected static readonly SpriteSequenceField<string> DepthSprite = new SpriteSequenceField<string>(nameof(DepthSprite), null);
|
||||
|
||||
[Desc("Frame index containing the depth data.")]
|
||||
static readonly SpriteSequenceField<int> DepthSpriteFrame = new SpriteSequenceField<int>(nameof(DepthSpriteFrame), 0);
|
||||
protected static readonly SpriteSequenceField<int> DepthSpriteFrame = new SpriteSequenceField<int>(nameof(DepthSpriteFrame), 0);
|
||||
|
||||
[Desc("X, Y offset to apply to the depth sprite.")]
|
||||
static readonly SpriteSequenceField<float2> DepthSpriteOffset = new SpriteSequenceField<float2>(nameof(DepthSpriteOffset), float2.Zero);
|
||||
protected static readonly SpriteSequenceField<float2> DepthSpriteOffset = new SpriteSequenceField<float2>(nameof(DepthSpriteOffset), float2.Zero);
|
||||
|
||||
protected virtual string GetSpriteFilename(ModData modData, string tileset, string image, string sequence, MiniYaml data, MiniYaml defaults)
|
||||
protected static readonly MiniYaml NoData = new MiniYaml(null);
|
||||
protected readonly ISpriteSequenceLoader Loader;
|
||||
|
||||
protected string image;
|
||||
protected List<SpriteReservation> spritesToLoad = new List<SpriteReservation>();
|
||||
protected Sprite[] sprites;
|
||||
protected Sprite[] shadowSprites;
|
||||
protected bool reverseFacings;
|
||||
protected bool reverses;
|
||||
|
||||
protected int start;
|
||||
protected int shadowStart;
|
||||
protected int? length;
|
||||
protected int? stride;
|
||||
protected bool transpose;
|
||||
|
||||
protected int facings;
|
||||
protected int? interpolatedFacings;
|
||||
protected int tick;
|
||||
protected int zOffset;
|
||||
protected int shadowZOffset;
|
||||
protected bool ignoreWorldTint;
|
||||
protected float scale;
|
||||
protected float[] alpha;
|
||||
protected bool alphaFade;
|
||||
protected Rectangle? bounds;
|
||||
|
||||
protected int? depthSpriteReservation;
|
||||
protected float2 depthSpriteOffset;
|
||||
|
||||
protected void ThrowIfUnresolved()
|
||||
{
|
||||
return LoadField(Filename, data, defaults);
|
||||
if (bounds == null)
|
||||
throw new InvalidOperationException($"Unable to query unresolved sequence {image}.{Name}.");
|
||||
}
|
||||
|
||||
protected static T LoadField<T>(string key, T fallback, MiniYaml data, MiniYaml defaults)
|
||||
int ISpriteSequence.Length
|
||||
{
|
||||
var node = data.Nodes.FirstOrDefault(n => n.Key == key) ?? defaults.Nodes.FirstOrDefault(n => n.Key == key);
|
||||
get
|
||||
{
|
||||
ThrowIfUnresolved();
|
||||
return length.Value;
|
||||
}
|
||||
}
|
||||
|
||||
int ISpriteSequence.Facings => interpolatedFacings ?? facings;
|
||||
int ISpriteSequence.Tick => tick;
|
||||
int ISpriteSequence.ZOffset => zOffset;
|
||||
int ISpriteSequence.ShadowZOffset => shadowZOffset;
|
||||
bool ISpriteSequence.IgnoreWorldTint => ignoreWorldTint;
|
||||
float ISpriteSequence.Scale => GetScale();
|
||||
Rectangle ISpriteSequence.Bounds
|
||||
{
|
||||
get
|
||||
{
|
||||
ThrowIfUnresolved();
|
||||
return bounds.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
protected static T LoadField<T>(string key, T fallback, MiniYaml data, MiniYaml defaults = null)
|
||||
{
|
||||
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>(SpriteSequenceField<T> field, MiniYaml data, MiniYaml defaults)
|
||||
protected static T LoadField<T>(SpriteSequenceField<T> field, MiniYaml data, MiniYaml defaults = null)
|
||||
{
|
||||
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 LoadField(field, data, defaults, out _);
|
||||
}
|
||||
|
||||
protected static T LoadField<T>(SpriteSequenceField<T> field, MiniYaml data, MiniYaml defaults, out MiniYamlNode.SourceLocation location)
|
||||
{
|
||||
var node = data.Nodes.FirstOrDefault(n => n.Key == field.Key) ?? defaults?.Nodes.FirstOrDefault(n => n.Key == field.Key);
|
||||
if (node == null)
|
||||
{
|
||||
location = default;
|
||||
return field.DefaultValue;
|
||||
}
|
||||
|
||||
location = node.Location;
|
||||
return FieldLoader.GetValue<T>(field.Key, node.Value.Value);
|
||||
}
|
||||
|
||||
@@ -259,30 +294,92 @@ namespace OpenRA.Mods.Common.Graphics
|
||||
return Rectangle.FromLTRB(left, top, right, bottom);
|
||||
}
|
||||
|
||||
public DefaultSpriteSequence(ModData modData, string tileSet, SpriteCache cache, ISpriteSequenceLoader loader, string image, string sequence, MiniYaml data, MiniYaml defaults)
|
||||
protected static int[] CalculateFrameIndices(int start, int length, int stride, int facings, int[] frames, bool transpose, bool reverseFacings)
|
||||
{
|
||||
var usedFrames = new List<int>();
|
||||
for (var facing = 0; facing < facings; facing++)
|
||||
{
|
||||
var facingInner = reverseFacings ? (facings - facing) % facings : facing;
|
||||
for (var frame = 0; frame < length; frame++)
|
||||
{
|
||||
var i = transpose ? frame % length * facings + facingInner :
|
||||
facingInner * stride + frame % length;
|
||||
|
||||
usedFrames.Add(frames?[i] ?? start + i);
|
||||
}
|
||||
}
|
||||
|
||||
return usedFrames.ToArray();
|
||||
}
|
||||
|
||||
protected virtual IEnumerable<ReservationInfo> ParseFilenames(ModData modData, string tileset, int[] frames, MiniYaml data, MiniYaml defaults)
|
||||
{
|
||||
var filename = LoadField(Filename, data, defaults, out var location);
|
||||
|
||||
// Only request the subset of frames that we actually need.
|
||||
int[] loadFrames = null;
|
||||
if (length != null)
|
||||
{
|
||||
loadFrames = CalculateFrameIndices(start, length.Value, stride ?? length.Value, facings, frames, transpose, reverseFacings);
|
||||
if (shadowStart >= 0)
|
||||
loadFrames = loadFrames.Concat(loadFrames.Select(i => i + shadowStart - start)).ToArray();
|
||||
}
|
||||
|
||||
yield return new ReservationInfo(filename, loadFrames, frames, location);
|
||||
}
|
||||
|
||||
protected virtual IEnumerable<ReservationInfo> ParseCombineFilenames(ModData modData, string tileset, int[] frames, MiniYaml data)
|
||||
{
|
||||
var filename = LoadField(Filename, data, null, out var location);
|
||||
if (frames == null)
|
||||
{
|
||||
if (LoadField<string>(Length.Key, null, data) != "*")
|
||||
{
|
||||
var subStart = LoadField("Start", 0, data);
|
||||
var subLength = LoadField("Length", 1, data);
|
||||
frames = Exts.MakeArray(subLength, i => subStart + i);
|
||||
}
|
||||
}
|
||||
|
||||
yield return new ReservationInfo(filename, frames, frames, location);
|
||||
}
|
||||
|
||||
public DefaultSpriteSequence(SpriteCache cache, ISpriteSequenceLoader loader, string image, string sequence, MiniYaml data, MiniYaml defaults)
|
||||
{
|
||||
this.image = image;
|
||||
Name = sequence;
|
||||
Loader = loader;
|
||||
|
||||
start = LoadField(Start, data, defaults);
|
||||
|
||||
length = null;
|
||||
var lengthLocation = default(MiniYamlNode.SourceLocation);
|
||||
if (LoadField<string>(Length.Key, null, data, defaults) != "*")
|
||||
length = LoadField(Length, data, defaults, out lengthLocation);
|
||||
|
||||
stride = LoadField(Stride.Key, length, data, defaults);
|
||||
facings = LoadField(Facings, data, defaults, out var facingsLocation);
|
||||
interpolatedFacings = LoadField(InterpolatedFacings, data, defaults, out var interpolatedFacingsLocation);
|
||||
|
||||
tick = LoadField(Tick, data, defaults);
|
||||
zOffset = LoadField(ZOffset, data, defaults).Length;
|
||||
|
||||
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(FlipX, data, defaults);
|
||||
var flipY = LoadField(FlipY, data, defaults);
|
||||
var zRamp = LoadField(ZRamp, data, defaults);
|
||||
reverses = LoadField(Reverses, data, defaults);
|
||||
transpose = LoadField(Transpose, data, defaults);
|
||||
alpha = LoadField(Alpha, data, defaults);
|
||||
alphaFade = LoadField(AlphaFade, data, defaults, out var alphaFadeLocation);
|
||||
|
||||
facings = LoadField(Facings, data, defaults);
|
||||
interpolatedFacings = LoadField(InterpolatedFacings, data, defaults);
|
||||
if (interpolatedFacings != null && (interpolatedFacings <= 1 || interpolatedFacings <= Math.Abs(facings) || interpolatedFacings > 1024 || !Exts.IsPowerOf2(interpolatedFacings.Value)))
|
||||
throw new YamlException($"Sequence {image}.{sequence}: InterpolatedFacings must be greater than Facings, within the range of 2 to 1024, and a power of 2.");
|
||||
var depthSprite = LoadField(DepthSprite, data, defaults, out var depthSpriteLocation);
|
||||
if (!string.IsNullOrEmpty(depthSprite))
|
||||
depthSpriteReservation = cache.ReserveSprites(depthSprite, new[] { LoadField(DepthSpriteFrame, data, defaults) }, depthSpriteLocation);
|
||||
|
||||
depthSpriteOffset = LoadField(DepthSpriteOffset, data, defaults);
|
||||
|
||||
if (facings < 0)
|
||||
{
|
||||
@@ -290,216 +387,157 @@ namespace OpenRA.Mods.Common.Graphics
|
||||
facings = -facings;
|
||||
}
|
||||
|
||||
if (interpolatedFacings != null && (interpolatedFacings < 2 || interpolatedFacings <= facings || interpolatedFacings > 1024 || !Exts.IsPowerOf2(interpolatedFacings.Value)))
|
||||
throw new YamlException($"{interpolatedFacingsLocation}: {InterpolatedFacings.Key} must be greater than {Facings.Key}, within the range of 2 to 1024, and a power of 2.");
|
||||
|
||||
if (length != null && length <= 0)
|
||||
throw new YamlException($"{lengthLocation}: {Length.Key} must be positive.");
|
||||
|
||||
if (length == null && facings > 1)
|
||||
throw new YamlException($"{facingsLocation}: {Facings.Key} cannot be used with {Length.Key}: *.");
|
||||
|
||||
if (alphaFade && alpha != null)
|
||||
throw new YamlException($"{alphaFadeLocation}: {AlphaFade.Key} cannot be used with {Alpha.Key}.");
|
||||
}
|
||||
|
||||
public virtual void ReserveSprites(ModData modData, string tileset, SpriteCache cache, MiniYaml data, MiniYaml defaults)
|
||||
{
|
||||
var frames = LoadField(Frames, data, defaults);
|
||||
var flipX = LoadField(FlipX, data, defaults);
|
||||
var flipY = LoadField(FlipY, data, defaults);
|
||||
var zRamp = LoadField(ZRamp, data, defaults);
|
||||
var offset = LoadField(Offset, data, defaults);
|
||||
var blendMode = LoadField(BlendMode, data, defaults);
|
||||
|
||||
IEnumerable<int> GetUsedFrames(int frameCount)
|
||||
{
|
||||
if (LoadField(Length.Key, "", data, defaults) == "*")
|
||||
length = frames?.Length ?? frameCount - start;
|
||||
else
|
||||
length = LoadField(Length, data, defaults);
|
||||
|
||||
// Plays the animation forwards, and then in reverse
|
||||
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();
|
||||
length = 2 * length - 2;
|
||||
start = 0;
|
||||
}
|
||||
|
||||
// Overrides Length with a custom stride
|
||||
stride = LoadField(Stride.Key, length, data, defaults);
|
||||
|
||||
if (length > stride)
|
||||
throw new YamlException($"Sequence {image}.{sequence}: Length must be <= stride");
|
||||
|
||||
if (frames != null && length > 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 {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 {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 {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 {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++)
|
||||
{
|
||||
for (var frame = 0; frame < length; frame++)
|
||||
{
|
||||
var i = transpose ? frame % length * facings + facing :
|
||||
facing * stride + frame % length;
|
||||
|
||||
usedFrames.Add(frames != null ? frames[i] : start + i);
|
||||
}
|
||||
}
|
||||
|
||||
if (shadowStart >= 0)
|
||||
return usedFrames.Concat(usedFrames.Select(i => i + shadowStart - start));
|
||||
|
||||
return usedFrames;
|
||||
}
|
||||
|
||||
var combineNode = data.Nodes.FirstOrDefault(n => n.Key == Combine.Key);
|
||||
if (combineNode != null)
|
||||
{
|
||||
var combined = Enumerable.Empty<Sprite>();
|
||||
foreach (var combineSequenceNode in combineNode.Value.Nodes)
|
||||
for (var i = 0; i < combineNode.Value.Nodes.Count; i++)
|
||||
{
|
||||
var combineData = combineSequenceNode.Value;
|
||||
var subData = combineNode.Value.Nodes[i].Value;
|
||||
var subOffset = LoadField(Offset, subData, NoData);
|
||||
var subFlipX = LoadField(FlipX, subData, NoData);
|
||||
var subFlipY = LoadField(FlipY, subData, NoData);
|
||||
var subFrames = LoadField(Frames, data);
|
||||
|
||||
// Allow per-sprite offset, flipping, start, and length
|
||||
// These shouldn't inherit Start/Offset/etc from the main definition
|
||||
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;
|
||||
|
||||
IEnumerable<int> SubGetUsedFrames(int subFrameCount)
|
||||
foreach (var f in ParseCombineFilenames(modData, tileset, subFrames, subData))
|
||||
{
|
||||
var combineLengthNode = combineData.Nodes.FirstOrDefault(n => n.Key == Length.Key);
|
||||
if (combineLengthNode?.Value.Value == "*")
|
||||
subLength = subFrames?.Length ?? subFrameCount - subStart;
|
||||
else
|
||||
subLength = LoadField(Length, combineData, NoData);
|
||||
|
||||
return subFrames != null ? subFrames.Skip(subStart).Take(subLength) : Enumerable.Range(subStart, subLength);
|
||||
spritesToLoad.Add(new SpriteReservation
|
||||
{
|
||||
Token = cache.ReserveSprites(f.Filename, f.LoadFrames, f.Location),
|
||||
Offset = subOffset + offset,
|
||||
FlipX = subFlipX ^ flipX,
|
||||
FlipY = subFlipY ^ flipY,
|
||||
BlendMode = blendMode,
|
||||
ZRamp = zRamp,
|
||||
Frames = f.Frames
|
||||
});
|
||||
}
|
||||
|
||||
var subFilename = GetSpriteFilename(modData, tileSet, image, sequence, combineData, NoData);
|
||||
if (subFilename == null)
|
||||
throw new YamlException($"Sequence {image}.{sequence}.{combineSequenceNode.Key} does not define a filename.");
|
||||
|
||||
var subSprites = cache[subFilename, SubGetUsedFrames].Select(s =>
|
||||
{
|
||||
if (s == null)
|
||||
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;
|
||||
|
||||
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);
|
||||
combined = combined.Concat(frames.Select(i => subSprites[i]));
|
||||
}
|
||||
|
||||
sprites = combined.ToArray();
|
||||
GetUsedFrames(sprites.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Apply offset to each sprite in the sequence
|
||||
// Different sequences may apply different offsets to the same frame
|
||||
var filename = GetSpriteFilename(modData, tileSet, image, sequence, data, defaults);
|
||||
if (filename == null)
|
||||
throw new YamlException($"Sequence {image}.{sequence} does not define a filename.");
|
||||
foreach (var f in ParseFilenames(modData, tileset, frames, data, defaults))
|
||||
{
|
||||
spritesToLoad.Add(new SpriteReservation
|
||||
{
|
||||
Token = cache.ReserveSprites(f.Filename, f.LoadFrames, f.Location),
|
||||
Offset = offset,
|
||||
FlipX = flipX,
|
||||
FlipY = flipY,
|
||||
BlendMode = blendMode,
|
||||
ZRamp = zRamp,
|
||||
Frames = f.Frames,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sprites = cache[filename, GetUsedFrames].Select(s =>
|
||||
public virtual void ResolveSprites(SpriteCache cache)
|
||||
{
|
||||
if (bounds != null)
|
||||
return;
|
||||
|
||||
Sprite depthSprite = null;
|
||||
if (depthSpriteReservation != null)
|
||||
depthSprite = cache.ResolveSprites(depthSpriteReservation.Value).First(s => s != null);
|
||||
|
||||
var allSprites = spritesToLoad.SelectMany(r =>
|
||||
{
|
||||
var resolved = cache.ResolveSprites(r.Token);
|
||||
if (r.Frames != null)
|
||||
resolved = r.Frames.Select(f => resolved[f]).ToArray();
|
||||
|
||||
return resolved.Select(s =>
|
||||
{
|
||||
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 dx = r.Offset.X + (r.FlipX ? -s.Offset.X : s.Offset.X);
|
||||
var dy = r.Offset.Y + (r.FlipY ? -s.Offset.Y : s.Offset.Y);
|
||||
var dz = r.Offset.Z + s.Offset.Z + r.ZRamp * dy;
|
||||
var sprite = new Sprite(s.Sheet, FlipRectangle(s.Bounds, r.FlipX, r.FlipY), r.ZRamp, new float3(dx, dy, dz), s.Channel, r.BlendMode);
|
||||
if (depthSprite == null)
|
||||
return sprite;
|
||||
|
||||
return new Sprite(s.Sheet, bounds, zRamp, new float3(dx, dy, dz), s.Channel, blendMode);
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
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 {image}.{sequence} must define either 1 or {length} Alpha values.");
|
||||
}
|
||||
|
||||
if (LoadField(AlphaFade, data, defaults))
|
||||
{
|
||||
if (alpha != null)
|
||||
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(DepthSprite, data, defaults);
|
||||
if (!string.IsNullOrEmpty(depthSprite))
|
||||
{
|
||||
var depthSpriteFrame = LoadField(DepthSpriteFrame, data, defaults);
|
||||
var depthOffset = LoadField(DepthSpriteOffset, data, defaults);
|
||||
IEnumerable<int> GetDepthFrame(int _) => new[] { depthSpriteFrame };
|
||||
var ds = cache[depthSprite, GetDepthFrame][depthSpriteFrame];
|
||||
|
||||
sprites = sprites.Select(s =>
|
||||
{
|
||||
if (s == null)
|
||||
return null;
|
||||
|
||||
var cw = (ds.Bounds.Left + ds.Bounds.Right) / 2 + (int)(s.Offset.X + depthOffset.X);
|
||||
var ch = (ds.Bounds.Top + ds.Bounds.Bottom) / 2 + (int)(s.Offset.Y + depthOffset.Y);
|
||||
var cw = (depthSprite.Bounds.Left + depthSprite.Bounds.Right) / 2 + (int)(s.Offset.X + depthSpriteOffset.X);
|
||||
var ch = (depthSprite.Bounds.Top + depthSprite.Bounds.Bottom) / 2 + (int)(s.Offset.Y + depthSpriteOffset.Y);
|
||||
var w = s.Bounds.Width / 2;
|
||||
var h = s.Bounds.Height / 2;
|
||||
|
||||
var r = Rectangle.FromLTRB(cw - w, ch - h, cw + w, ch + h);
|
||||
return new SpriteWithSecondaryData(s, ds.Sheet, r, ds.Channel);
|
||||
return new SpriteWithSecondaryData(sprite, depthSprite.Sheet, Rectangle.FromLTRB(cw - w, ch - h, cw + w, ch + h), depthSprite.Channel);
|
||||
});
|
||||
}).ToArray();
|
||||
|
||||
length = length ?? allSprites.Length - start;
|
||||
|
||||
if (alpha != null)
|
||||
{
|
||||
if (alpha.Length == 1)
|
||||
alpha = Exts.MakeArray(length.Value, _ => alpha[0]);
|
||||
else if (alpha.Length != length.Value)
|
||||
throw new YamlException($"Sequence {image}.{Name} must define either 1 or {length.Value} Alpha values.");
|
||||
}
|
||||
else if (alphaFade)
|
||||
alpha = Exts.MakeArray(length.Value, i => float2.Lerp(1f, 0f, i / (length.Value - 1f)));
|
||||
|
||||
// Reindex sprites to order facings anti-clockwise and remove unused frames
|
||||
var index = CalculateFrameIndices(start, length.Value, stride ?? length.Value, facings, null, transpose, reverseFacings).ToList();
|
||||
if (reverses)
|
||||
{
|
||||
index.AddRange(index.Skip(1).Take(length.Value - 2).Reverse());
|
||||
length = 2 * length - 2;
|
||||
}
|
||||
|
||||
var boundSprites = SpriteBounds(sprites, frames, start, facings, length, stride, transpose);
|
||||
if (shadowStart > 0)
|
||||
boundSprites = boundSprites.Concat(SpriteBounds(sprites, frames, shadowStart, facings, length, stride, transpose));
|
||||
if (!index.Any())
|
||||
throw new YamlException($"Sequence {image}.{Name} does not define any frames.");
|
||||
|
||||
Bounds = boundSprites.Union();
|
||||
var minIndex = index.Min();
|
||||
var maxIndex = index.Max();
|
||||
if (minIndex < 0 || maxIndex >= allSprites.Length)
|
||||
throw new YamlException($"Sequence {image}.{Name} uses frames between {minIndex}..{maxIndex}, but only 0..{allSprites.Length - 1} exist.");
|
||||
|
||||
sprites = index.Select(f => allSprites[f]).ToArray();
|
||||
if (shadowStart >= 0)
|
||||
shadowSprites = index.Select(f => allSprites[f - start + shadowStart]).ToArray();
|
||||
|
||||
bounds = sprites.Concat(shadowSprites ?? Enumerable.Empty<Sprite>()).Select(OffsetSpriteBounds).Union();
|
||||
}
|
||||
|
||||
/// <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)
|
||||
protected static Rectangle OffsetSpriteBounds(Sprite sprite)
|
||||
{
|
||||
for (var facing = 0; facing < facings; facing++)
|
||||
{
|
||||
for (var frame = 0; frame < length; frame++)
|
||||
{
|
||||
var i = transpose ? frame % length * facings + facing :
|
||||
facing * stride + frame % length;
|
||||
var s = frames != null ? sprites[frames[i]] : sprites[start + i];
|
||||
if (!s.Bounds.IsEmpty)
|
||||
yield return new Rectangle(
|
||||
(int)(s.Offset.X - s.Size.X / 2),
|
||||
(int)(s.Offset.Y - s.Size.Y / 2),
|
||||
s.Bounds.Width, s.Bounds.Height);
|
||||
}
|
||||
}
|
||||
if (sprite == null || sprite.Bounds.IsEmpty)
|
||||
return Rectangle.Empty;
|
||||
|
||||
return new Rectangle(
|
||||
(int)(sprite.Offset.X - sprite.Size.X / 2),
|
||||
(int)(sprite.Offset.Y - sprite.Size.Y / 2),
|
||||
sprite.Bounds.Width, sprite.Bounds.Height);
|
||||
}
|
||||
|
||||
public Sprite GetSprite(int frame)
|
||||
{
|
||||
return GetSprite(start, frame, WAngle.Zero);
|
||||
}
|
||||
|
||||
public Sprite GetSprite(int frame, WAngle facing)
|
||||
{
|
||||
return GetSprite(start, frame, facing);
|
||||
return GetSprite(frame, WAngle.Zero);
|
||||
}
|
||||
|
||||
public (Sprite, WAngle) GetSpriteWithRotation(int frame, WAngle facing)
|
||||
@@ -508,28 +546,31 @@ namespace OpenRA.Mods.Common.Graphics
|
||||
if (interpolatedFacings != null)
|
||||
rotation = Util.GetInterpolatedFacingRotation(facing, Math.Abs(facings), interpolatedFacings.Value);
|
||||
|
||||
return (GetSprite(start, frame, facing), rotation);
|
||||
return (GetSprite(frame, facing), rotation);
|
||||
}
|
||||
|
||||
public Sprite GetShadow(int frame, WAngle facing)
|
||||
{
|
||||
return shadowStart >= 0 ? GetSprite(shadowStart, frame, facing) : null;
|
||||
if (shadowSprites == null)
|
||||
return null;
|
||||
|
||||
var index = GetFacingFrameOffset(facing) * length.Value + frame % length.Value;
|
||||
var sprite = shadowSprites[index];
|
||||
if (sprite == null)
|
||||
throw new InvalidOperationException($"Attempted to query unloaded shadow sprite from {image}.{Name} frame={frame} facing={facing}.");
|
||||
|
||||
return sprite;
|
||||
}
|
||||
|
||||
protected virtual Sprite GetSprite(int start, int frame, WAngle facing)
|
||||
public virtual Sprite GetSprite(int frame, WAngle facing)
|
||||
{
|
||||
var f = GetFacingFrameOffset(facing);
|
||||
if (reverseFacings)
|
||||
f = (facings - f) % facings;
|
||||
ThrowIfUnresolved();
|
||||
var index = GetFacingFrameOffset(facing) * length.Value + frame % length.Value;
|
||||
var sprite = sprites[index];
|
||||
if (sprite == null)
|
||||
throw new InvalidOperationException($"Attempted to query unloaded sprite from {image}.{Name} frame={frame} facing={facing}.");
|
||||
|
||||
var i = transpose ? frame % length * facings + f :
|
||||
f * stride + frame % length;
|
||||
|
||||
var j = frames != null ? frames[i] : start + i;
|
||||
if (sprites[j] == null)
|
||||
throw new InvalidOperationException($"Attempted to query unloaded sprite from {image}.{Name} start={start} frame={frame} facing={facing}");
|
||||
|
||||
return sprites[j];
|
||||
return sprite;
|
||||
}
|
||||
|
||||
protected virtual int GetFacingFrameOffset(WAngle facing)
|
||||
@@ -541,5 +582,10 @@ namespace OpenRA.Mods.Common.Graphics
|
||||
{
|
||||
return alpha?[frame] ?? 1f;
|
||||
}
|
||||
|
||||
protected virtual float GetScale()
|
||||
{
|
||||
return scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace OpenRA.Mods.Common.Graphics
|
||||
|
||||
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, image, sequence, data, defaults);
|
||||
return new TilesetSpecificSpriteSequence(cache, this, image, sequence, data, defaults);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,20 +32,56 @@ namespace OpenRA.Mods.Common.Graphics
|
||||
[Desc("Dictionary of <tileset name>: filename to override the Filename key.")]
|
||||
static readonly SpriteSequenceField<Dictionary<string, string>> TilesetFilenames = new SpriteSequenceField<Dictionary<string, string>>(nameof(TilesetFilenames), null);
|
||||
|
||||
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) { }
|
||||
public TilesetSpecificSpriteSequence(SpriteCache cache, ISpriteSequenceLoader loader, string image, string sequence, MiniYaml data, MiniYaml defaults)
|
||||
: base(cache, loader, image, sequence, data, defaults) { }
|
||||
|
||||
protected override string GetSpriteFilename(ModData modData, string tileset, string image, string sequence, MiniYaml data, MiniYaml defaults)
|
||||
protected override IEnumerable<ReservationInfo> ParseFilenames(ModData modData, string tileset, int[] frames, MiniYaml data, MiniYaml defaults)
|
||||
{
|
||||
var node = data.Nodes.FirstOrDefault(n => n.Key == TilesetFilenames.Key) ?? defaults.Nodes.FirstOrDefault(n => n.Key == TilesetFilenames.Key);
|
||||
if (node != null)
|
||||
{
|
||||
var tilesetNode = node.Value.Nodes.FirstOrDefault(n => n.Key == tileset);
|
||||
if (tilesetNode != null)
|
||||
return tilesetNode.Value.Value;
|
||||
{
|
||||
// Only request the subset of frames that we actually need
|
||||
int[] loadFrames = null;
|
||||
if (length != null)
|
||||
{
|
||||
loadFrames = CalculateFrameIndices(start, length.Value, stride ?? length.Value, facings, frames, transpose, reverseFacings);
|
||||
if (shadowStart >= 0)
|
||||
loadFrames = loadFrames.Concat(loadFrames.Select(i => i + shadowStart - start)).ToArray();
|
||||
}
|
||||
|
||||
return base.GetSpriteFilename(modData, tileset, image, sequence, data, defaults);
|
||||
return new[] { new ReservationInfo(tilesetNode.Value.Value, loadFrames, frames, tilesetNode.Location) };
|
||||
}
|
||||
}
|
||||
|
||||
return base.ParseFilenames(modData, tileset, frames, data, defaults);
|
||||
}
|
||||
|
||||
protected override IEnumerable<ReservationInfo> ParseCombineFilenames(ModData modData, string tileset, int[] frames, MiniYaml data)
|
||||
{
|
||||
var node = data.Nodes.FirstOrDefault(n => n.Key == TilesetFilenames.Key);
|
||||
if (node != null)
|
||||
{
|
||||
var tilesetNode = node.Value.Nodes.FirstOrDefault(n => n.Key == tileset);
|
||||
if (tilesetNode != null)
|
||||
{
|
||||
if (frames == null)
|
||||
{
|
||||
if (LoadField<string>("Length", null, data) != "*")
|
||||
{
|
||||
var subStart = LoadField("Start", 0, data);
|
||||
var subLength = LoadField("Length", 1, data);
|
||||
frames = Exts.MakeArray(subLength, i => subStart + i);
|
||||
}
|
||||
}
|
||||
|
||||
return new[] { new ReservationInfo(tilesetNode.Value.Value, frames, frames, tilesetNode.Location) };
|
||||
}
|
||||
}
|
||||
|
||||
return base.ParseCombineFilenames(modData, tileset, frames, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace OpenRA.Mods.Common.Lint
|
||||
{
|
||||
class CheckSequences : ILintSequencesPass
|
||||
{
|
||||
void ILintSequencesPass.Run(Action<string> emitError, Action<string> emitWarning, ModData modData, Ruleset rules, SequenceProvider sequences)
|
||||
void ILintSequencesPass.Run(Action<string> emitError, Action<string> emitWarning, ModData modData, Ruleset rules, SequenceSet sequences)
|
||||
{
|
||||
var factions = rules.Actors[SystemActors.World].TraitInfos<FactionInfo>().Select(f => f.InternalName).ToArray();
|
||||
foreach (var actorInfo in rules.Actors)
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[Desc("Defines sequence to derive facings from.")]
|
||||
public readonly string Sequence = "idle";
|
||||
|
||||
public int QuantizedBodyFacings(ActorInfo ai, SequenceProvider sequences, string faction)
|
||||
public int QuantizedBodyFacings(ActorInfo ai, SequenceSet sequences, string faction)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Sequence))
|
||||
throw new InvalidOperationException($"Actor {ai.Name} is missing sequence to quantize facings from.");
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
public interface IQuantizeBodyOrientationInfo : ITraitInfoInterface
|
||||
{
|
||||
int QuantizedBodyFacings(ActorInfo ai, SequenceProvider sequenceProvider, string race);
|
||||
int QuantizedBodyFacings(ActorInfo ai, SequenceSet sequences, string faction);
|
||||
}
|
||||
|
||||
public interface IPlaceBuildingDecorationInfo : ITraitInfoInterface
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using OpenRA.Mods.Common.Graphics;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.Terrain;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
|
||||
@@ -38,31 +38,23 @@ namespace OpenRA.Mods.Common.UtilityCommands
|
||||
// any tilesets from being checked further.
|
||||
try
|
||||
{
|
||||
// DefaultSequences is a dictionary of tileset: SequenceProvider
|
||||
// so we can also use this to key our tileset checks
|
||||
foreach (var kv in modData.DefaultSequences)
|
||||
foreach (var (tileset, terrainInfo) in modData.DefaultTerrainInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine("Tileset: " + kv.Key);
|
||||
var terrainInfo = modData.DefaultTerrainInfo[kv.Key];
|
||||
|
||||
Console.WriteLine("Tileset: " + tileset);
|
||||
if (terrainInfo is ITemplatedTerrainInfo templatedTerrainInfo)
|
||||
foreach (var r in modData.DefaultRules.Actors[SystemActors.World].TraitInfos<ITiledTerrainRendererInfo>())
|
||||
failed |= r.ValidateTileSprites(templatedTerrainInfo, Console.WriteLine);
|
||||
foreach (var ttr in modData.DefaultRules.Actors[SystemActors.World].TraitInfos<ITiledTerrainRendererInfo>())
|
||||
failed |= ttr.ValidateTileSprites(templatedTerrainInfo, Console.WriteLine);
|
||||
|
||||
foreach (var image in kv.Value.Images)
|
||||
var sequences = new SequenceSet(modData.DefaultFileSystem, modData, tileset, null);
|
||||
sequences.SpriteCache.LoadReservations(modData);
|
||||
foreach (var (filename, location) in sequences.SpriteCache.MissingFiles)
|
||||
{
|
||||
foreach (var sequence in kv.Value.Sequences(image))
|
||||
{
|
||||
if (!(kv.Value.GetSequence(image, sequence) is FileNotFoundSequence s))
|
||||
continue;
|
||||
|
||||
Console.WriteLine("\tSequence `{0}.{1}` references sprite `{2}` that does not exist.", image, sequence, s.Filename);
|
||||
Console.WriteLine($"\t{location}: {filename} not found");
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (YamlException e)
|
||||
{
|
||||
// The stacktrace associated with yaml errors are not very useful
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
|
||||
{
|
||||
Console.WriteLine($"Testing default sequences for {tileset}");
|
||||
|
||||
var sequences = new SequenceProvider(modData.DefaultFileSystem, modData, tileset, null);
|
||||
var sequences = new SequenceSet(modData.DefaultFileSystem, modData, tileset, null);
|
||||
CheckSequences(modData, modData.DefaultRules, sequences);
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
|
||||
}
|
||||
}
|
||||
|
||||
void CheckSequences(ModData modData, Ruleset rules, SequenceProvider sequences)
|
||||
void CheckSequences(ModData modData, Ruleset rules, SequenceSet sequences)
|
||||
{
|
||||
foreach (var customSequencesPassType in modData.ObjectCreator.GetTypesImplementing<ILintSequencesPass>())
|
||||
{
|
||||
|
||||
@@ -34,8 +34,10 @@ namespace OpenRA.Mods.Common.UtilityCommands
|
||||
|
||||
var palette = new ImmutablePalette(args[1], new[] { 0 }, Array.Empty<int>());
|
||||
|
||||
SequenceProvider sequences;
|
||||
if (!modData.DefaultSequences.TryGetValue(args[2], out sequences))
|
||||
SequenceSet sequences;
|
||||
if (modData.DefaultTerrainInfo.ContainsKey(args[2]))
|
||||
sequences = new SequenceSet(modData.ModFiles, modData, args[2], null);
|
||||
else
|
||||
{
|
||||
var mapPackage = new Folder(Platform.EngineDir).OpenPackage(args[2], modData.ModFiles);
|
||||
if (mapPackage == null)
|
||||
@@ -44,7 +46,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
|
||||
sequences = new Map(modData, mapPackage).Sequences;
|
||||
}
|
||||
|
||||
sequences.Preload();
|
||||
sequences.LoadSprites();
|
||||
|
||||
var count = 0;
|
||||
|
||||
|
||||
@@ -51,8 +51,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
|
||||
var relatedEnumTypes = new HashSet<Type>();
|
||||
|
||||
var sequenceTypesInfo = sequenceTypes
|
||||
.Where(x => !x.ContainsGenericParameters && !x.IsAbstract
|
||||
&& x.Name != nameof(FileNotFoundSequence)) // NOTE: This is the simplest way to exclude FileNotFoundSequence, which shouldn't be added.
|
||||
.Where(x => !x.ContainsGenericParameters && !x.IsAbstract)
|
||||
.Select(type => new
|
||||
{
|
||||
type.Namespace,
|
||||
|
||||
@@ -17,5 +17,5 @@ namespace OpenRA.Mods.Common.Lint
|
||||
public interface ILintPass { void Run(Action<string> emitError, Action<string> emitWarning, ModData modData); }
|
||||
public interface ILintMapPass { void Run(Action<string> emitError, Action<string> emitWarning, ModData modData, Map map); }
|
||||
public interface ILintRulesPass { void Run(Action<string> emitError, Action<string> emitWarning, ModData modData, Ruleset rules); }
|
||||
public interface ILintSequencesPass { void Run(Action<string> emitError, Action<string> emitWarning, ModData modData, Ruleset rules, SequenceProvider sequences); }
|
||||
public interface ILintSequencesPass { void Run(Action<string> emitError, Action<string> emitWarning, ModData modData, Ruleset rules, SequenceSet sequences); }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user