diff --git a/OpenRA.Game/Graphics/SequenceProvider.cs b/OpenRA.Game/Graphics/SequenceProvider.cs index 2875c14f76..7d548cd48f 100644 --- a/OpenRA.Game/Graphics/SequenceProvider.cs +++ b/OpenRA.Game/Graphics/SequenceProvider.cs @@ -41,7 +41,6 @@ namespace OpenRA.Graphics public interface ISpriteSequenceLoader { - Action OnMissingSpriteError { get; set; } IReadOnlyDictionary ParseSequences(ModData modData, TileSet tileSet, SpriteCache cache, MiniYamlNode node); } @@ -79,6 +78,8 @@ namespace OpenRA.Graphics return seq; } + public IEnumerable Images { get { return sequences.Value.Keys; } } + public bool HasSequence(string unitName) { return sequences.Value.ContainsKey(unitName); @@ -104,10 +105,11 @@ namespace OpenRA.Graphics { var nodes = MiniYaml.Load(fileSystem, modData.Manifest.Sequences, additionalSequences); var items = new Dictionary(); - foreach (var n in nodes) + foreach (var node in nodes) { - // Work around the loop closure issue in older versions of C# - var node = n; + // 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("|"); diff --git a/OpenRA.Game/ModData.cs b/OpenRA.Game/ModData.cs index d777d2a362..3422dab38f 100644 --- a/OpenRA.Game/ModData.cs +++ b/OpenRA.Game/ModData.cs @@ -79,7 +79,6 @@ namespace OpenRA throw new InvalidOperationException("Unable to find a sequence loader for type '{0}'.".F(sequenceFormat.Type)); SpriteSequenceLoader = (ISpriteSequenceLoader)sequenceCtor.Invoke(new[] { this }); - SpriteSequenceLoader.OnMissingSpriteError = s => Log.Write("debug", s); var modelFormat = Manifest.Get(); var modelLoader = ObjectCreator.FindType(modelFormat.Type + "Loader"); diff --git a/OpenRA.Game/Traits/LintAttributes.cs b/OpenRA.Game/Traits/LintAttributes.cs index 307214f52c..70dfa4cfe5 100644 --- a/OpenRA.Game/Traits/LintAttributes.cs +++ b/OpenRA.Game/Traits/LintAttributes.cs @@ -31,12 +31,13 @@ namespace OpenRA.Traits { public readonly string ImageReference; // The field name in the same trait info that contains the image name. public readonly bool Prefix; - public readonly bool ActorNameFallback; - public SequenceReferenceAttribute(string imageReference = null, bool prefix = false, bool actorNameFallback = false) + public readonly bool AllowNullImage; + + public SequenceReferenceAttribute(string imageReference = null, bool prefix = false, bool allowNullImage = false) { ImageReference = imageReference; Prefix = prefix; - ActorNameFallback = actorNameFallback; + AllowNullImage = allowNullImage; } } diff --git a/OpenRA.Mods.Cnc/Traits/SupportPowers/ChronoshiftPower.cs b/OpenRA.Mods.Cnc/Traits/SupportPowers/ChronoshiftPower.cs index 70ac9e3348..9ed724cd96 100644 --- a/OpenRA.Mods.Cnc/Traits/SupportPowers/ChronoshiftPower.cs +++ b/OpenRA.Mods.Cnc/Traits/SupportPowers/ChronoshiftPower.cs @@ -37,7 +37,7 @@ namespace OpenRA.Mods.Cnc.Traits public readonly string FootprintImage = "overlay"; - [SequenceReference("FootprintImage", true)] + [SequenceReference("FootprintImage", prefix: true)] public readonly string ValidFootprintSequence = "target-valid"; [SequenceReference("FootprintImage")] diff --git a/OpenRA.Mods.Common/Graphics/DefaultSpriteSequence.cs b/OpenRA.Mods.Common/Graphics/DefaultSpriteSequence.cs index 362bb0bb6e..1f201be94d 100644 --- a/OpenRA.Mods.Common/Graphics/DefaultSpriteSequence.cs +++ b/OpenRA.Mods.Common/Graphics/DefaultSpriteSequence.cs @@ -40,7 +40,6 @@ namespace OpenRA.Mods.Common.Graphics public class DefaultSpriteSequenceLoader : ISpriteSequenceLoader { - public Action OnMissingSpriteError { get; set; } public DefaultSpriteSequenceLoader(ModData modData) { } public virtual ISpriteSequence CreateSequence(ModData modData, TileSet tileSet, SpriteCache cache, string sequence, string animation, MiniYaml info) @@ -80,8 +79,9 @@ namespace OpenRA.Mods.Common.Graphics } catch (FileNotFoundException ex) { - // Eat the FileNotFound exceptions from missing sprites - OnMissingSpriteError(ex.Message); + // Defer exception until something tries to access the sequence + // This allows the asset installer and OpenRA.Utility to load the game without having the actor assets + sequences.Add(kvp.Key, new FileNotFoundSequence(ex)); } } } @@ -90,6 +90,34 @@ namespace OpenRA.Mods.Common.Graphics } } + public class FileNotFoundSequence : ISpriteSequence + { + readonly FileNotFoundException exception; + + public FileNotFoundSequence(FileNotFoundException exception) + { + this.exception = exception; + } + + public string Filename { get { return exception.FileName; } } + + string ISpriteSequence.Name { get { throw exception; } } + int ISpriteSequence.Start { get { throw exception; } } + int ISpriteSequence.Length { get { throw exception; } } + int ISpriteSequence.Stride { get { throw exception; } } + int ISpriteSequence.Facings { get { throw exception; } } + int ISpriteSequence.Tick { get { throw exception; } } + int ISpriteSequence.ZOffset { get { throw exception; } } + int ISpriteSequence.ShadowStart { get { throw exception; } } + int ISpriteSequence.ShadowZOffset { get { throw exception; } } + int[] ISpriteSequence.Frames { get { throw exception; } } + Rectangle ISpriteSequence.Bounds { get { throw exception; } } + bool ISpriteSequence.IgnoreWorldTint { get { throw exception; } } + Sprite ISpriteSequence.GetSprite(int frame) { throw exception; } + Sprite ISpriteSequence.GetSprite(int frame, WAngle facing) { throw exception; } + Sprite ISpriteSequence.GetShadow(int frame, WAngle facing) { throw exception; } + } + public class DefaultSpriteSequence : ISpriteSequence { static readonly WDist DefaultShadowSpriteZOffset = new WDist(-5); diff --git a/OpenRA.Mods.Common/Lint/CheckSequences.cs b/OpenRA.Mods.Common/Lint/CheckSequences.cs index ab83086635..705d53f701 100644 --- a/OpenRA.Mods.Common/Lint/CheckSequences.cs +++ b/OpenRA.Mods.Common/Lint/CheckSequences.cs @@ -12,172 +12,136 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; +using OpenRA.Graphics; using OpenRA.Mods.Common.Traits.Render; using OpenRA.Traits; namespace OpenRA.Mods.Common.Lint { - class CheckSequences : ILintMapPass + class CheckSequences : ILintRulesPass { - Action emitError; - - List sequenceDefinitions; - - public void Run(Action emitError, Action emitWarning, ModData modData, Map map) + void ILintRulesPass.Run(Action emitError, Action emitWarning, ModData modData, Ruleset rules) { - if (map.SequenceDefinitions == null) - return; + // Custom maps define rules.Sequences, default mod rules leave it null. + if (rules.Sequences == null) + { + foreach (var kv in modData.DefaultSequences) + { + Console.WriteLine("Testing default sequences for {0}", kv.Key); + Run(emitError, emitWarning, rules, kv.Value); + } + } + else if (!modData.DefaultSequences.Values.Contains(rules.Sequences)) + Run(emitError, emitWarning, rules, rules.Sequences); + } - this.emitError = emitError; - - sequenceDefinitions = MiniYaml.Load(map, modData.Manifest.Sequences, map.SequenceDefinitions); - - var rules = map.Rules; + void Run(Action emitError, Action emitWarning, Ruleset rules, SequenceProvider sequences) + { var factions = rules.Actors["world"].TraitInfos().Select(f => f.InternalName).ToArray(); - var sequenceProviders = new[] { rules.Sequences }; - foreach (var actorInfo in rules.Actors) { - foreach (var renderInfo in actorInfo.Value.TraitInfos()) + // Actors may have 0 or 1 RenderSprites traits + var renderInfo = actorInfo.Value.TraitInfoOrDefault(); + if (renderInfo == null) + continue; + + var images = new HashSet() { - foreach (var faction in factions) - { - foreach (var sequenceProvider in sequenceProviders) - { - var image = renderInfo.GetImage(actorInfo.Value, sequenceProvider, faction); - if (sequenceDefinitions.All(s => s.Key != image.ToLowerInvariant())) - emitError("Sprite image {0} from actor {1} using faction {2} has no sequence definition." - .F(image, actorInfo.Value.Name, faction)); - } - } - } + renderInfo.GetImage(actorInfo.Value, sequences, null).ToLowerInvariant() + }; + + // Some actors define faction-specific artwork + foreach (var faction in factions) + images.Add(renderInfo.GetImage(actorInfo.Value, sequences, faction).ToLowerInvariant()); foreach (var traitInfo in actorInfo.Value.TraitInfos()) { + // Remove the "Info" suffix + var traitName = traitInfo.GetType().Name; + traitName = traitName.Remove(traitName.Length - 4); + var fields = traitInfo.GetType().GetFields(); foreach (var field in fields) { - if (field.HasAttribute()) + var sequenceReference = field.GetCustomAttributes(true).FirstOrDefault(); + if (sequenceReference == null) + continue; + + // Some sequences may specify their own Image override + IEnumerable sequenceImages = images; + if (!string.IsNullOrEmpty(sequenceReference.ImageReference)) { - var sequences = LintExts.GetFieldValues(traitInfo, field, emitError); - foreach (var sequence in sequences) + var imageField = fields.First(f => f.Name == sequenceReference.ImageReference); + var imageOverride = (string)imageField.GetValue(traitInfo); + if (string.IsNullOrEmpty(imageOverride)) { - if (string.IsNullOrEmpty(sequence)) - continue; - - var renderInfo = actorInfo.Value.TraitInfos().FirstOrDefault(); - if (renderInfo == null) - continue; - - foreach (var faction in factions) - { - var sequenceReference = field.GetCustomAttributes(true).FirstOrDefault(); - if (sequenceReference != null && !string.IsNullOrEmpty(sequenceReference.ImageReference)) - { - var imageField = fields.FirstOrDefault(f => f.Name == sequenceReference.ImageReference); - if (imageField != null) - { - foreach (var imageOverride in LintExts.GetFieldValues(traitInfo, imageField, emitError)) - { - if (string.IsNullOrEmpty(imageOverride)) - { - if (!sequenceReference.ActorNameFallback) - { - emitWarning("Custom sprite image of actor {0} is null and there is no fallback.".F(actorInfo.Value.Name)); - continue; - } - - foreach (var sequenceProvider in sequenceProviders) - { - var image = renderInfo.GetImage(actorInfo.Value, sequenceProvider, faction); - CheckDefinitions(image, sequenceReference, actorInfo, sequence, faction, field, traitInfo); - } - - continue; - } - - if (sequenceDefinitions.All(s => s.Key != imageOverride.ToLowerInvariant())) - emitError("Custom sprite image {0} from actor {1} has no sequence definition.".F(imageOverride, actorInfo.Value.Name)); - else - CheckDefinitions(imageOverride, sequenceReference, actorInfo, sequence, faction, field, traitInfo); - } - } - } - else - { - foreach (var sequenceProvider in sequenceProviders) - { - var image = renderInfo.GetImage(actorInfo.Value, sequenceProvider, faction); - CheckDefinitions(image, sequenceReference, actorInfo, sequence, faction, field, traitInfo); - } - } - } + if (!sequenceReference.AllowNullImage) + emitError("Actor type `{0}` trait `{1}` must define a value for `{2}`".F(actorInfo.Value.Name, traitName, sequenceReference.ImageReference)); + continue; } + + sequenceImages = new[] { imageOverride.ToLowerInvariant() }; } - } - } - foreach (var weaponInfo in rules.Weapons) - { - var projectileInfo = weaponInfo.Value.Projectile; - if (projectileInfo == null) - continue; - - var fields = projectileInfo.GetType().GetFields(); - foreach (var field in fields) - { - if (field.HasAttribute()) + foreach (var sequence in LintExts.GetFieldValues(traitInfo, field, emitError)) { - var sequences = LintExts.GetFieldValues(projectileInfo, field, emitError); - foreach (var sequence in sequences) - { - if (string.IsNullOrEmpty(sequence)) - continue; + if (string.IsNullOrEmpty(sequence)) + continue; - var sequenceReference = field.GetCustomAttributes(true).FirstOrDefault(); - if (sequenceReference != null && !string.IsNullOrEmpty(sequenceReference.ImageReference)) + foreach (var i in sequenceImages) + { + if (sequenceReference.Prefix) { - var imageField = fields.FirstOrDefault(f => f.Name == sequenceReference.ImageReference); - if (imageField != null) - { - foreach (var imageOverride in LintExts.GetFieldValues(projectileInfo, imageField, emitError)) - { - if (!string.IsNullOrEmpty(imageOverride)) - { - var definitions = sequenceDefinitions.FirstOrDefault(n => n.Key == imageOverride.ToLowerInvariant()); - if (definitions == null) - emitError("Can't find sequence definition for projectile image {0} at weapon {1}.".F(imageOverride, weaponInfo.Key)); - else if (!definitions.Value.Nodes.Any(n => n.Key == sequence)) - emitError("Projectile sprite image {0} from weapon {1} does not define sequence {2} from field {3} of {4}" - .F(imageOverride, weaponInfo.Key, sequence, field.Name, projectileInfo)); - } - } - } + // TODO: Remove prefixed sequence references and instead use explicit lists of lintable references + if (!sequences.Sequences(i).Any(s => s.StartsWith(sequence))) + emitWarning("Actor type `{0}` trait `{1}` field `{2}` defines a prefix `{3}` that does not match any sequences on image `{4}`.".F(actorInfo.Value.Name, traitName, field.Name, sequence, i)); } + else if (!sequences.HasSequence(i, sequence)) + emitError("Actor type `{0}` trait `{1}` field `{2}` references an undefined sequence `{3}` on image `{4}`.".F(actorInfo.Value.Name, traitName, field.Name, sequence, i)); } } } } } - } - void CheckDefinitions(string image, SequenceReferenceAttribute sequenceReference, - KeyValuePair actorInfo, string sequence, string faction, FieldInfo field, TraitInfo traitInfo) - { - var definitions = sequenceDefinitions.FirstOrDefault(n => n.Key == image.ToLowerInvariant()); - if (definitions != null) + foreach (var weaponInfo in rules.Weapons) { - if (sequenceReference != null && sequenceReference.Prefix) + var projectileInfo = weaponInfo.Value.Projectile; + if (projectileInfo == null) + continue; + + var fields = projectileInfo.GetType().GetFields(); + foreach (var field in fields) { - if (!definitions.Value.Nodes.Any(n => n.Key.StartsWith(sequence))) - emitError("Sprite image {0} from actor {1} of faction {2} does not define sequence prefix {3} from field {4} of {5}" - .F(image, actorInfo.Value.Name, faction, sequence, field.Name, traitInfo)); - } - else if (definitions.Value.Nodes.All(n => n.Key != sequence)) - { - emitError("Sprite image {0} from actor {1} of faction {2} does not define sequence {3} from field {4} of {5}" - .F(image, actorInfo.Value.Name, faction, sequence, field.Name, traitInfo)); + var sequenceReference = field.GetCustomAttributes(true).FirstOrDefault(); + if (sequenceReference == null) + continue; + + // All weapon sequences must specify their corresponding image + var image = ((string)fields.First(f => f.Name == sequenceReference.ImageReference).GetValue(projectileInfo)); + if (string.IsNullOrEmpty(image)) + { + if (!sequenceReference.AllowNullImage) + emitError("Weapon type `{0}` projectile field `{1}` must define a value".F(weaponInfo.Key, sequenceReference.ImageReference)); + + continue; + } + + image = image.ToLowerInvariant(); + foreach (var sequence in LintExts.GetFieldValues(projectileInfo, field, emitError)) + { + if (string.IsNullOrEmpty(sequence)) + continue; + + if (sequenceReference.Prefix) + { + // TODO: Remove prefixed sequence references and instead use explicit lists of lintable references + if (!sequences.Sequences(image).Any(s => s.StartsWith(sequence))) + emitWarning("Weapon type `{0}` projectile field `{1}` defines a prefix `{2}` that does not match any sequences on image `{3}`.".F(weaponInfo.Key, field.Name, sequence, image)); + } + else if (!sequences.HasSequence(image, sequence)) + emitError("Weapon type `{0}` projectile field `{1}` references an undefined sequence `{2}` on image `{3}`.".F(weaponInfo.Key, field.Name, sequence, image)); + } } } } diff --git a/OpenRA.Mods.Common/Projectiles/Bullet.cs b/OpenRA.Mods.Common/Projectiles/Bullet.cs index 5a98843375..f248d883ca 100644 --- a/OpenRA.Mods.Common/Projectiles/Bullet.cs +++ b/OpenRA.Mods.Common/Projectiles/Bullet.cs @@ -36,7 +36,7 @@ namespace OpenRA.Mods.Common.Projectiles [Desc("Image to display.")] public readonly string Image = null; - [SequenceReference("Image")] + [SequenceReference("Image", allowNullImage: true)] [Desc("Loop a randomly chosen sequence of Image from this list while this projectile is moving.")] public readonly string[] Sequences = { "idle" }; @@ -57,7 +57,7 @@ namespace OpenRA.Mods.Common.Projectiles [Desc("Trail animation.")] public readonly string TrailImage = null; - [SequenceReference("TrailImage")] + [SequenceReference("TrailImage", allowNullImage: true)] [Desc("Loop a randomly chosen sequence of TrailImage from this list while this projectile is moving.")] public readonly string[] TrailSequences = { "idle" }; diff --git a/OpenRA.Mods.Common/Projectiles/GravityBomb.cs b/OpenRA.Mods.Common/Projectiles/GravityBomb.cs index aec40d23ee..c8b362f5f7 100644 --- a/OpenRA.Mods.Common/Projectiles/GravityBomb.cs +++ b/OpenRA.Mods.Common/Projectiles/GravityBomb.cs @@ -20,11 +20,11 @@ namespace OpenRA.Mods.Common.Projectiles { public readonly string Image = null; - [SequenceReference("Image")] + [SequenceReference("Image", allowNullImage: true)] [Desc("Loop a randomly chosen sequence of Image from this list while falling.")] public readonly string[] Sequences = { "idle" }; - [SequenceReference("Image")] + [SequenceReference("Image", allowNullImage: true)] [Desc("Sequence to play when launched. Skipped if null or empty.")] public readonly string OpenSequence = null; diff --git a/OpenRA.Mods.Common/Projectiles/LaserZap.cs b/OpenRA.Mods.Common/Projectiles/LaserZap.cs index 5ae71d2426..d6bb100332 100644 --- a/OpenRA.Mods.Common/Projectiles/LaserZap.cs +++ b/OpenRA.Mods.Common/Projectiles/LaserZap.cs @@ -78,7 +78,7 @@ namespace OpenRA.Mods.Common.Projectiles [Desc("Impact animation.")] public readonly string HitAnim = null; - [SequenceReference("HitAnim")] + [SequenceReference("HitAnim", allowNullImage: true)] [Desc("Sequence of impact animation to use.")] public readonly string HitAnimSequence = "idle"; @@ -88,7 +88,7 @@ namespace OpenRA.Mods.Common.Projectiles [Desc("Image containing launch effect sequence.")] public readonly string LaunchEffectImage = null; - [SequenceReference("LaunchEffectImage")] + [SequenceReference("LaunchEffectImage", allowNullImage: true)] [Desc("Launch effect sequence to play.")] public readonly string LaunchEffectSequence = null; diff --git a/OpenRA.Mods.Common/Projectiles/Missile.cs b/OpenRA.Mods.Common/Projectiles/Missile.cs index d9f1274cc5..e3ec9285e8 100644 --- a/OpenRA.Mods.Common/Projectiles/Missile.cs +++ b/OpenRA.Mods.Common/Projectiles/Missile.cs @@ -26,7 +26,7 @@ namespace OpenRA.Mods.Common.Projectiles [Desc("Name of the image containing the projectile sequence.")] public readonly string Image = null; - [SequenceReference("Image")] + [SequenceReference("Image", allowNullImage: true)] [Desc("Loop a randomly chosen sequence of Image from this list while this projectile is moving.")] public readonly string[] Sequences = { "idle" }; @@ -109,7 +109,7 @@ namespace OpenRA.Mods.Common.Projectiles [Desc("Image that contains the trail animation.")] public readonly string TrailImage = null; - [SequenceReference("TrailImage")] + [SequenceReference("TrailImage", allowNullImage: true)] [Desc("Loop a randomly chosen sequence of TrailImage from this list while this projectile is moving.")] public readonly string[] TrailSequences = { "idle" }; diff --git a/OpenRA.Mods.Common/Projectiles/Railgun.cs b/OpenRA.Mods.Common/Projectiles/Railgun.cs index 27a3c69d06..e5db4cb73d 100644 --- a/OpenRA.Mods.Common/Projectiles/Railgun.cs +++ b/OpenRA.Mods.Common/Projectiles/Railgun.cs @@ -87,7 +87,7 @@ namespace OpenRA.Mods.Common.Projectiles public readonly string HitAnim = null; [Desc("Sequence of impact animation to use.")] - [SequenceReference("HitAnim")] + [SequenceReference("HitAnim", allowNullImage: true)] public readonly string HitAnimSequence = "idle"; [PaletteReference] diff --git a/OpenRA.Mods.Common/Traits/Buildings/RallyPoint.cs b/OpenRA.Mods.Common/Traits/Buildings/RallyPoint.cs index 557210f327..5613c570e5 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/RallyPoint.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/RallyPoint.cs @@ -24,10 +24,10 @@ namespace OpenRA.Mods.Common.Traits [Desc("Width (in pixels) of the rallypoint line.")] public readonly int LineWidth = 1; - [SequenceReference("Image")] + [SequenceReference("Image", allowNullImage: true)] public readonly string FlagSequence = "flag"; - [SequenceReference("Image")] + [SequenceReference("Image", allowNullImage: true)] public readonly string CirclesSequence = "circles"; [Desc("Cursor to display when rally point can be set.")] diff --git a/OpenRA.Mods.Common/Traits/Crates/CrateAction.cs b/OpenRA.Mods.Common/Traits/Crates/CrateAction.cs index 620c0706be..6d1581bff2 100644 --- a/OpenRA.Mods.Common/Traits/Crates/CrateAction.cs +++ b/OpenRA.Mods.Common/Traits/Crates/CrateAction.cs @@ -23,7 +23,7 @@ namespace OpenRA.Mods.Common.Traits [Desc("Image containing the crate effect animation sequence.")] public readonly string Image = "crate-effects"; - [SequenceReference("Image")] + [SequenceReference("Image", allowNullImage: true)] [Desc("Animation sequence played when collected. Leave empty for no effect.")] public readonly string Sequence = null; diff --git a/OpenRA.Mods.Common/Traits/GainsExperience.cs b/OpenRA.Mods.Common/Traits/GainsExperience.cs index 149a09a8fb..875d862eb5 100644 --- a/OpenRA.Mods.Common/Traits/GainsExperience.cs +++ b/OpenRA.Mods.Common/Traits/GainsExperience.cs @@ -32,7 +32,7 @@ namespace OpenRA.Mods.Common.Traits [Desc("Image for the level up sprite.")] public readonly string LevelUpImage = null; - [SequenceReference("LevelUpImage")] + [SequenceReference("LevelUpImage", allowNullImage: true)] [Desc("Sequence for the level up sprite. Needs to be present on LevelUpImage.")] public readonly string LevelUpSequence = "levelup"; diff --git a/OpenRA.Mods.Common/Traits/Parachutable.cs b/OpenRA.Mods.Common/Traits/Parachutable.cs index 2a279bec30..cbc43a8f82 100644 --- a/OpenRA.Mods.Common/Traits/Parachutable.cs +++ b/OpenRA.Mods.Common/Traits/Parachutable.cs @@ -29,7 +29,7 @@ namespace OpenRA.Mods.Common.Traits [Desc("Image where Ground/WaterCorpseSequence is looked up.")] public readonly string Image = "explosion"; - [SequenceReference("Image")] + [SequenceReference("Image", allowNullImage: true)] public readonly string GroundCorpseSequence = null; [PaletteReference] @@ -37,7 +37,7 @@ namespace OpenRA.Mods.Common.Traits public readonly string GroundImpactSound = null; - [SequenceReference("Image")] + [SequenceReference("Image", allowNullImage: true)] public readonly string WaterCorpseSequence = null; [PaletteReference] diff --git a/OpenRA.Mods.Common/Traits/Render/WithDecoration.cs b/OpenRA.Mods.Common/Traits/Render/WithDecoration.cs index 1db7890d3f..80c04e8629 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithDecoration.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithDecoration.cs @@ -23,7 +23,7 @@ namespace OpenRA.Mods.Common.Traits.Render public readonly string Image = null; [FieldLoader.Require] - [SequenceReference("Image", false, true)] + [SequenceReference("Image", allowNullImage: true)] [Desc("Sequence used for this decoration (can be animated).")] public readonly string Sequence = null; diff --git a/OpenRA.Mods.Common/Traits/Render/WithIdleOverlay.cs b/OpenRA.Mods.Common/Traits/Render/WithIdleOverlay.cs index 1f44392d30..174a703e02 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithIdleOverlay.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithIdleOverlay.cs @@ -23,11 +23,11 @@ namespace OpenRA.Mods.Common.Traits.Render [Desc("Image used for this decoration. Defaults to the actor's type.")] public readonly string Image = null; - [SequenceReference("Image", false, true)] + [SequenceReference("Image", allowNullImage: true)] [Desc("Animation to play when the actor is created.")] public readonly string StartSequence = null; - [SequenceReference("Image", false, true)] + [SequenceReference("Image", allowNullImage: true)] [Desc("Sequence name to use")] public readonly string Sequence = "idle-overlay"; diff --git a/OpenRA.Mods.Common/Traits/Render/WithParachute.cs b/OpenRA.Mods.Common/Traits/Render/WithParachute.cs index fc64dbaa3c..50f61e476a 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithParachute.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithParachute.cs @@ -25,15 +25,15 @@ namespace OpenRA.Mods.Common.Traits.Render [Desc("The image that contains the parachute sequences.")] public readonly string Image = null; - [SequenceReference("Image")] + [SequenceReference("Image", allowNullImage: true)] [Desc("Parachute opening sequence.")] public readonly string OpeningSequence = null; - [SequenceReference("Image")] + [SequenceReference("Image", allowNullImage: true)] [Desc("Parachute idle sequence.")] public readonly string Sequence = null; - [SequenceReference("Image")] + [SequenceReference("Image", allowNullImage: true)] [Desc("Parachute closing sequence. Defaults to opening sequence played backwards.")] public readonly string ClosingSequence = null; @@ -49,7 +49,7 @@ namespace OpenRA.Mods.Common.Traits.Render [Desc("The image that contains the shadow sequence for the paradropped unit.")] public readonly string ShadowImage = null; - [SequenceReference("ShadowImage")] + [SequenceReference("ShadowImage", allowNullImage: true)] [Desc("Paradropped unit's shadow sequence.")] public readonly string ShadowSequence = null; diff --git a/OpenRA.Mods.Common/Traits/SupportPowers/NukePower.cs b/OpenRA.Mods.Common/Traits/SupportPowers/NukePower.cs index 0c67fd5b28..9b72954bd0 100644 --- a/OpenRA.Mods.Common/Traits/SupportPowers/NukePower.cs +++ b/OpenRA.Mods.Common/Traits/SupportPowers/NukePower.cs @@ -55,7 +55,7 @@ namespace OpenRA.Mods.Common.Traits [Desc("Trail animation.")] public readonly string TrailImage = null; - [SequenceReference("TrailImage")] + [SequenceReference("TrailImage", allowNullImage: true)] [Desc("Loop a randomly chosen sequence of TrailImage from this list while this projectile is moving.")] public readonly string[] TrailSequences = { }; diff --git a/OpenRA.Mods.Common/UtilityCommands/CheckSequenceSprites.cs b/OpenRA.Mods.Common/UtilityCommands/CheckSequenceSprites.cs index a382cb9880..5c0f903ccf 100644 --- a/OpenRA.Mods.Common/UtilityCommands/CheckSequenceSprites.cs +++ b/OpenRA.Mods.Common/UtilityCommands/CheckSequenceSprites.cs @@ -10,12 +10,11 @@ #endregion using System; -using System.Linq; -using OpenRA.Graphics; +using OpenRA.Mods.Common.Graphics; namespace OpenRA.Mods.Common.UtilityCommands { - class CheckSquenceSprites : IUtilityCommand + class CheckSequenceSprites : IUtilityCommand { string IUtilityCommand.Name { get { return "--check-sequence-sprites"; } } @@ -29,18 +28,22 @@ namespace OpenRA.Mods.Common.UtilityCommands { // HACK: The engine code assumes that Game.modData is set. var modData = Game.ModData = utility.ModData; - var failed = false; - modData.SpriteSequenceLoader.OnMissingSpriteError = s => { Console.WriteLine("\t" + s); failed = true; }; - - foreach (var t in modData.Manifest.TileSets) + foreach (var kv in modData.DefaultSequences) { - var ts = new TileSet(modData.DefaultFileSystem, t); - Console.WriteLine("Tileset: " + ts.Name); - var sc = new SpriteCache(modData.DefaultFileSystem, modData.SpriteLoaders); - var nodes = MiniYaml.Merge(modData.Manifest.Sequences.Select(s => MiniYaml.FromStream(modData.DefaultFileSystem.Open(s), s))); - foreach (var n in nodes.Where(node => !node.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal))) - modData.SpriteSequenceLoader.ParseSequences(modData, ts, sc, n); + Console.WriteLine("Tileset: " + kv.Key); + foreach (var image in kv.Value.Images) + { + foreach (var sequence in kv.Value.Sequences(image)) + { + var s = kv.Value.GetSequence(image, sequence) as FileNotFoundSequence; + if (s != null) + { + Console.WriteLine("\tSequence `{0}.{1}` references sprite `{2}` that does not exist.", image, sequence, s.Filename); + failed = true; + } + } + } } if (failed) diff --git a/OpenRA.Mods.Common/Warheads/CreateEffectWarhead.cs b/OpenRA.Mods.Common/Warheads/CreateEffectWarhead.cs index 9043d8d168..0b49e5a002 100644 --- a/OpenRA.Mods.Common/Warheads/CreateEffectWarhead.cs +++ b/OpenRA.Mods.Common/Warheads/CreateEffectWarhead.cs @@ -20,7 +20,7 @@ namespace OpenRA.Mods.Common.Warheads { public class CreateEffectWarhead : Warhead { - [SequenceReference("Image")] + [SequenceReference("Image", allowNullImage: true)] [Desc("List of explosion sequences that can be used.")] public readonly string[] Explosions = new string[0];