diff --git a/OpenRA.Game/Graphics/Theater.cs b/OpenRA.Game/Graphics/Theater.cs index e556466ab1..e282d5ee96 100644 --- a/OpenRA.Game/Graphics/Theater.cs +++ b/OpenRA.Game/Graphics/Theater.cs @@ -40,7 +40,7 @@ namespace OpenRA.Graphics readonly MersenneTwister random; TileSet tileset; - public Theater(TileSet tileset) + public Theater(TileSet tileset, Action onMissingImage = null) { this.tileset = tileset; var allocated = false; @@ -63,7 +63,22 @@ namespace OpenRA.Graphics foreach (var i in t.Value.Images) { - var allFrames = frameCache[i]; + ISpriteFrame[] allFrames; + if (onMissingImage != null) + { + try + { + allFrames = frameCache[i]; + } + catch (FileNotFoundException) + { + onMissingImage(t.Key, i); + continue; + } + } + else + allFrames = frameCache[i]; + var frameCount = tileset.EnableDepth ? allFrames.Length / 2 : allFrames.Length; var indices = t.Value.Frames != null ? t.Value.Frames : Enumerable.Range(0, frameCount); variants.Add(indices.Select(j => @@ -107,6 +122,9 @@ namespace OpenRA.Graphics if (tileset.IgnoreTileSpriteOffsets) allSprites = allSprites.Select(s => new Sprite(s.Sheet, s.Bounds, s.ZRamp, new float3(float2.Zero, s.Offset.Z), s.Channel, s.BlendMode)); + if (onMissingImage != null && !variants.Any()) + continue; + templates.Add(t.Value.Id, new TheaterTemplate(allSprites.ToArray(), variants.First().Count(), t.Value.Images.Length)); } @@ -116,6 +134,11 @@ namespace OpenRA.Graphics Sheet.ReleaseBuffer(); } + public bool HasTileSprite(TerrainTile r, int? variant = null) + { + return TileSprite(r, variant) != missingTile; + } + public Sprite TileSprite(TerrainTile r, int? variant = null) { if (!templates.TryGetValue(r.Type, out var template)) diff --git a/OpenRA.Mods.Common/UtilityCommands/CheckSequenceSprites.cs b/OpenRA.Mods.Common/UtilityCommands/CheckMissingSprites.cs similarity index 53% rename from OpenRA.Mods.Common/UtilityCommands/CheckSequenceSprites.cs rename to OpenRA.Mods.Common/UtilityCommands/CheckMissingSprites.cs index 5c0f903ccf..b8edd59348 100644 --- a/OpenRA.Mods.Common/UtilityCommands/CheckSequenceSprites.cs +++ b/OpenRA.Mods.Common/UtilityCommands/CheckMissingSprites.cs @@ -10,28 +10,49 @@ #endregion using System; +using System.Collections.Generic; +using OpenRA.Graphics; using OpenRA.Mods.Common.Graphics; namespace OpenRA.Mods.Common.UtilityCommands { - class CheckSequenceSprites : IUtilityCommand + class CheckMissingSprites : IUtilityCommand { - string IUtilityCommand.Name { get { return "--check-sequence-sprites"; } } + string IUtilityCommand.Name { get { return "--check-missing-sprites"; } } bool IUtilityCommand.ValidateArguments(string[] args) { return true; } - [Desc("Check the sequence definitions for missing sprite files.")] + [Desc("Check tileset and sequence definitions for missing sprite files.")] void IUtilityCommand.Run(Utility utility, string[] args) { // HACK: The engine code assumes that Game.modData is set. var modData = Game.ModData = utility.ModData; var failed = false; + + // DefaultSequences is a dictionary of tileset: SequenceProvider + // so we can also use this to key our tileset checks foreach (var kv in modData.DefaultSequences) { Console.WriteLine("Tileset: " + kv.Key); + var tileset = modData.DefaultTileSets[kv.Key]; + var missingImages = new HashSet(); + Action onMissingImage = (id, f) => + { + Console.WriteLine("\tTemplate `{0}` references sprite `{1}` that does not exist.", id, f); + missingImages.Add(f); + }; + + var theater = new Theater(tileset, onMissingImage); + foreach (var t in tileset.Templates) + for (var v = 0; v < t.Value.Images.Length; v++) + if (!missingImages.Contains(t.Value.Images[v])) + for (var i = 0; i < t.Value.TilesCount; i++) + if (t.Value[i] != null && !theater.HasTileSprite(new TerrainTile(t.Key, (byte)i), v)) + Console.WriteLine("\tTemplate `{0}` references frame {1} that does not exist in sprite `{2}`.", t.Key, i, t.Value.Images[v]); + foreach (var image in kv.Value.Images) { foreach (var sequence in kv.Value.Sequences(image))