#region Copyright & License Information /* * Copyright 2007-2020 The OpenRA Developers (see AUTHORS) * 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>>; using UnitSequences = Lazy>; public interface ISpriteSequence { string Name { get; } int Start { get; } int Length { get; } int Stride { get; } int Facings { get; } int Tick { get; } int ZOffset { get; } int ShadowStart { get; } int ShadowZOffset { get; } int[] Frames { get; } Rectangle Bounds { get; } bool IgnoreWorldTint { get; } float Scale { get; } Sprite GetSprite(int frame); Sprite GetSprite(int frame, WAngle facing); Sprite GetShadow(int frame, WAngle facing); float GetAlpha(int frame); } public interface ISpriteSequenceLoader { IReadOnlyDictionary ParseSequences(ModData modData, string tileSet, SpriteCache cache, MiniYamlNode node); } public class SequenceProvider : IDisposable { readonly ModData modData; readonly string tileSet; readonly Lazy sequences; readonly Lazy spriteCache; public SpriteCache SpriteCache => spriteCache.Value; readonly Dictionary sequenceCache = new Dictionary(); 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 `{0}` does not have any sequences defined.".F(unitName)); if (!unitSeq.Value.TryGetValue(sequenceName, out var seq)) throw new InvalidOperationException("Unit `{0}` does not have a sequence named `{1}`".F(unitName, sequenceName)); return seq; } public IEnumerable 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 `{0}` does not have any sequences defined.".F(unitName)); return unitSeq.Value.ContainsKey(sequenceName); } public IEnumerable Sequences(string unitName) { if (!sequences.Value.TryGetValue(unitName, out var unitSeq)) throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName)); return unitSeq.Value.Keys; } Sequences Load(IReadOnlyFileSystem fileSystem, MiniYaml additionalSequences) { var nodes = MiniYaml.Load(fileSystem, modData.Manifest.Sequences, additionalSequences); var items = new Dictionary(); 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 new ReadOnlyDictionary(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(); } } }