using System; using System.Diagnostics.CodeAnalysis; using Robust.Client.Graphics; using Robust.Client.ResourceManagement; using Robust.Shared.GameObjects; using Robust.Shared.Utility; using static Robust.Client.GameObjects.SpriteComponent; namespace Robust.Client.GameObjects; // This partial class contains various public methods for managing a sprite's layers. // This setter methods for modifying a layer's properties are in a separate file. public sealed partial class SpriteSystem { public bool LayerExists(Entity sprite, int index) { if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) return false; return index >= 0 && index < sprite.Comp.Layers.Count; } public bool TryGetLayer( Entity sprite, int index, [NotNullWhen(true)] out Layer? layer, bool logMissing) { layer = null; if (!_query.Resolve(sprite.Owner, ref sprite.Comp, logMissing)) return false; if (index >= 0 && index < sprite.Comp.Layers.Count) { layer = sprite.Comp.Layers[index]; DebugTools.AssertEqual(layer.Owner, sprite!); DebugTools.AssertEqual(layer.Index, index); return true; } if (logMissing) Log.Error($"Layer index '{index}' on entity {ToPrettyString(sprite)} does not exist! Trace:\n{Environment.StackTrace}"); return false; } public bool RemoveLayer(Entity sprite, int index, bool logMissing = true) { return RemoveLayer(sprite.Owner, index, out _, logMissing); } public bool RemoveLayer( Entity sprite, int index, [NotNullWhen(true)] out Layer? layer, bool logMissing = true) { layer = null; if (!_query.Resolve(sprite.Owner, ref sprite.Comp, logMissing)) return false; if (!TryGetLayer(sprite, index, out layer, logMissing)) return false; sprite.Comp.Layers.RemoveAt(index); foreach (var otherLayer in sprite.Comp.Layers[index..]) { otherLayer.Index--; } // TODO SPRITE track inverse-mapping? foreach (var (key, value) in sprite.Comp.LayerMap) { if (value == index) sprite.Comp.LayerMap.Remove(key); else if (value > index) { sprite.Comp.LayerMap[key]--; } } layer.Owner = default; layer.Index = -1; #if DEBUG foreach (var otherLayer in sprite.Comp.Layers) { DebugTools.AssertEqual(otherLayer, sprite.Comp.Layers[otherLayer.Index]); } #endif sprite.Comp.BoundsDirty = true; _tree.QueueTreeUpdate(sprite!); QueueUpdateIsInert(sprite!); return true; } #region AddLayer /// /// Add the given sprite layer. If an index is specified, this will insert the layer with the given index, resulting /// in all other layers being reshuffled. /// public int AddLayer(Entity sprite, Layer layer, int? index = null) { if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) { layer.Index = -1; layer.Owner = default; return -1; } layer.Owner = sprite!; if (index is { } i && i != sprite.Comp.Layers.Count) { foreach (var otherLayer in sprite.Comp.Layers[i..]) { otherLayer.Index++; } // TODO SPRITE track inverse-mapping? sprite.Comp.Layers.Insert(i, layer); layer.Index = i; foreach (var (key, value) in sprite.Comp.LayerMap) { if (value >= i) sprite.Comp.LayerMap[key]++; } } else { layer.Index = sprite.Comp.Layers.Count; sprite.Comp.Layers.Add(layer); } #if DEBUG foreach (var otherLayer in sprite.Comp.Layers) { DebugTools.AssertEqual(otherLayer, sprite.Comp.Layers[otherLayer.Index]); } #endif layer.BoundsDirty = true; if (!layer.Blank) { sprite.Comp.BoundsDirty = true; _tree.QueueTreeUpdate(sprite!); QueueUpdateIsInert(sprite!); } return layer.Index; } /// /// Add a layer corresponding to the given RSI state. /// /// The sprite /// The RSI state /// The RSI to use. If not specified, it will default to using /// The layer index to use for the new sprite. /// public int AddRsiLayer(Entity sprite, RSI.StateId stateId, RSI? rsi = null, int? index = null) { if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) return -1; var layer = AddBlankLayer(sprite!, index); if (rsi != null) LayerSetRsi(layer, rsi, stateId); else LayerSetRsiState(layer, stateId); return layer.Index; } /// /// Add a layer corresponding to the given RSI state. /// /// The sprite /// The RSI state /// The path to the RSI. /// The layer index to use for the new sprite. /// public int AddRsiLayer(Entity sprite, RSI.StateId state, ResPath path, int? index = null) { if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) return -1; if (!_resourceCache.TryGetResource(TextureRoot / path, out var res)) Log.Error($"Unable to load RSI '{path}'. Trace:\n{Environment.StackTrace}"); if (path.Extension != "rsi") Log.Error($"Expected rsi path but got '{path}'?"); return AddRsiLayer(sprite, state, res?.RSI, index); } public int AddTextureLayer(Entity sprite, ResPath path, int? index = null) { if (_resourceCache.TryGetResource(TextureRoot / path, out var texture)) return AddTextureLayer(sprite, texture?.Texture, index); if (path.Extension == "rsi") Log.Error($"Expected texture but got rsi '{path}', did you mean 'sprite:' instead of 'texture:'?"); Log.Error($"Unable to load texture '{path}'. Trace:\n{Environment.StackTrace}"); return AddTextureLayer(sprite, texture?.Texture, index); } public int AddTextureLayer(Entity sprite, Texture? texture, int? index = null) { if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) return -1; var layer = new Layer {Texture = texture}; return AddLayer(sprite, layer, index); } public int AddLayer(Entity sprite, SpriteSpecifier specifier, int? newIndex = null) { return specifier switch { SpriteSpecifier.Texture tex => AddTextureLayer(sprite, tex.TexturePath, newIndex), SpriteSpecifier.Rsi rsi => AddRsiLayer(sprite, rsi.RsiState, rsi.RsiPath, newIndex), _ => throw new NotImplementedException() }; } /// /// Add a new sprite layer and populate it using the provided layer data. /// public int AddLayer(Entity sprite, PrototypeLayerData layerDatum, int? index) { if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) return -1; var layer = AddBlankLayer(sprite!, index); LayerSetData(layer, layerDatum); return layer.Index; } /// /// Add a blank sprite layer. /// public Layer AddBlankLayer(Entity sprite, int? index = null) { var layer = new Layer(); AddLayer(sprite!, layer, index); return layer; } #endregion }