public void ChildCanBeOverriddenThenRemoved() { var baseYaml = @" ^BaseA: MockString: AString: Base ^BaseB: Inherits: ^BaseA MockString: AString: Override "; var overrideYaml = @" Test: Inherits: ^BaseB MockString: -AString: "; var result = MiniYaml.Merge(new[] { baseYaml, overrideYaml }.Select(s => MiniYaml.FromString(s, ""))) .First(n => n.Key == "Test").Value.Nodes; Assert.IsTrue(result.Any(n => n.Key == "MockString"), "Node should have the MockString child, but does not."); Assert.IsFalse(result.First(n => n.Key == "MockString").Value.Nodes.Any(n => n.Key == "AString"), "MockString value should have been removed, but was not."); }
public void InheritanceAndRemovalCanBeComposed() { var baseYaml = @" ^BaseA: MockA2: ^BaseB: Inherits@a: ^BaseA MockB2: "; var extendedYaml = @" Test: Inherits@b: ^BaseB -MockA2: "; var mapYaml = @" ^BaseC: MockC2: Test: Inherits@c: ^BaseC "; var result = MiniYaml.Merge(new[] { baseYaml, extendedYaml, mapYaml }.Select(s => MiniYaml.FromString(s, ""))) .First(n => n.Key == "Test").Value.Nodes; Assert.IsFalse(result.Any(n => n.Key == "MockA2"), "Node should not have the MockA2 child, but does."); Assert.IsTrue(result.Any(n => n.Key == "MockB2"), "Node should have the MockB2 child, but does not."); Assert.IsTrue(result.Any(n => n.Key == "MockC2"), "Node should have the MockC2 child, but does not."); }
public void TestSelfMerging() { var baseYaml = @" Test: Merge: original Child: original Original: Test: Merge: override Child: override Override: "; var result = MiniYaml.Merge(new[] { baseYaml }.Select(s => MiniYaml.FromString(s, ""))); Assert.That(result.Count(n => n.Key == "Test"), Is.EqualTo(1), "Result should have exactly one Test node."); var testNodes = result.First(n => n.Key == "Test").Value.Nodes; Assert.That(testNodes.Select(n => n.Key), Is.EqualTo(new[] { "Merge", "Original", "Override" }), "Merged Test node has incorrect child nodes."); var mergeNode = testNodes.First(n => n.Key == "Merge").Value; Assert.That(mergeNode.Value, Is.EqualTo("override"), "Merge node has incorrect value."); Assert.That(mergeNode.Nodes[0].Value.Value, Is.EqualTo("override"), "Merge node Child value should be 'override', but is not"); }
public WeaponInfo(string name, MiniYaml content) { // Resolve any weapon-level yaml inheritance or removals // HACK: The "Defaults" sequence syntax prevents us from doing this generally during yaml parsing content.Nodes = MiniYaml.Merge(new[] { content.Nodes }); FieldLoader.Load(this, content); }
public void Run(ModData modData, string[] args) { // HACK: The engine code assumes that Game.modData is set. Game.ModData = modData; var failed = false; modData.SpriteSequenceLoader.OnMissingSpriteError = s => { Console.WriteLine("\t" + s); failed = true; }; foreach (var t in modData.Manifest.TileSets) { var ts = new TileSet(modData.DefaultFileSystem, t); Console.WriteLine("Tileset: " + ts.Name); var sc = new SpriteCache(modData.DefaultFileSystem, modData.SpriteLoaders, new SheetBuilder(SheetType.Indexed)); var nodes = MiniYaml.Merge(modData.Manifest.Sequences.Select(s => MiniYaml.FromStream(modData.DefaultFileSystem.Open(s), s))); foreach (var n in nodes) { modData.SpriteSequenceLoader.ParseSequences(modData, ts, sc, n); } } if (failed) { Environment.Exit(1); } }
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; modData.SpriteSequenceLoader.OnMissingSpriteError = s => { Console.WriteLine("\t" + s); failed = true; }; foreach (var t in modData.Manifest.TileSets) { 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); } } if (failed) { Environment.Exit(1); } }
public static void Initialize(ModData modData) { if (Initialized) { return; } Deinitialize(); fileSystem = modData.DefaultFileSystem; collections = new Dictionary <string, Collection>(); cachedSheets = new Dictionary <string, Sheet>(); cachedSheets2d = new Dictionary <string, SheetCache>(); cachedSprites = new Dictionary <string, Dictionary <string, Sprite> >(); var chrome = MiniYaml.Merge(modData.Manifest.Chrome .Select(s => MiniYaml.FromStream(fileSystem.Open(s), s))); foreach (var c in chrome) { LoadCollection(c.Key, c.Value); } Initialized = true; }
public IReadOnlyDictionary <string, ISpriteSequence> ParseSequences(ModData modData, TileSet tileSet, SpriteCache cache, MiniYamlNode node) { var sequences = new Dictionary <string, ISpriteSequence>(); var nodes = node.Value.ToDictionary(); MiniYaml defaults; if (nodes.TryGetValue("Defaults", out defaults)) { nodes.Remove("Defaults"); foreach (var n in nodes) { n.Value.Nodes = MiniYaml.Merge(new[] { defaults.Nodes, n.Value.Nodes }); n.Value.Value = n.Value.Value ?? defaults.Value; } } foreach (var kvp in nodes) { using (new Support.PerfTimer("new Sequence(\"{0}\")".F(node.Key), 20)) { try { sequences.Add(kvp.Key, CreateSequence(modData, tileSet, cache, node.Key, kvp.Key, kvp.Value)); } catch (FileNotFoundException ex) { // Eat the FileNotFound exceptions from missing sprites OnMissingSpriteError(ex.Message); } } } return(new ReadOnlyDictionary <string, ISpriteSequence>(sequences)); }
public static void Initialize(ModData modData) { Deinitialize(); // Load higher resolution images if available on HiDPI displays if (Game.Renderer != null) { dpiScale = Game.Renderer.WindowScale; } fileSystem = modData.DefaultFileSystem; collections = new Dictionary <string, Collection>(); cachedSheets = new Dictionary <string, Pair <Sheet, int> >(); cachedSprites = new Dictionary <string, Dictionary <string, Sprite> >(); cachedPanelSprites = new Dictionary <string, Sprite[]>(); cachedCollectionSheets = new Dictionary <Collection, Pair <Sheet, int> >(); Collections = new ReadOnlyDictionary <string, Collection>(collections); var chrome = MiniYaml.Merge(modData.Manifest.Chrome .Select(s => MiniYaml.FromStream(fileSystem.Open(s), s))); foreach (var c in chrome) { if (!c.Key.StartsWith("^", StringComparison.Ordinal)) { LoadCollection(c.Key, c.Value); } } }
public void MergeYamlA() { var a = MiniYaml.FromString(mixedMergeA, "mixedMergeA"); var b = MiniYaml.FromString(mixedMergeB, "mixedMergeB"); // Merge order should not matter // Note: All the Merge* variants are different plumbing over the same // Internal logic. Testing only Merge is sufficient. TestMixedMerge(MiniYaml.Merge(a, b).First().Value); TestMixedMerge(MiniYaml.Merge(b, a).First().Value); }
public static void Initialize(ModData modData) { data = new Dictionary <string, string>(); var metrics = MiniYaml.Merge(modData.Manifest.ChromeMetrics.Select( y => MiniYaml.FromStream(modData.DefaultFileSystem.Open(y), y))); foreach (var m in metrics) { foreach (var n in m.Value.Nodes) { data[n.Key] = n.Value.Value; } } }
public IReadOnlyDictionary <string, ISpriteSequence> ParseSequences(ModData modData, TileSet tileSet, SpriteCache cache, MiniYamlNode node) { var sequences = new Dictionary <string, ISpriteSequence>(); var nodes = node.Value.ToDictionary(); MiniYaml defaults; if (nodes.TryGetValue("Defaults", out defaults)) { nodes.Remove("Defaults"); nodes = nodes.ToDictionary(kv => kv.Key, kv => MiniYaml.Merge(kv.Value, defaults)); // Merge 'Defaults' animation image value. An example follows. // // - Before - // stand: // Facings: 8 // // - After - // stand: e1 // Facings: 8 foreach (var n in nodes) { n.Value.Value = n.Value.Value ?? defaults.Value; } } foreach (var kvp in nodes) { using (new Support.PerfTimer("new Sequence(\"{0}\")".F(node.Key), 20)) { try { sequences.Add(kvp.Key, CreateSequence(modData, tileSet, cache, node.Key, kvp.Key, kvp.Value)); } catch (FileNotFoundException ex) { // Eat the FileNotFound exceptions from missing sprites OnMissingSpriteError(ex.Message); } } } return(new ReadOnlyDictionary <string, ISpriteSequence>(sequences)); }
void Run(Action <string> emitError, ModData modData, Ruleset rules) { var fileSystem = modData.DefaultFileSystem; var sequenceYaml = MiniYaml.Merge(modData.Manifest.Cursors.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s))); var nodesDict = new MiniYaml(null, sequenceYaml).ToDictionary(); // Avoid using CursorProvider as it attempts to load palettes from the file system. var cursors = new List <string>(); foreach (var s in nodesDict["Cursors"].Nodes) { foreach (var sequence in s.Value.Nodes) { cursors.Add(sequence.Key); } } foreach (var actorInfo in rules.Actors) { foreach (var traitInfo in actorInfo.Value.TraitInfos <TraitInfo>()) { var fields = traitInfo.GetType().GetFields(); foreach (var field in fields) { var cursorReference = field.GetCustomAttributes <CursorReferenceAttribute>(true).FirstOrDefault(); if (cursorReference == null) { continue; } var cursor = LintExts.GetFieldValues(traitInfo, field, emitError, cursorReference.DictionaryReference).FirstOrDefault(); if (string.IsNullOrEmpty(cursor)) { continue; } if (!cursors.Contains(cursor)) { emitError("Undefined cursor {0} for actor {1} with trait {2}.".F(cursor, actorInfo.Value.Name, traitInfo)); } } } } }
public void ChildCanBeRemovedAfterMultipleInheritance() { var baseYaml = @" ^BaseA: MockA2: Test: Inherits: ^BaseA MockA2: "; var overrideYaml = @" Test: -MockA2 "; var result = MiniYaml.Merge(new[] { baseYaml, overrideYaml }.Select(s => MiniYaml.FromString(s, ""))) .First(n => n.Key == "Test").Value.Nodes; Assert.IsFalse(result.Any(n => n.Key == "MockA2"), "Node should not have the MockA2 child, but does."); }
/// <summary> /// 解析序列 /// </summary> /// <param name="modData"></param> /// <param name="tileSet"></param> /// <param name="cache"></param> /// <param name=""></param> /// <returns></returns> public IReadOnlyDictionary <string, ISpriteSequence> ParseSequences(ModData modData, TileSet tileSet, SpriteCache cache, MiniYamlNode node) { var sequences = new Dictionary <string, ISpriteSequence>(); var nodes = node.Value.ToDictionary(); MiniYaml defaults; try { if (nodes.TryGetValue("Defaults", out defaults)) { nodes.Remove("Defaults"); foreach (var n in nodes) { n.Value.Nodes = MiniYaml.Merge(new[] { defaults.Nodes, n.Value.Nodes }); n.Value.Value = n.Value.Value ?? defaults.Value; } } } catch (Exception e) { throw new InvalidDataException("Error occurred while parsing {0} ".F(node.Key), e); } foreach (var kvp in nodes) { using (new Support.PerfTimer("new Sequence(\"{0}\")".F(node.Key), 20)) { try { //Console.WriteLine("Parse sequence:node key:" + node.Key + " animation:" + kvp.Key + " count:"+kvp.Value.Nodes.Count); sequences.Add(kvp.Key, CreateSequence(modData, tileSet, cache, node.Key, kvp.Key, kvp.Value)); } catch (FileNotFoundException ex) { OnMissingSpriteError(ex.Message); } } } return(new ReadOnlyDictionary <string, ISpriteSequence>(sequences)); }
public IReadOnlyDictionary <string, ISpriteSequence> ParseSequences(ModData modData, string tileSet, SpriteCache cache, MiniYamlNode node) { var sequences = new Dictionary <string, ISpriteSequence>(); var nodes = node.Value.ToDictionary(); try { if (nodes.TryGetValue("Defaults", out var defaults)) { nodes.Remove("Defaults"); foreach (var n in nodes) { n.Value.Nodes = MiniYaml.Merge(new[] { defaults.Nodes, n.Value.Nodes }); n.Value.Value = n.Value.Value ?? defaults.Value; } } } catch (Exception e) { throw new InvalidDataException($"Error occurred while parsing {node.Key}", e); } foreach (var kvp in nodes) { using (new Support.PerfTimer($"new Sequence(\"{node.Key}\")", 20)) { try { sequences.Add(kvp.Key, CreateSequence(modData, tileSet, cache, node.Key, kvp.Key, kvp.Value)); } catch (FileNotFoundException ex) { // 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)); } } } return(new ReadOnlyDictionary <string, ISpriteSequence>(sequences)); }
public CursorProvider(ModData modData) { var fileSystem = modData.DefaultFileSystem; var sequenceYaml = MiniYaml.Merge(modData.Manifest.Cursors.Select( s => MiniYaml.FromStream(fileSystem.Open(s), s))); var shadowIndex = new int[] { }; var nodesDict = new MiniYaml(null, sequenceYaml).ToDictionary(); if (nodesDict.ContainsKey("ShadowIndex")) { Array.Resize(ref shadowIndex, shadowIndex.Length + 1); Exts.TryParseIntegerInvariant(nodesDict["ShadowIndex"].Value, out shadowIndex[shadowIndex.Length - 1]); } var palettes = new Dictionary <string, ImmutablePalette>(); foreach (var p in nodesDict["Palettes"].Nodes) { palettes.Add(p.Key, new ImmutablePalette(fileSystem.Open(p.Value.Value), shadowIndex)); } Palettes = palettes.AsReadOnly(); var frameCache = new FrameCache(fileSystem, modData.SpriteLoaders); var cursors = new Dictionary <string, CursorSequence>(); foreach (var s in nodesDict["Cursors"].Nodes) { foreach (var sequence in s.Value.Nodes) { cursors.Add(sequence.Key, new CursorSequence(frameCache, sequence.Key, s.Key, s.Value.Value, sequence.Value)); } } Cursors = cursors.AsReadOnly(); }
public CursorProvider(ModData modData) { var fileSystem = modData.DefaultFileSystem; var sequenceYaml = MiniYaml.Merge(modData.Manifest.Cursors.Select( s => MiniYaml.FromStream(fileSystem.Open(s), s))); var nodesDict = new MiniYaml(null, sequenceYaml).ToDictionary(); // Overwrite previous definitions if there are duplicates var pals = new Dictionary <string, IProvidesCursorPaletteInfo>(); foreach (var p in modData.DefaultRules.Actors["world"].TraitInfos <IProvidesCursorPaletteInfo>()) { if (p.Palette != null) { pals[p.Palette] = p; } } Palettes = nodesDict["Cursors"].Nodes.Select(n => n.Value.Value) .Where(p => p != null) .Distinct() .ToDictionary(p => p, p => pals[p].ReadPalette(modData.DefaultFileSystem)) .AsReadOnly(); var frameCache = new FrameCache(fileSystem, modData.SpriteLoaders); var cursors = new Dictionary <string, CursorSequence>(); foreach (var s in nodesDict["Cursors"].Nodes) { foreach (var sequence in s.Value.Nodes) { cursors.Add(sequence.Key, new CursorSequence(frameCache, sequence.Key, s.Key, s.Value.Value, sequence.Value)); } } Cursors = cursors.AsReadOnly(); }
public void ChildCanBeRemovedAndLaterOverridden() { var baseYaml = @" ^BaseA: MockString: AString: Base Test: Inherits: ^BaseA -MockString: "; var overrideYaml = @" Test: MockString: AString: Override "; var result = MiniYaml.Merge(new[] { baseYaml, overrideYaml }.Select(s => MiniYaml.FromString(s, ""))) .First(n => n.Key == "Test").Value.Nodes; Assert.IsTrue(result.Any(n => n.Key == "MockString"), "Node should have the MockString child, but does not."); Assert.IsTrue(result.First(n => n.Key == "MockString").Value.ToDictionary()["AString"].Value == "Override", "MockString value has not been set with the correct override value for AString."); }
public static void Initialize(ModData modData) { Deinitialize(); fileSystem = modData.DefaultFileSystem; collections = new Dictionary <string, D2Collection>(); Collections = new ReadOnlyDictionary <string, D2Collection>(collections); cachedCollectionSheets = new Dictionary <D2Collection, Sheet>(); cachedSheets = new Dictionary <string, Sheet>(); cachedSprites = new Dictionary <string, Sprite>(); var chrome = MiniYaml.Merge(modData.Manifest.Chrome .Select(s => MiniYaml.FromStream(fileSystem.Open(s), s))); foreach (var c in chrome) { if (!c.Key.StartsWith("^", StringComparison.Ordinal)) { LoadCollection(c.Key, c.Value); } } }
public MissionBrowserLogic(Widget widget, ModData modData, World world, Action onStart, Action onExit) { this.modData = modData; this.onStart = onStart; Game.BeforeGameStart += OnGameStart; missionList = widget.Get <ScrollPanelWidget>("MISSION_LIST"); headerTemplate = widget.Get <ScrollItemWidget>("HEADER"); template = widget.Get <ScrollItemWidget>("TEMPLATE"); var title = widget.GetOrNull <LabelWidget>("MISSIONBROWSER_TITLE"); if (title != null) { title.GetText = () => playingVideo != PlayingVideo.None ? selectedMap.Title : title.Text; } widget.Get("MISSION_INFO").IsVisible = () => selectedMap != null; var previewWidget = widget.Get <MapPreviewWidget>("MISSION_PREVIEW"); previewWidget.Preview = () => selectedMap; previewWidget.IsVisible = () => playingVideo == PlayingVideo.None; videoPlayer = widget.Get <VideoPlayerWidget>("MISSION_VIDEO"); widget.Get("MISSION_BIN").IsVisible = () => playingVideo != PlayingVideo.None; fullscreenVideoPlayer = Ui.LoadWidget <BackgroundWidget>("FULLSCREEN_PLAYER", Ui.Root, new WidgetArgs { { "world", world } }); descriptionPanel = widget.Get <ScrollPanelWidget>("MISSION_DESCRIPTION_PANEL"); description = descriptionPanel.Get <LabelWidget>("MISSION_DESCRIPTION"); descriptionFont = Game.Renderer.Fonts[description.Font]; difficultyButton = widget.Get <DropDownButtonWidget>("DIFFICULTY_DROPDOWNBUTTON"); gameSpeedButton = widget.GetOrNull <DropDownButtonWidget>("GAMESPEED_DROPDOWNBUTTON"); startBriefingVideoButton = widget.Get <ButtonWidget>("START_BRIEFING_VIDEO_BUTTON"); stopBriefingVideoButton = widget.Get <ButtonWidget>("STOP_BRIEFING_VIDEO_BUTTON"); stopBriefingVideoButton.IsVisible = () => playingVideo == PlayingVideo.Briefing; stopBriefingVideoButton.OnClick = () => StopVideo(videoPlayer); startInfoVideoButton = widget.Get <ButtonWidget>("START_INFO_VIDEO_BUTTON"); stopInfoVideoButton = widget.Get <ButtonWidget>("STOP_INFO_VIDEO_BUTTON"); stopInfoVideoButton.IsVisible = () => playingVideo == PlayingVideo.Info; stopInfoVideoButton.OnClick = () => StopVideo(videoPlayer); var allPreviews = new List <MapPreview>(); missionList.RemoveChildren(); // Add a group for each campaign if (modData.Manifest.Missions.Any()) { var yaml = MiniYaml.Merge(modData.Manifest.Missions.Select( m => MiniYaml.FromStream(modData.DefaultFileSystem.Open(m), m))); foreach (var kv in yaml) { var missionMapPaths = kv.Value.Nodes.Select(n => n.Key).ToList(); var previews = modData.MapCache .Where(p => p.Class == MapClassification.System && p.Status == MapStatus.Available) .Select(p => new { Preview = p, Index = missionMapPaths.IndexOf(Path.GetFileName(p.Package.Name)) }) .Where(x => x.Index != -1) .OrderBy(x => x.Index) .Select(x => x.Preview); if (previews.Any()) { CreateMissionGroup(kv.Key, previews); allPreviews.AddRange(previews); } } } // Add an additional group for loose missions var loosePreviews = modData.MapCache .Where(p => p.Status == MapStatus.Available && p.Visibility.HasFlag(MapVisibility.MissionSelector) && !allPreviews.Any(a => a.Uid == p.Uid)); if (loosePreviews.Any()) { CreateMissionGroup("Missions", loosePreviews); allPreviews.AddRange(loosePreviews); } if (allPreviews.Any()) { SelectMap(allPreviews.First()); } // Preload map preview to reduce jank new Thread(() => { foreach (var p in allPreviews) { p.GetMinimap(); } }).Start(); var startButton = widget.Get <ButtonWidget>("STARTGAME_BUTTON"); startButton.OnClick = StartMissionClicked; startButton.IsDisabled = () => selectedMap == null; widget.Get <ButtonWidget>("BACK_BUTTON").OnClick = () => { StopVideo(videoPlayer); Game.Disconnect(); Ui.CloseWindow(); onExit(); }; }
public void Run(Action <string> emitError, Action <string> emitWarning, Map map) { if (map != null && !map.SequenceDefinitions.Any()) { return; } this.emitError = emitError; var sequenceSource = map != null ? map.SequenceDefinitions : new List <MiniYamlNode>(); var partial = Game.ModData.Manifest.Sequences .Select(MiniYaml.FromFile) .Aggregate(MiniYaml.MergePartial); sequenceDefinitions = MiniYaml.Merge(sequenceSource, partial); var rules = map == null ? Game.ModData.DefaultRules : map.Rules; var factions = rules.Actors["world"].TraitInfos <FactionInfo>().Select(f => f.InternalName).ToArray(); var sequenceProviders = map == null ? rules.Sequences.Values : new[] { rules.Sequences[map.Tileset] }; foreach (var actorInfo in rules.Actors) { foreach (var renderInfo in actorInfo.Value.TraitInfos <RenderSpritesInfo>()) { 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()) && !actorInfo.Value.Name.Contains("^")) { emitError("Sprite image {0} from actor {1} using faction {2} has no sequence definition." .F(image, actorInfo.Value.Name, faction)); } } } } foreach (var traitInfo in actorInfo.Value.TraitInfos <ITraitInfo>()) { var fields = traitInfo.GetType().GetFields(); foreach (var field in fields) { if (field.HasAttribute <SequenceReferenceAttribute>()) { var sequences = LintExts.GetFieldValues(traitInfo, field, emitError); foreach (var sequence in sequences) { if (string.IsNullOrEmpty(sequence)) { continue; } var renderInfo = actorInfo.Value.TraitInfos <RenderSpritesInfo>().FirstOrDefault(); if (renderInfo == null) { continue; } foreach (var faction in factions) { var sequenceReference = field.GetCustomAttributes <SequenceReferenceAttribute>(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) && 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 { CheckDefintions(imageOverride, sequenceReference, actorInfo, sequence, faction, field, traitInfo); } } } } else { foreach (var sequenceProvider in sequenceProviders) { var image = renderInfo.GetImage(actorInfo.Value, sequenceProvider, faction); CheckDefintions(image, sequenceReference, actorInfo, sequence, faction, field, traitInfo); } } } } } } } 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 <SequenceReferenceAttribute>()) { var sequences = LintExts.GetFieldValues(projectileInfo, field, emitError); foreach (var sequence in sequences) { if (string.IsNullOrEmpty(sequence)) { continue; } var sequenceReference = field.GetCustomAttributes <SequenceReferenceAttribute>(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(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)); } } } } } } } } } } }