From 428173d60690c47adb25f12b5fdf75258c454962 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Mon, 13 Jan 2025 04:04:42 +1300 Subject: [PATCH 01/36] Partial sprite component ECS --- .../ComponentTrees/SpriteTreeSystem.cs | 20 - .../Renderable/IRenderableComponent.cs | 4 +- .../Components/Renderable/ISpriteLayer.cs | 1 - .../Components/Renderable/SpriteComponent.cs | 484 +++++------------- .../EntitySystems/SpriteSystem.Component.cs | 55 ++ .../EntitySystems/SpriteSystem.Layer.cs | 222 ++++++++ .../EntitySystems/SpriteSystem.LayerMap.cs | 275 ++++++++++ .../EntitySystems/SpriteSystem.Setters.cs | 135 +++++ .../GameObjects/EntitySystems/SpriteSystem.cs | 3 + Robust.Client/Graphics/Clyde/Clyde.HLR.cs | 2 +- .../ComponentTrees/ComponentTreeSystem.cs | 5 + 11 files changed, 830 insertions(+), 376 deletions(-) create mode 100644 Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs create mode 100644 Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerMap.cs create mode 100644 Robust.Client/GameObjects/EntitySystems/SpriteSystem.Setters.cs diff --git a/Robust.Client/ComponentTrees/SpriteTreeSystem.cs b/Robust.Client/ComponentTrees/SpriteTreeSystem.cs index 9a434bda100..c859ebc501c 100644 --- a/Robust.Client/ComponentTrees/SpriteTreeSystem.cs +++ b/Robust.Client/ComponentTrees/SpriteTreeSystem.cs @@ -9,26 +9,6 @@ namespace Robust.Client.ComponentTrees; public sealed class SpriteTreeSystem : ComponentTreeSystem { - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnQueueUpdate); - } - - private void OnQueueUpdate(EntityUid uid, SpriteComponent component, ref QueueSpriteTreeUpdateEvent args) - => QueueTreeUpdate(uid, component, args.Xform); - - // TODO remove this when finally ECSing sprite components - [ByRefEvent] - internal readonly struct QueueSpriteTreeUpdateEvent - { - public readonly TransformComponent Xform; - public QueueSpriteTreeUpdateEvent(TransformComponent xform) - { - Xform = xform; - } - } - #region Component Tree Overrides protected override bool DoFrameUpdate => true; protected override bool DoTickUpdate => false; diff --git a/Robust.Client/GameObjects/Components/Renderable/IRenderableComponent.cs b/Robust.Client/GameObjects/Components/Renderable/IRenderableComponent.cs index 1f863359df0..65e1c2a03eb 100644 --- a/Robust.Client/GameObjects/Components/Renderable/IRenderableComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/IRenderableComponent.cs @@ -1,9 +1,11 @@ -using Robust.Shared.GameObjects; +using System; +using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Maths; namespace Robust.Client.GameObjects { + [Obsolete] public partial interface IRenderableComponent : IComponent { int DrawDepth { get; set; } diff --git a/Robust.Client/GameObjects/Components/Renderable/ISpriteLayer.cs b/Robust.Client/GameObjects/Components/Renderable/ISpriteLayer.cs index a2d5134a5a0..9f8d0dae618 100644 --- a/Robust.Client/GameObjects/Components/Renderable/ISpriteLayer.cs +++ b/Robust.Client/GameObjects/Components/Renderable/ISpriteLayer.cs @@ -1,6 +1,5 @@ using System.Numerics; using Robust.Client.Graphics; -using Robust.Shared.Graphics; using Robust.Shared.Graphics.RSI; using Robust.Shared.Maths; diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index a4bdcae91b7..1c5470aaee3 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -5,10 +5,12 @@ using System.Linq; using System.Numerics; using System.Text; +using Robust.Client.ComponentTrees; using Robust.Client.Graphics; using Robust.Client.Graphics.Clyde; using Robust.Client.ResourceManagement; using Robust.Client.Utility; +using Robust.Shared; using Robust.Shared.Animations; using Robust.Shared.ComponentTrees; using Robust.Shared.GameObjects; @@ -25,23 +27,23 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; using Robust.Shared.Utility; using Robust.Shared.ViewVariables; -using static Robust.Client.ComponentTrees.SpriteTreeSystem; using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth; using static Robust.Shared.Serialization.TypeSerializers.Implementations.SpriteSpecifierSerializer; using Direction = Robust.Shared.Maths.Direction; using Vector4 = Robust.Shared.Maths.Vector4; +#pragma warning disable CS0618 // Type or member is obsolete namespace Robust.Client.GameObjects { [RegisterComponent] public sealed partial class SpriteComponent : Component, IComponentDebug, ISerializationHooks, IComponentTreeEntry, IAnimationProperties { + public const string LogCategory = "go.comp.sprite"; + [Dependency] private readonly IResourceCache resourceCache = default!; [Dependency] private readonly IPrototypeManager prototypes = default!; - [Dependency] private readonly IEntityManager entities = default!; + [Dependency] private readonly EntityManager entities = default!; [Dependency] private readonly IReflectionManager reflection = default!; - [Dependency] private readonly IEyeManager eyeManager = default!; - [Dependency] private readonly IComponentFactory factory = default!; /// /// See . @@ -52,11 +54,11 @@ public sealed partial class SpriteComponent : Component, IComponentDebug, ISeria /// Whether the layers have independant drawing strategies, e.g some may snap to cardinals while others won't. /// The sprite should still set its global rendering method (e.g NoRot or SnapCardinals), this only gives finer control over how layers are rendered internally. /// - [DataField("granularLayersRendering")] + [DataField] public bool GranularLayersRendering = false; - [DataField("visible")] - private bool _visible = true; + [DataField] + internal bool _visible = true; // VV convenience variable to examine layer objects using layer keys [ViewVariables] @@ -66,17 +68,17 @@ public sealed partial class SpriteComponent : Component, IComponentDebug, ISeria public bool Visible { get => _visible; - set - { - if (_visible == value) return; - _visible = value; - - QueueUpdateRenderTree(); - } + [Obsolete("Use SpriteSystem.SetVisible() instead.")] + set => Sys.SetVisible((Owner, this), value); } + private SpriteSystem? _sys; + private SpriteTreeSystem? _treeSys; + private SpriteSystem Sys => _sys ??= entities.System(); + private SpriteTreeSystem TreeSys => _treeSys ??= entities.System(); + [DataField("drawdepth", customTypeSerializer: typeof(ConstantSerializer))] - private int drawDepth = DrawDepthTag.Default; + internal int drawDepth = DrawDepthTag.Default; /// /// Z-index for drawing. @@ -85,11 +87,12 @@ public bool Visible public int DrawDepth { get => drawDepth; - set => drawDepth = value; + [Obsolete("Use SpriteSystem.SetDrawDepth() instead.")] + set => Sys.SetDrawDepth((Owner, this), value); } - [DataField("scale")] - private Vector2 scale = Vector2.One; + [DataField] + internal Vector2 scale = Vector2.One; /// /// A scale applied to all layers. @@ -99,38 +102,24 @@ public int DrawDepth public Vector2 Scale { get => scale; - set - { - if (MathF.Abs(value.X) < 0.005f || MathF.Abs(value.Y) < 0.005f) - { - // Scales of ~0.0025 or lower can lead to singular matrices due to rounding errors. - Logger.Error($"Attempted to set layer sprite scale to very small values. Entity: {entities.ToPrettyString(Owner)}. Scale: {value}"); - return; - } - - _bounds = _bounds.Scale(value / scale); - scale = value; - UpdateLocalMatrix(); - } + [Obsolete("Use SpriteSystem.SetScale() instead.")] + set => Sys.SetScale((Owner, this), value); } - [DataField("rotation")] - private Angle rotation = Angle.Zero; + [DataField] + internal Angle rotation = Angle.Zero; [Animatable] [ViewVariables(VVAccess.ReadWrite)] public Angle Rotation { get => rotation; - set - { - rotation = value; - UpdateLocalMatrix(); - } + [Obsolete("Use SpriteSystem.SetRotation() instead.")] + set => Sys.SetRotation((Owner, this), value); } - [DataField("offset")] - private Vector2 offset = Vector2.Zero; + [DataField] + internal Vector2 offset = Vector2.Zero; /// /// Offset applied to all layers. @@ -140,26 +129,25 @@ public Angle Rotation public Vector2 Offset { get => offset; - set - { - offset = value; - UpdateLocalMatrix(); - } + [Obsolete("Use SpriteSystem.SetOffset() instead.")] + set => Sys.SetOffset((Owner, this), value); } - [DataField("color")] - private Color color = Color.White; - - public Matrix3x2 LocalMatrix = Matrix3x2.Identity; + [DataField] + internal Color color = Color.White; [Animatable] [ViewVariables(VVAccess.ReadWrite)] public Color Color { get => color; - set => color = value; + [Obsolete("Use SpriteSystem.SetColor() instead.")] + set => Sys.SetColor((Owner, this), value); } + public Matrix3x2 LocalMatrix = Matrix3x2.Identity; + + #region SpriteTree [ViewVariables] public DynamicTree>? Tree { get; set; } @@ -168,52 +156,23 @@ public Color Color public bool AddToTree => Visible && !ContainerOccluded && Layers.Count > 0; public bool TreeUpdateQueued { get; set; } + #endregion - private RSI? _baseRsi; + internal RSI? _baseRsi; [ViewVariables(VVAccess.ReadWrite)] public RSI? BaseRSI { get => _baseRsi; - set - { - if (value == _baseRsi) - return; - - _baseRsi = value; - if (value == null) - return; - - for (var i = 0; i < Layers.Count; i++) - { - var layer = Layers[i]; - if (!layer.State.IsValid || layer.RSI != null) - { - continue; - } - - layer.UpdateActualState(); - - if (value.TryGetState(layer.State, out var state)) - { - layer.AnimationTimeLeft = state.GetDelay(0); - } - else - { - Logger.ErrorS(LogCategory, - "Layer '{0}'no longer has state '{1}' due to base RSI change. Trace:\n{2}", - i, layer.State, Environment.StackTrace); - layer.Texture = null; - } - } - } + [Obsolete("Use SpriteSystem.SetBaseRSI() instead.")] + set => Sys.SetBaseRsi((Owner, this), value); } [DataField("sprite", readOnly: true)] private string? rsi; [DataField("layers", readOnly: true)] private List layerDatums = new(); - [DataField("state", readOnly: true)] private string? state; - [DataField("texture", readOnly: true)] private string? texture; + [DataField(readOnly: true)] private string? state; + [DataField(readOnly: true)] private string? texture; /// /// Should this entity show up in containers regardless of whether the container can show contents? @@ -226,17 +185,13 @@ public RSI? BaseRSI public bool ContainerOccluded { get => _containerOccluded && !OverrideContainerOcclusion; - set - { - if (_containerOccluded == value) return; - _containerOccluded = value; - QueueUpdateRenderTree(); - } + [Obsolete("Use SpriteSystem.SetContainerOccluded() instead.")] + set => Sys.SetContainerOccluded((Owner, this), value); } - private bool _containerOccluded; + internal bool _containerOccluded; - private Box2 _bounds; + internal Box2 _bounds; /// /// The bounds of the sprite. This does factor in the sprite's but not the @@ -250,61 +205,52 @@ public bool ContainerOccluded /// Shader instance to use when drawing the final sprite to the world. /// [ViewVariables(VVAccess.ReadWrite)] - public ShaderInstance? PostShader { get; set; } + public ShaderInstance? PostShader + { + get; + // This will get obsoleted, but I only want to mark it as obsolete when multi-shader support is added, so + // that people can use the appropriate method and don't migrate to an incorrect new method that wont + // be obsoleted. + set; + } /// - /// Whether or not to pass the screen texture to the . + /// Whether to pass the screen texture to the . /// /// /// Should be false unless you really need it. /// - [DataField("getScreenTexture")] - [ViewVariables(VVAccess.ReadWrite)] - private bool _getScreenTexture = false; - public bool GetScreenTexture - { - get => _getScreenTexture && PostShader != null; - set => _getScreenTexture = value; - } + [DataField] + public bool GetScreenTexture; /// /// If true, this raise a entity system event before rendering this sprite, allowing systems to modify the /// shader parameters. Usually this can just be done via a frame-update, but some shaders require /// information about the viewport / eye. /// - [DataField("raiseShaderEvent")] - [ViewVariables(VVAccess.ReadWrite)] - public bool RaiseShaderEvent = false; + [DataField] + public bool RaiseShaderEvent; - [ViewVariables] private Dictionary LayerMap = new(); - [ViewVariables] private bool _layerMapShared; + [ViewVariables] internal Dictionary LayerMap { get; set; } = new(); [ViewVariables] internal List Layers = new(); [ViewVariables(VVAccess.ReadWrite)] public uint RenderOrder { get; set; } - public const string LogCategory = "go.comp.sprite"; - [ViewVariables(VVAccess.ReadWrite)] public bool IsInert { get; internal set; } void ISerializationHooks.AfterDeserialization() { // Please somebody burn this to the ground. There is so much spaghetti. + // Why has no one answered my prayers. IoCManager.InjectDependencies(this); - + if (!string.IsNullOrWhiteSpace(rsi)) { - if (!string.IsNullOrWhiteSpace(rsi)) - { - var rsiPath = TextureRoot / rsi; - if(resourceCache.TryGetResource(rsiPath, out RSIResource? resource)) - { - BaseRSI = resource.RSI; - } - else - { - Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'.", rsiPath); - } - } + var rsiPath = TextureRoot / rsi; + if (resourceCache.TryGetResource(rsiPath, out RSIResource? resource)) + _baseRsi = resource.RSI; + else + Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'.", rsiPath); } if (layerDatums.Count == 0) @@ -332,16 +278,22 @@ void ISerializationHooks.AfterDeserialization() Layers.Clear(); foreach (var datum in layerDatums) { - AddLayer(datum); + var layer = new Layer(this); + Layers.Add(layer); + LayerSetData(layer, Layers.Count - 1, datum); } - _layerMapShared = true; + } - QueueUpdateRenderTree(); - QueueUpdateIsInert(); + _bounds = new Box2(); + foreach (var layer in Layers) + { + if (layer is {Visible: true, Blank: false}) + _bounds = _bounds.Union(layer.CalculateBoundingBox()); } - UpdateLocalMatrix(); + _bounds = _bounds.Scale(Scale); + LocalMatrix = Matrix3Helpers.CreateTransform(in offset, in rotation, in scale); } /// @@ -349,55 +301,19 @@ void ISerializationHooks.AfterDeserialization() /// this is called. Does not keep them perpetually in sync. /// This does some deep copying thus exerts some gc pressure, so avoid this for hot code paths. /// + [Obsolete("Use SpriteSystem.CopySprite() instead.")] public void CopyFrom(SpriteComponent other) { - //deep copying things to avoid entanglement - _baseRsi = other._baseRsi; - _bounds = other._bounds; - _visible = other._visible; - _layerMapShared = other._layerMapShared; - color = other.color; - offset = other.offset; - rotation = other.rotation; - scale = other.scale; - UpdateLocalMatrix(); - drawDepth = other.drawDepth; - _screenLock = other._screenLock; - DirectionOverride = other.DirectionOverride; - EnableDirectionOverride = other.EnableDirectionOverride; - Layers = new List(other.Layers.Count); - foreach (var otherLayer in other.Layers) - { - Layers.Add(new Layer(otherLayer, this)); - } - IsInert = other.IsInert; - LayerMap = other.LayerMap.ToDictionary(entry => entry.Key, - entry => entry.Value); - if (other.PostShader != null) - { - // only need to copy the shader if it's mutable - PostShader = other.PostShader.Mutable ? other.PostShader.Duplicate() : other.PostShader; - } - else - { - PostShader = null; - } - - RenderOrder = other.RenderOrder; - GranularLayersRendering = other.GranularLayersRendering; - } - - internal void UpdateLocalMatrix() - { - LocalMatrix = Matrix3Helpers.CreateTransform(in offset, in rotation, in scale); + Sys.CopySprite(other, this); } + [Obsolete("Use LocalMatrix")] public Matrix3x2 GetLocalMatrix() { return LocalMatrix; } - /// + [Obsolete("Use SpriteSystem.LayerMapSet() instead.")] public void LayerMapSet(object key, int layer) { if (layer < 0 || layer >= Layers.Count) @@ -405,24 +321,22 @@ public void LayerMapSet(object key, int layer) throw new ArgumentOutOfRangeException(); } - _layerMapEnsurePrivate(); LayerMap.Add(key, layer); } - /// + [Obsolete("Use SpriteSystem.LayerMapRemove() instead.")] public void LayerMapRemove(object key) { - _layerMapEnsurePrivate(); LayerMap.Remove(key); } - /// + [Obsolete("Use SpriteSystem.LayerMapGet() instead.")] public int LayerMapGet(object key) { return LayerMap[key]; } - /// + [Obsolete("Use SpriteSystem.LayerMapTryGet() instead.")] public bool LayerMapTryGet(object key, out int layer, bool logError = false) { var result = LayerMap.TryGetValue(key, out layer); @@ -436,38 +350,18 @@ public bool LayerMapTryGet(object key, out int layer, bool logError = false) return result; } + [Obsolete("Use SpriteSystem.TryGetLayer() instead.")] public bool TryGetLayer(int index, [NotNullWhen(true)] out Layer? layer, bool logError = false) - { - if (index < Layers.Count) - { - layer = Layers[index]; - return true; - } - - if (logError) - { - Logger.ErrorS(LogCategory, "{0} - Layer index '{1}' does not exist! Trace:\n{2}", - entities.ToPrettyString(Owner), index, Environment.StackTrace); - } + => Sys.TryGetLayer((Owner, this), index, out layer, logError); - layer = null; - return false; - } + [Obsolete("Use SpriteSystem.LayerExists() instead.")] + public bool LayerExists(int layer, bool logError = true) + => Sys.LayerExists((Owner, this), layer); - public bool LayerExists(int layer, bool logError = true) => TryGetLayer(layer, out _, logError); + [Obsolete("Use SpriteSystem.LayerExists() instead.")] public bool LayerExists(object key, bool logError = false) => LayerMapTryGet(key, out _, logError); - private void _layerMapEnsurePrivate() - { - if (!_layerMapShared) - { - return; - } - - LayerMap = LayerMap.ShallowClone(); - _layerMapShared = false; - } - + [Obsolete("Use SpriteSystem.LayerMapReserve() instead.")] public int LayerMapReserveBlank(object key) { if (LayerMapTryGet(key, out var index)) @@ -481,186 +375,79 @@ public int LayerMapReserveBlank(object key) return index; } + [Obsolete("Use SpriteSystem.AddBlankLayer() instead.")] public int AddBlankLayer(int? newIndex = null) - { - var layer = new Layer(this); - return AddLayer(layer, newIndex); - } + => Sys.AddBlankLayer((Owner, this), newIndex); - /// - /// Add a new layer based on some . - /// + [Obsolete("Use SpriteSystem.AddLayer() instead.")] public int AddLayer(PrototypeLayerData layerDatum, int? newIndex = null) - { - var layer = new Layer(this); - - var index = AddLayer(layer, newIndex); - - LayerSetData(index, layerDatum); - return index; - } + => Sys.AddLayer((Owner, this), layerDatum, newIndex); + [Obsolete("Use SpriteSystem.AddTextureLayer() instead.")] public int AddLayer(string texturePath, int? newIndex = null) { return AddLayer(new ResPath(texturePath), newIndex); } + [Obsolete("Use SpriteSystem.AddTextureLayer() instead.")] public int AddLayer(ResPath texturePath, int? newIndex = null) - { - if (!resourceCache.TryGetResource(TextureRoot / texturePath, out var texture)) - { - if (texturePath.Extension == "rsi") - { - Logger.ErrorS(LogCategory, - "Expected texture but got rsi '{0}', did you mean 'sprite:' instead of 'texture:'?", - texturePath); - } - - Logger.ErrorS(LogCategory, "Unable to load texture '{0}'. Trace:\n{1}", texturePath, - Environment.StackTrace); - } - - return AddLayer(texture?.Texture, newIndex); - } + => Sys.AddTextureLayer((Owner, this), texturePath, newIndex); + [Obsolete("Use SpriteSystem.AddTextureLayer() instead.")] public int AddLayer(Texture? texture, int? newIndex = null) - { - var layer = new Layer(this) { Texture = texture }; - return AddLayer(layer, newIndex); - } + => Sys.AddTextureLayer((Owner, this), texture, newIndex); + [Obsolete("Use SpriteSystem.AddRsiLayer() instead.")] public int AddLayer(RSI.StateId stateId, int? newIndex = null) - { - var layer = new Layer(this) { State = stateId }; - if (BaseRSI != null && BaseRSI.TryGetState(stateId, out var state)) - { - layer.AnimationTimeLeft = state.GetDelay(0); - } - else - { - Logger.ErrorS(LogCategory, "State does not exist in RSI: '{0}'. Trace:\n{1}", stateId, - Environment.StackTrace); - } - - return AddLayer(layer, newIndex); - } + => Sys.AddRsiLayer((Owner, this), stateId, null, newIndex); + [Obsolete("Use SpriteSystem.AddRsiLayer() instead.")] public int AddLayerState(string stateId, int? newIndex = null) { return AddLayer(new RSI.StateId(stateId), newIndex); } + [Obsolete("Use SpriteSystem.AddRsiLayer() instead.")] public int AddLayer(RSI.StateId stateId, string rsiPath, int? newIndex = null) { return AddLayer(stateId, new ResPath(rsiPath), newIndex); } + [Obsolete("Use SpriteSystem.AddRsiLayer() instead.")] public int AddLayerState(string stateId, string rsiPath, int? newIndex = null) { return AddLayer(new RSI.StateId(stateId), rsiPath, newIndex); } + [Obsolete("Use SpriteSystem.AddRsiLayer() instead.")] public int AddLayer(RSI.StateId stateId, ResPath rsiPath, int? newIndex = null) - { - if (!resourceCache.TryGetResource(TextureRoot / rsiPath, out var res)) - { - Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath, Environment.StackTrace); - } - - return AddLayer(stateId, res?.RSI, newIndex); - } + => Sys.AddRsiLayer((Owner, this), stateId, rsiPath, newIndex); + [Obsolete("Use SpriteSystem.AddRsiLayer() instead.")] public int AddLayerState(string stateId, ResPath rsiPath, int? newIndex = null) { return AddLayer(new RSI.StateId(stateId), rsiPath, newIndex); } + [Obsolete("Use SpriteSystem.AddRsiLayer() instead.")] public int AddLayer(RSI.StateId stateId, RSI? rsi, int? newIndex = null) - { - var layer = new Layer(this) { State = stateId, RSI = rsi }; - if (rsi != null && rsi.TryGetState(stateId, out var state)) - { - layer.AnimationTimeLeft = state.GetDelay(0); - } - else - { - Logger.ErrorS(LogCategory, "State does not exist in RSI: '{0}'. Trace:\n{1}", stateId, - Environment.StackTrace); - } - - return AddLayer(layer, newIndex); - } + => Sys.AddRsiLayer((Owner, this), stateId, rsi, newIndex); + [Obsolete("Use SpriteSystem.AddRsiLayer() instead.")] public int AddLayerState(string stateId, RSI rsi, int? newIndex = null) { return AddLayer(new RSI.StateId(stateId), rsi, newIndex); } + [Obsolete("Use SpriteSystem.AddLayer() instead.")] public int AddLayer(SpriteSpecifier specifier, int? newIndex = null) - { - switch (specifier) - { - case SpriteSpecifier.Texture tex: - return AddLayer(tex.TexturePath, newIndex); - - case SpriteSpecifier.Rsi rsi: - return AddLayerState(rsi.RsiState, rsi.RsiPath, newIndex); - - default: - throw new NotImplementedException(); - } - } - - private int AddLayer(Layer layer, int? newIndex) - { - int index; - if (newIndex.HasValue) - { - Layers.Insert(newIndex.Value, layer); - foreach (var kv in LayerMap) - { - if (kv.Value >= newIndex.Value) - { - LayerMap[kv.Key] = kv.Value + 1; - } - } - - index = newIndex.Value; - } - else - { - Layers.Add(layer); - index = Layers.Count - 1; - } - - RebuildBounds(); - QueueUpdateIsInert(); - return index; - } + => Sys.AddLayer((Owner, this), specifier, newIndex); + [Obsolete("Use SpriteSystem.RemoveLayer() instead.")] public void RemoveLayer(int layer) - { - if (!LayerExists(layer)) - return; - - Layers.RemoveAt(layer); - foreach (var kv in LayerMap) - { - if (kv.Value == layer) - { - LayerMap.Remove(kv.Key); - } - - else if (kv.Value > layer) - { - LayerMap[kv.Key] = kv.Value - 1; - } - } - - RebuildBounds(); - QueueUpdateIsInert(); - } + => Sys.RemoveLayer((Owner, this), layer); + [Obsolete("Use SpriteSystem.RemoveLayer() instead.")] public void RemoveLayer(object layerKey) { if (!LayerMapTryGet(layerKey, out var layer, true)) @@ -669,17 +456,11 @@ public void RemoveLayer(object layerKey) RemoveLayer(layer); } - private void RebuildBounds() + [Obsolete("Use SpriteSystem.RebuildBounds() instead.")] + internal void RebuildBounds() { - _bounds = new Box2(); - foreach (var layer in Layers) - { - if (!layer.Visible || layer.Blank) continue; - - _bounds = _bounds.Union(layer.CalculateBoundingBox()); - } - _bounds = _bounds.Scale(Scale); - QueueUpdateRenderTree(); + if (entities.Initialized && entities.TrySystem(out SpriteSystem? sys)) + sys.RebuildBounds((Owner, this)); } /// @@ -687,9 +468,12 @@ private void RebuildBounds() /// public void LayerSetData(int index, PrototypeLayerData layerDatum) { - if (!TryGetLayer(index, out var layer)) - return; + if (TryGetLayer(index, out var layer)) + LayerSetData(layer, index, layerDatum); + } + private void LayerSetData(Layer layer, int index, PrototypeLayerData layerDatum) + { if (!string.IsNullOrWhiteSpace(layerDatum.RsiPath)) { var path = TextureRoot / layerDatum.RsiPath; @@ -781,7 +565,6 @@ public void LayerSetData(int index, PrototypeLayerData layerDatum) continue; } - _layerMapEnsurePrivate(); LayerMap[key] = index; } } @@ -1255,7 +1038,7 @@ public void Render(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle wo RenderInternal(drawingHandle, eyeRotation, worldRotation, position, overrideDirection); } - [DataField("noRot")] private bool _screenLock = false; + [DataField("noRot")] internal bool _screenLock = false; /// /// If the sprite only has 1 direction should it snap at cardinals if rotated. @@ -1374,15 +1157,10 @@ public int GetLayerDirectionCount(ISpriteLayer layer) private void QueueUpdateRenderTree() { - if (TreeUpdateQueued || !Owner.IsValid()) - return; - - // TODO whenever sprite comp gets ECS'd , just make this a direct method call. - var ev = new QueueSpriteTreeUpdateEvent(entities.GetComponent(Owner)); - entities.EventBus.RaiseComponentEvent(Owner, this, ref ev); + TreeSys.QueueTreeUpdate((Owner, this)); } - private void QueueUpdateIsInert() + internal void QueueUpdateIsInert() { if (_inertUpdateQueued || !Owner.IsValid()) return; diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Component.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Component.cs index de02891ecc3..97e53e499af 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Component.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Component.cs @@ -1,4 +1,8 @@ +using System.Collections.Generic; using System.Linq; +using Robust.Shared.GameObjects; +using Robust.Shared.Maths; +using Robust.Shared.Utility; namespace Robust.Client.GameObjects; @@ -41,4 +45,55 @@ public void SetAutoAnimateSync(SpriteComponent sprite, SpriteComponent.Layer lay layer.AnimationTimeLeft = (float) -(time % state.TotalDelay); layer.AnimationFrame = 0; } + + public void CopySprite(Entity source, Entity target) + { + if (!Resolve(source.Owner, ref source.Comp)) + return; + + if (!Resolve(target.Owner, ref target.Comp)) + return; + + CopySprite(source.Comp, target.Comp); + } + + public void CopySprite(SpriteComponent source, SpriteComponent target) + { + target._baseRsi = source._baseRsi; + target._bounds = source._bounds; + target._visible = source._visible; + target.color = source.color; + target.offset = source.offset; + target.rotation = source.rotation; + target.scale = source.scale; + target.LocalMatrix = Matrix3Helpers.CreateTransform(in target.offset, in target.rotation, in target.scale); + target.drawDepth = source.drawDepth; + target._screenLock = source._screenLock; + target.DirectionOverride = source.DirectionOverride; + target.EnableDirectionOverride = source.EnableDirectionOverride; + target.Layers = new List(source.Layers.Count); + foreach (var otherLayer in source.Layers) + { + target.Layers.Add(new SpriteComponent.Layer(otherLayer, target)); + } + + target.IsInert = source.IsInert; + target.LayerMap = source.LayerMap.ShallowClone(); + target.PostShader = source.PostShader is {Mutable: true} ? source.PostShader.Duplicate() : source.PostShader; + target.RenderOrder = source.RenderOrder; + target.GranularLayersRendering = source.GranularLayersRendering; + } + + public void RebuildBounds(Entity sprite) + { + var bounds = new Box2(); + foreach (var layer in sprite.Comp.Layers) + { + if (layer is {Visible: true, Blank: false}) + bounds = bounds.Union(layer.CalculateBoundingBox()); + } + + sprite.Comp._bounds = bounds.Scale(sprite.Comp.Scale); + _tree.QueueTreeUpdate(sprite); + } } diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs new file mode 100644 index 00000000000..b2d589f7a88 --- /dev/null +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs @@ -0,0 +1,222 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Robust.Client.Graphics; +using Robust.Client.ResourceManagement; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.TypeSerializers.Implementations; +using Robust.Shared.Utility; +using static Robust.Client.GameObjects.SpriteComponent; + +namespace Robust.Client.GameObjects; + +// This partial class contains various public methods for manipulating layers. +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]; + 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 (index < 0 || index >= sprite.Comp.Layers.Count) + { + if (logMissing) + Log.Error($"Layer index '{index}' on entity {ToPrettyString(sprite)} does not exist! Trace:\n{Environment.StackTrace}"); + return false; + } + + layer = sprite.Comp.Layers[index]; + sprite.Comp.Layers.RemoveAt(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]--; + } + } + + RebuildBounds(sprite!); + sprite.Comp.QueueUpdateIsInert(); + 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)) + return -1; + + if (index is { } i && i != sprite.Comp.Layers.Count) + { + // TODO SPRITE track inverse-mapping? + sprite.Comp.Layers.Insert(i, layer); + foreach (var (key, value) in sprite.Comp.LayerMap) + { + if (value >= i) + sprite.Comp.LayerMap[key]++; + } + } + else + { + index = sprite.Comp.Layers.Count; + sprite.Comp.Layers.Add(layer); + } + + sprite.Comp.RebuildBounds(); + sprite.Comp.QueueUpdateIsInert(); + return index.Value; + } + + /// + /// 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 = new Layer(sprite.Comp) {State = stateId, RSI = rsi}; + rsi ??= sprite.Comp._baseRsi; + + if (rsi != null && rsi.TryGetState(stateId, out var state)) + layer.AnimationTimeLeft = state.GetDelay(0); + else + Log.Error($"State does not exist in RSI: '{stateId}'. Trace:\n{Environment.StackTrace}"); + + return AddLayer(sprite, 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(SpriteSpecifierSerializer.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(SpriteSpecifierSerializer.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(sprite.Comp) {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; + + index = AddBlankLayer(sprite, index); + sprite.Comp.LayerSetData(index, layerDatum); + return index.Value; + } + + /// + /// Add a blank sprite layer. + /// + public int AddBlankLayer(Entity sprite, int? index = null) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return -1; + + var layer = new Layer(sprite.Comp); + return AddLayer(sprite, layer, index); + } + + #endregion +} diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerMap.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerMap.cs new file mode 100644 index 00000000000..52cca68648b --- /dev/null +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerMap.cs @@ -0,0 +1,275 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Robust.Shared.GameObjects; +using static Robust.Client.GameObjects.SpriteComponent; + +namespace Robust.Client.GameObjects; + +// This partial class contains various public methods for manipulating layer mappings. +public sealed partial class SpriteSystem +{ + /// + /// Map an enum to a layer index. + /// + public void LayerMapSet(Entity sprite, Enum key, int index) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (index < 0 || index >= sprite.Comp.Layers.Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + sprite.Comp.LayerMap[key] = index; + } + + /// + /// Map string to a layer index. If possible, it is preferred to use an enum key. + /// string keys mainly exist to make it easier to define custom layer keys in yaml. + /// + public void LayerMapSet(Entity sprite, string key, int index) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (index < 0 || index >= sprite.Comp.Layers.Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + sprite.Comp.LayerMap[key] = index; + } + + /// + /// Map an enum to a layer index. + /// + public void LayerMapAdd(Entity sprite, Enum key, int index) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (index < 0 || index >= sprite.Comp.Layers.Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + sprite.Comp.LayerMap.Add(key, index); + } + + /// + /// Map a string to a layer index. If possible, it is preferred to use an enum key. + /// string keys mainly exist to make it easier to define custom layer keys in yaml. + /// + public void LayerMapAdd(Entity sprite, string key, int index) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (index < 0 || index >= sprite.Comp.Layers.Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + sprite.Comp.LayerMap.Add(key, index); + } + + /// + /// Remove an enum mapping. + /// + public bool LayerMapRemove(Entity sprite, Enum key) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return false; + + return sprite.Comp.LayerMap.Remove(key); + } + + /// + /// Remove a string mapping. + /// + public bool LayerMapRemove(Entity sprite, string key) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return false; + + return sprite.Comp.LayerMap.Remove(key); + } + + /// + /// Remove an enum mapping. + /// + public bool LayerMapRemove(Entity sprite, Enum key, out int index) + { + if (_query.Resolve(sprite.Owner, ref sprite.Comp)) + return sprite.Comp.LayerMap.Remove(key, out index); + + index = 0; + return false; + } + + /// + /// Remove a string mapping. + /// + public bool LayerMapRemove(Entity sprite, string key, out int index) + { + if (_query.Resolve(sprite.Owner, ref sprite.Comp)) + return sprite.Comp.LayerMap.Remove(key, out index); + + index = 0; + return false; + } + + /// + /// Attempt to resolve an enum mapping. + /// + public bool LayerMapTryGet(Entity sprite, Enum key, out int index, bool logMissing) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp, logMissing)) + { + index = 0; + return false; + } + + if (sprite.Comp.LayerMap.TryGetValue(key, out index)) + return true; + + if (logMissing) + Log.Error($"Layer with key '{key}' does not exist on entity {ToPrettyString(sprite)}! Trace:\n{Environment.StackTrace}"); + + return false; + } + + /// + /// Attempt to resolve a string mapping. + /// + public bool LayerMapTryGet(Entity sprite, string key, out int index, bool logMissing) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp, logMissing)) + { + index = 0; + return false; + } + + if (sprite.Comp.LayerMap.TryGetValue(key, out index)) + return true; + + if (logMissing) + Log.Error($"Layer with key '{key}' does not exist on entity {ToPrettyString(sprite)}! Trace:\n{Environment.StackTrace}"); + + return false; + } + + public int LayerMapGet(Entity sprite, Enum key) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return -1; + + return sprite.Comp.LayerMap[key]; + } + + public int LayerMapGet(Entity sprite, string key) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return -1; + + return sprite.Comp.LayerMap[key]; + } + + public bool LayerExists(Entity sprite, string key) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return false; + + return sprite.Comp.LayerMap.TryGetValue(key, out var index) + && LayerExists(sprite, index); + } + + public bool LayerExists(Entity sprite, Enum key) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return false; + + return sprite.Comp.LayerMap.TryGetValue(key, out var index) + && LayerExists(sprite, index); + } + + /// + /// Create a new blank layer and map the given key to it. + /// + public int LayerMapReserve(Entity sprite, Enum key) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return -1; + + if (LayerExists(sprite, key)) + throw new Exception("Layer already exists"); + + var index = AddBlankLayer(sprite); + LayerMapSet(sprite, key, index); + return index; + } + + /// + /// A create a new blank layer and map the given key to it. If possible, it is preferred to use an enum key. + /// string keys mainly exist to make it easier to define custom layer keys in yaml. + /// + public int LayerMapReserve(Entity sprite, string key) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return -1; + + if (LayerExists(sprite, key)) + throw new Exception("Layer already exists"); + + var index = AddBlankLayer(sprite); + LayerMapSet(sprite, key, index); + return index; + } + + public bool RemoveLayer(Entity sprite, string key, bool logMissing = true) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp, logMissing)) + return false; + + if (!LayerMapTryGet(sprite, key, out var index, logMissing)) + return false; + + return RemoveLayer(sprite, index, logMissing); + } + + public bool RemoveLayer(Entity sprite, Enum key, bool logMissing = true) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp, logMissing)) + return false; + + if (!LayerMapTryGet(sprite, key, out var index, logMissing)) + return false; + + return RemoveLayer(sprite, index, logMissing); + } + + public bool RemoveLayer( + Entity sprite, + string key, + [NotNullWhen(true)] out Layer? layer, + bool logMissing = true) + { + layer = null; + if (!_query.Resolve(sprite.Owner, ref sprite.Comp, logMissing)) + return false; + + if (!LayerMapTryGet(sprite, key, out var index, logMissing)) + return false; + + return RemoveLayer(sprite, index, out layer, logMissing); + } + + public bool RemoveLayer( + Entity sprite, + Enum key, + [NotNullWhen(true)] out Layer? layer, + bool logMissing = true) + { + layer = null; + if (!_query.Resolve(sprite.Owner, ref sprite.Comp, logMissing)) + return false; + + if (!LayerMapTryGet(sprite, key, out var index, logMissing)) + return false; + + return RemoveLayer(sprite, index, out layer, logMissing); + } +} diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Setters.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Setters.cs new file mode 100644 index 00000000000..860ea6152c6 --- /dev/null +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Setters.cs @@ -0,0 +1,135 @@ +using System; +using System.Numerics; +using Robust.Client.Graphics; +using Robust.Shared.GameObjects; +using Robust.Shared.Maths; + +namespace Robust.Client.GameObjects; + +// This partial class contains various public methods for setting sprite component data. +public sealed partial class SpriteSystem +{ + #region Transform + public void SetScale(Entity sprite, Vector2 value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (MathF.Abs(value.X) < 0.005f || MathF.Abs(value.Y) < 0.005f) + { + // Scales of ~0.0025 or lower can lead to singular matrices due to rounding errors. + Log.Error($"Attempted to set layer sprite scale to very small values. Entity: {ToPrettyString(sprite)}. Scale: {value}"); + return; + } + + sprite.Comp._bounds = sprite.Comp._bounds.Scale(value / sprite.Comp.scale); + sprite.Comp.scale = value; + sprite.Comp.LocalMatrix = Matrix3Helpers.CreateTransform( + in sprite.Comp.offset, + in sprite.Comp.rotation, + in sprite.Comp.scale); + } + + public void SetRotation(Entity sprite, Angle value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + sprite.Comp.rotation = value; + sprite.Comp.LocalMatrix = Matrix3Helpers.CreateTransform( + in sprite.Comp.offset, + in sprite.Comp.rotation, + in sprite.Comp.scale); + } + + public void SetOffset(Entity sprite, Vector2 value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + sprite.Comp.offset = value; + sprite.Comp.LocalMatrix = Matrix3Helpers.CreateTransform( + in sprite.Comp.offset, + in sprite.Comp.rotation, + in sprite.Comp.scale); + } + #endregion + + public void SetVisible(Entity sprite, bool value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (sprite.Comp.Visible == value) + return; + + sprite.Comp._visible = value; + if (!sprite.Comp.TreeUpdateQueued) + _tree.QueueTreeUpdate(sprite!); + } + + public void SetDrawDepth(Entity sprite, int value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + sprite.Comp.drawDepth = value; + } + + public void SetColor(Entity sprite, Color value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + sprite.Comp.color = value; + } + + public void SetBaseRsi(Entity sprite, RSI? value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (value == sprite.Comp._baseRsi) + return; + + sprite.Comp._baseRsi = value; + if (value == null) + return; + + for (var i = 0; i < sprite.Comp.Layers.Count; i++) + { + var layer = sprite.Comp.Layers[i]; + if (!layer.State.IsValid || layer.RSI != null) + continue; + + layer.UpdateActualState(); + + if (value.TryGetState(layer.State, out var state)) + { + layer.AnimationTimeLeft = state.GetDelay(0); + } + else + { + Log.Error($"Layer {i} no longer has state '{layer.State}' due to base RSI change. Trace:\n{Environment.StackTrace}"); + layer.Texture = null; + } + } + } + + public void SetContainerOccluded(Entity sprite, bool value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + sprite.Comp._containerOccluded = value; + _tree.QueueTreeUpdate(sprite!); + } + + public void SetShader(Entity sprite, ShaderInstance? shader) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + sprite.Comp.PostShader = shader; + } +} diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs index e92f0df258c..aad326091cf 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs @@ -36,6 +36,7 @@ public sealed partial class SpriteSystem : EntitySystem [Dependency] private readonly IResourceCache _resourceCache = default!; [Dependency] private readonly ILogManager _logManager = default!; [Dependency] private readonly SharedTransformSystem _xforms = default!; + [Dependency] private readonly SpriteTreeSystem _tree = default!; private readonly Queue _inertUpdateQueue = new(); @@ -45,6 +46,7 @@ public sealed partial class SpriteSystem : EntitySystem private readonly HashSet _queuedFrameUpdate = new(); private ISawmill _sawmill = default!; + private EntityQuery _query; internal void Render(EntityUid uid, SpriteComponent sprite, DrawingHandleWorld drawingHandle, Angle eyeRotation, in Angle worldRotation, in Vector2 worldPosition) { @@ -66,6 +68,7 @@ public override void Initialize() Subs.CVar(_cfg, CVars.RenderSpriteDirectionBias, OnBiasChanged, true); _sawmill = _logManager.GetSawmill("sprite"); + _query = GetEntityQuery(); } public bool IsVisible(Layer layer) diff --git a/Robust.Client/Graphics/Clyde/Clyde.HLR.cs b/Robust.Client/Graphics/Clyde/Clyde.HLR.cs index ea9271562a3..933327331a9 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.HLR.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.HLR.cs @@ -304,7 +304,7 @@ private void DrawEntities(Viewport viewport, Box2Rotated worldBounds, Box2 world screenSpriteSize.Y++; bool exit = false; - if (entry.Sprite.GetScreenTexture) + if (entry.Sprite.GetScreenTexture && entry.Sprite.PostShader != null) { FlushRenderQueue(); var tex = CopyScreenTexture(viewport.RenderTarget); diff --git a/Robust.Shared/ComponentTrees/ComponentTreeSystem.cs b/Robust.Shared/ComponentTrees/ComponentTreeSystem.cs index 45a418d71a6..6a737675257 100644 --- a/Robust.Shared/ComponentTrees/ComponentTreeSystem.cs +++ b/Robust.Shared/ComponentTrees/ComponentTreeSystem.cs @@ -111,6 +111,11 @@ public void QueueTreeUpdate(EntityUid uid, TComp component, TransformComponent? component.TreeUpdateQueued = true; _updateQueue.Enqueue((component, xform)); } + + public void QueueTreeUpdate(Entity entity, TransformComponent? xform = null) + { + QueueTreeUpdate(entity.Owner, entity.Comp, xform); + } #endregion #region Component Management From 1d117cae412a11a356fd1918db41e907ebaee57d Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Mon, 13 Jan 2025 04:20:14 +1300 Subject: [PATCH 02/36] release notes --- RELEASE-NOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 93b7361a5db..ce040842009 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -47,7 +47,7 @@ END TEMPLATE--> ### Other -*None yet* +* Several SpriteComponent methods have been marked as obsolete, and should be replaced with new methods in SpriteSystem. ### Internal From 47940dc7441726320fc84e823fef0c6974781a94 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Mon, 13 Jan 2025 18:12:48 +1300 Subject: [PATCH 03/36] tests --- .../GameObjects/Components/Renderable/SpriteComponent.cs | 3 ++- Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index 1c5470aaee3..f99300835c6 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -459,7 +459,8 @@ public void RemoveLayer(object layerKey) [Obsolete("Use SpriteSystem.RebuildBounds() instead.")] internal void RebuildBounds() { - if (entities.Initialized && entities.TrySystem(out SpriteSystem? sys)) + // I Love ISerializationHooks & inconsistent initialization ordering between server, client, and tests. + if (entities.Started && entities.TrySystem(out SpriteSystem? sys)) sys.RebuildBounds((Owner, this)); } diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs index b2d589f7a88..29d9e79f232 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs @@ -111,7 +111,7 @@ public int AddLayer(Entity sprite, Layer layer, int? index = n sprite.Comp.Layers.Add(layer); } - sprite.Comp.RebuildBounds(); + RebuildBounds(sprite!); sprite.Comp.QueueUpdateIsInert(); return index.Value; } From 1eef1aa4b2486ed949976f93f69c953334a7780e Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Mon, 13 Jan 2025 18:47:21 +1300 Subject: [PATCH 04/36] Why --- .../Components/Renderable/SpriteComponent.cs | 93 +++++++------------ .../EntitySystems/SpriteSystem.Component.cs | 19 ++++ .../EntitySystems/SpriteSystem.Layer.cs | 4 +- .../GameObjects/EntitySystems/SpriteSystem.cs | 13 --- 4 files changed, 54 insertions(+), 75 deletions(-) diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index f99300835c6..84140a033a8 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -74,8 +74,8 @@ public bool Visible private SpriteSystem? _sys; private SpriteTreeSystem? _treeSys; - private SpriteSystem Sys => _sys ??= entities.System(); - private SpriteTreeSystem TreeSys => _treeSys ??= entities.System(); + private SpriteSystem Sys => _sys ??= (entities.Started ? entities.System() : null)!; + private SpriteTreeSystem TreeSys => _treeSys ??= (entities.Started ? entities.System() : null)!; [DataField("drawdepth", customTypeSerializer: typeof(ConstantSerializer))] internal int drawDepth = DrawDepthTag.Default; @@ -456,14 +456,6 @@ public void RemoveLayer(object layerKey) RemoveLayer(layer); } - [Obsolete("Use SpriteSystem.RebuildBounds() instead.")] - internal void RebuildBounds() - { - // I Love ISerializationHooks & inconsistent initialization ordering between server, client, and tests. - if (entities.Started && entities.TrySystem(out SpriteSystem? sys)) - sys.RebuildBounds((Owner, this)); - } - /// /// Fills in a layer's values using some . /// @@ -595,7 +587,8 @@ private void LayerSetData(Layer layer, int index, PrototypeLayerData layerDatum) layer.CopyToShaderParameters = null; } - RebuildBounds(); + // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract + Sys?.RebuildBounds((Owner, this)); } private object ParseKey(string keyString) @@ -682,8 +675,8 @@ public void LayerSetTexture(int layer, Texture? texture) return; theLayer.SetTexture(texture); - QueueUpdateIsInert(); - RebuildBounds(); + Sys.QueueUpdateIsInert((Owner, this)); + Sys.RebuildBounds((Owner, this)); } public void LayerSetTexture(object layerKey, Texture texture) @@ -735,7 +728,7 @@ public void LayerSetState(int layer, RSI.StateId stateId) if (!TryGetLayer(layer, out var theLayer, true)) return; theLayer.SetState(stateId); - RebuildBounds(); + Sys.RebuildBounds((Owner, this)); } public void LayerSetState(object layerKey, RSI.StateId stateId) @@ -774,8 +767,8 @@ public void LayerSetState(int layer, RSI.StateId stateId, RSI? rsi) } } - QueueUpdateIsInert(); - RebuildBounds(); + Sys.QueueUpdateIsInert((Owner, this)); + Sys.RebuildBounds((Owner, this)); } public void LayerSetState(object layerKey, RSI.StateId stateId, RSI rsi) @@ -819,7 +812,7 @@ public void LayerSetRSI(int layer, RSI? rsi) if (!TryGetLayer(layer, out var theLayer, true)) return; theLayer.SetRsi(rsi); - RebuildBounds(); + Sys.RebuildBounds((Owner, this)); } public void LayerSetRSI(object layerKey, RSI rsi) @@ -863,7 +856,7 @@ public void LayerSetScale(int layer, Vector2 scale) if (!TryGetLayer(layer, out var theLayer, true)) return; theLayer.Scale = scale; - RebuildBounds(); + Sys.RebuildBounds((Owner, this)); } public void LayerSetScale(object layerKey, Vector2 scale) @@ -880,7 +873,7 @@ public void LayerSetRotation(int layer, Angle rotation) if (!TryGetLayer(layer, out var theLayer, true)) return; theLayer.Rotation = rotation; - RebuildBounds(); + Sys.RebuildBounds((Owner, this)); } public void LayerSetRotation(object layerKey, Angle rotation) @@ -913,8 +906,7 @@ public void LayerSetColor(int layer, Color color) return; theLayer.Color = color; - - RebuildBounds(); + Sys.RebuildBounds((Owner, this)); } public void LayerSetColor(object layerKey, Color color) @@ -931,8 +923,7 @@ public void LayerSetDirOffset(int layer, DirectionOffset offset) return; theLayer.DirOffset = offset; - - RebuildBounds(); + Sys.RebuildBounds((Owner, this)); } public void LayerSetDirOffset(object layerKey, DirectionOffset offset) @@ -981,8 +972,7 @@ public void LayerSetOffset(int layer, Vector2 layerOffset) return; theLayer.Offset = layerOffset; - - RebuildBounds(); + Sys.RebuildBounds((Owner, this)); } public void LayerSetOffset(object layerKey, Vector2 layerOffset) @@ -1054,7 +1044,7 @@ public bool SnapCardinals return; _snapCardinals = value; - RebuildBounds(); + Sys.RebuildBounds((Owner, this)); } } @@ -1156,21 +1146,6 @@ public int GetLayerDirectionCount(ISpriteLayer layer) }; } - private void QueueUpdateRenderTree() - { - TreeSys.QueueTreeUpdate((Owner, this)); - } - - internal void QueueUpdateIsInert() - { - if (_inertUpdateQueued || !Owner.IsValid()) - return; - - // TODO whenever sprite comp gets ECS'd , just make this a direct method call. - var ev = new SpriteUpdateInertEvent(); - entities.EventBus.RaiseComponentEvent(Owner, this, ref ev); - } - [Obsolete("Use SpriteSystem instead.")] internal static RSI.State GetFallbackState(IResourceCache cache) { @@ -1343,7 +1318,7 @@ public Vector2 Scale _scale = value; UpdateLocalMatrix(); - _parent.RebuildBounds(); + _parent.Sys.RebuildBounds((_parent.Owner, _parent)); } } internal Vector2 _scale = Vector2.One; @@ -1358,7 +1333,7 @@ public Angle Rotation _rotation = value; UpdateLocalMatrix(); - _parent.RebuildBounds(); + _parent.Sys.RebuildBounds((_parent.Owner, _parent)); } } internal Angle _rotation = Angle.Zero; @@ -1374,8 +1349,9 @@ public bool Visible return; _visible = value; - _parent.QueueUpdateIsInert(); - _parent.RebuildBounds(); + // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract + _parent.Sys?.QueueUpdateIsInert((_parent.Owner, _parent)); + _parent.Sys?.RebuildBounds((_parent.Owner, _parent)); } } @@ -1395,7 +1371,8 @@ public bool AutoAnimated if (_autoAnimated == value) return; _autoAnimated = value; - _parent.QueueUpdateIsInert(); + // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract + _parent.Sys?.QueueUpdateIsInert((_parent.Owner, _parent)); } } @@ -1409,7 +1386,8 @@ public Vector2 Offset _offset = value; UpdateLocalMatrix(); - _parent.RebuildBounds(); + // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract + _parent.Sys?.RebuildBounds((_parent.Owner, _parent)); } } @@ -1581,7 +1559,7 @@ public void SetAutoAnimated(bool value) { AutoAnimated = value; - _parent.QueueUpdateIsInert(); + _parent.Sys.QueueUpdateIsInert((_parent.Owner, _parent)); } public void SetRsi(RSI? rsi) @@ -1613,8 +1591,8 @@ public void SetRsi(RSI? rsi) } } - _parent.QueueUpdateRenderTree(); - _parent.QueueUpdateIsInert(); + _parent.TreeSys.QueueTreeUpdate((_parent.Owner, _parent)); + _parent.Sys.QueueUpdateIsInert((_parent.Owner, _parent)); } public void SetState(RSI.StateId stateId) @@ -1646,7 +1624,7 @@ public void SetState(RSI.StateId stateId) AnimationTime = 0; AnimationTimeLeft = state.GetDelay(0); - _parent.QueueUpdateIsInert(); + _parent.Sys.QueueUpdateIsInert((_parent.Owner, _parent)); } public void SetTexture(Texture? texture) @@ -1654,8 +1632,8 @@ public void SetTexture(Texture? texture) State = default; Texture = texture; - _parent.QueueUpdateRenderTree(); - _parent.QueueUpdateIsInert(); + _parent.TreeSys.QueueTreeUpdate((_parent.Owner, _parent)); + _parent.Sys.QueueUpdateIsInert((_parent.Owner, _parent)); } /// @@ -1723,7 +1701,8 @@ public Box2 CalculateBoundingBox() /// internal void UpdateActualState() { - _parent.QueueUpdateIsInert(); + // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract + _parent.Sys?.QueueUpdateIsInert((_parent.Owner, _parent)); if (!State.IsValid) { _actualState = null; @@ -2078,10 +2057,4 @@ public static IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourc return result; } } - - - [ByRefEvent] - internal struct SpriteUpdateInertEvent - { - } } diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Component.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Component.cs index 97e53e499af..c7b37fed4fa 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Component.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Component.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Robust.Shared.GameObjects; @@ -86,6 +87,9 @@ public void CopySprite(SpriteComponent source, SpriteComponent target) public void RebuildBounds(Entity sprite) { + // Maybe the bounds calculation should be deferred? + // The tree update is already deferred anyways. + var bounds = new Box2(); foreach (var layer in sprite.Comp.Layers) { @@ -96,4 +100,19 @@ public void RebuildBounds(Entity sprite) sprite.Comp._bounds = bounds.Scale(sprite.Comp.Scale); _tree.QueueTreeUpdate(sprite); } + + /// + /// Adds a sprite to a queue that will update next frame. + /// + public void QueueUpdateIsInert(Entity sprite) + { + if (sprite.Comp._inertUpdateQueued) + return; + + sprite.Comp._inertUpdateQueued = true; + _inertUpdateQueue.Enqueue(sprite); + } + + [Obsolete("Use QueueUpdateIsInert")] + public void QueueUpdateInert(EntityUid uid, SpriteComponent sprite) => QueueUpdateIsInert(new (uid, sprite)); } diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs index 29d9e79f232..87ef9d40dfc 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs @@ -80,7 +80,7 @@ public bool RemoveLayer( } RebuildBounds(sprite!); - sprite.Comp.QueueUpdateIsInert(); + QueueUpdateIsInert(sprite!); return true; } @@ -112,7 +112,7 @@ public int AddLayer(Entity sprite, Layer layer, int? index = n } RebuildBounds(sprite!); - sprite.Comp.QueueUpdateIsInert(); + QueueUpdateIsInert(sprite!); return index.Value; } diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs index aad326091cf..cb3913bd508 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs @@ -63,7 +63,6 @@ public override void Initialize() UpdatesAfter.Add(typeof(SpriteTreeSystem)); SubscribeLocalEvent(OnPrototypesReloaded); - SubscribeLocalEvent(QueueUpdateInert); SubscribeLocalEvent(OnInit); Subs.CVar(_cfg, CVars.RenderSpriteDirectionBias, OnBiasChanged, true); @@ -87,18 +86,6 @@ private void OnBiasChanged(double value) SpriteComponent.DirectionBias = value; } - private void QueueUpdateInert(EntityUid uid, SpriteComponent sprite, ref SpriteUpdateInertEvent ev) - => QueueUpdateInert(uid, sprite); - - public void QueueUpdateInert(EntityUid uid, SpriteComponent sprite) - { - if (sprite._inertUpdateQueued) - return; - - sprite._inertUpdateQueued = true; - _inertUpdateQueue.Enqueue(sprite); - } - private void DoUpdateIsInert(SpriteComponent component) { component._inertUpdateQueued = false; From 1b61680aee2144c1aefbdf0e48b02556e965c052 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Mon, 13 Jan 2025 18:59:09 +1300 Subject: [PATCH 05/36] SetSnapCardinals --- .../Components/Renderable/SpriteComponent.cs | 35 +++++++++---------- .../EntitySystems/SpriteSystem.Component.cs | 2 ++ .../EntitySystems/SpriteSystem.Setters.cs | 12 +++++++ 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index 84140a033a8..a5c970a7f61 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -38,6 +38,7 @@ namespace Robust.Client.GameObjects [RegisterComponent] public sealed partial class SpriteComponent : Component, IComponentDebug, ISerializationHooks, IComponentTreeEntry, IAnimationProperties { + #region ECSd public const string LogCategory = "go.comp.sprite"; [Dependency] private readonly IResourceCache resourceCache = default!; @@ -456,6 +457,21 @@ public void RemoveLayer(object layerKey) RemoveLayer(layer); } + [DataField("snapCardinals")] + internal bool _snapCardinals = false; + + /// + /// If the sprite only has 1 direction should it snap at cardinals if rotated. + /// + [ViewVariables(VVAccess.ReadWrite)] + public bool SnapCardinals + { + get => _snapCardinals; + [Obsolete("Use SpriteSystem.SnapCardinals() instead.")] + set => Sys.SetSnapCardinals((Owner, this), value); + } + + #endregion /// /// Fills in a layer's values using some . /// @@ -1031,25 +1047,6 @@ public void Render(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle wo [DataField("noRot")] internal bool _screenLock = false; - /// - /// If the sprite only has 1 direction should it snap at cardinals if rotated. - /// - [ViewVariables(VVAccess.ReadWrite)] - public bool SnapCardinals - { - get => _snapCardinals; - set - { - if (value == _snapCardinals) - return; - - _snapCardinals = value; - Sys.RebuildBounds((Owner, this)); - } - } - - [DataField("snapCardinals")] - private bool _snapCardinals = false; [DataField("overrideDir")] [ViewVariables(VVAccess.ReadWrite)] diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Component.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Component.cs index c7b37fed4fa..80e2490a5b9 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Component.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Component.cs @@ -87,8 +87,10 @@ public void CopySprite(SpriteComponent source, SpriteComponent target) public void RebuildBounds(Entity sprite) { + // TODO SPRITE // Maybe the bounds calculation should be deferred? // The tree update is already deferred anyways. + // And layer.CalculateBoundingBox() is relatively expensive. var bounds = new Box2(); foreach (var layer in sprite.Comp.Layers) diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Setters.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Setters.cs index 860ea6152c6..ffaf728363a 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Setters.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Setters.cs @@ -132,4 +132,16 @@ public void SetShader(Entity sprite, ShaderInstance? shader) sprite.Comp.PostShader = shader; } + + public void SetSnapCardinals(Entity sprite, bool value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (value == sprite.Comp._snapCardinals) + return; + + sprite.Comp._snapCardinals = value; + RebuildBounds(sprite!); + } } From 62c7f5f9553dad5bb5dcf4cfaa26bb80e1642c64 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Mon, 13 Jan 2025 19:00:12 +1300 Subject: [PATCH 06/36] NoRotation --- .../Components/Renderable/SpriteComponent.cs | 18 +++++++++++------- .../EntitySystems/SpriteSystem.Component.cs | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index a5c970a7f61..b3a396d4140 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -471,6 +471,17 @@ public bool SnapCardinals set => Sys.SetSnapCardinals((Owner, this), value); } + /// + /// If true, the sprite will always be rendered as if its world rotation relative to the screen's eye is 0. + /// Note for 4- or 8- directional sprites, the relative rotation is still used to choose the RSI state. + /// + /// + /// E.g., this is used to ensure that players/mobs are always standing upright, but the sprite will still change + /// based on the direction that a mob is looking in. + /// + [DataField("noRot")] + public bool NoRotation; + #endregion /// /// Fills in a layer's values using some . @@ -1045,9 +1056,6 @@ public void Render(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle wo RenderInternal(drawingHandle, eyeRotation, worldRotation, position, overrideDirection); } - [DataField("noRot")] internal bool _screenLock = false; - - [DataField("overrideDir")] [ViewVariables(VVAccess.ReadWrite)] public Direction DirectionOverride = Direction.East; @@ -1056,10 +1064,6 @@ public void Render(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle wo [ViewVariables(VVAccess.ReadWrite)] public bool EnableDirectionOverride; - /// - [ViewVariables(VVAccess.ReadWrite)] - public bool NoRotation { get => _screenLock; set => _screenLock = value; } - internal void RenderInternal(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Vector2 worldPosition, Direction? overrideDirection) { var angle = worldRotation + eyeRotation; // angle on-screen. Used to decide the direction of 4/8 directional RSIs diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Component.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Component.cs index 80e2490a5b9..325fb6abf9d 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Component.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Component.cs @@ -69,7 +69,7 @@ public void CopySprite(SpriteComponent source, SpriteComponent target) target.scale = source.scale; target.LocalMatrix = Matrix3Helpers.CreateTransform(in target.offset, in target.rotation, in target.scale); target.drawDepth = source.drawDepth; - target._screenLock = source._screenLock; + target.NoRotation = source.NoRotation; target.DirectionOverride = source.DirectionOverride; target.EnableDirectionOverride = source.EnableDirectionOverride; target.Layers = new List(source.Layers.Count); From 81b01456a173ce218b3fba8a9b66bc4aa3882666 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Mon, 13 Jan 2025 19:00:46 +1300 Subject: [PATCH 07/36] DirectionOverride --- .../Components/Renderable/SpriteComponent.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index b3a396d4140..f2f81cf114c 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -482,7 +482,14 @@ public bool SnapCardinals [DataField("noRot")] public bool NoRotation; + [DataField("overrideDir")] + public Direction DirectionOverride = Direction.East; + + [DataField("enableOverrideDir")] + public bool EnableDirectionOverride; + #endregion + /// /// Fills in a layer's values using some . /// @@ -1056,14 +1063,6 @@ public void Render(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle wo RenderInternal(drawingHandle, eyeRotation, worldRotation, position, overrideDirection); } - [DataField("overrideDir")] - [ViewVariables(VVAccess.ReadWrite)] - public Direction DirectionOverride = Direction.East; - - [DataField("enableOverrideDir")] - [ViewVariables(VVAccess.ReadWrite)] - public bool EnableDirectionOverride; - internal void RenderInternal(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Vector2 worldPosition, Direction? overrideDirection) { var angle = worldRotation + eyeRotation; // angle on-screen. Used to decide the direction of 4/8 directional RSIs From e7f39701160d02ab99929907101e44f26a190249 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Mon, 13 Jan 2025 19:04:30 +1300 Subject: [PATCH 08/36] This is why I love distinct overrides that take in object --- Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs index 87ef9d40dfc..27c2fd5d890 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs @@ -202,7 +202,7 @@ public int AddLayer(Entity sprite, PrototypeLayerData layerDat return -1; index = AddBlankLayer(sprite, index); - sprite.Comp.LayerSetData(index, layerDatum); + sprite.Comp.LayerSetData(index.Value, layerDatum); return index.Value; } From 8cb228386e4c37c403bf198f9f3b724853c6f09a Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Mon, 13 Jan 2025 19:14:54 +1300 Subject: [PATCH 09/36] LayerSetData --- .../Components/Renderable/SpriteComponent.cs | 29 ++++++------- .../EntitySystems/SpriteSystem.Layer.cs | 3 +- .../SpriteSystem.LayerSetters.cs | 42 +++++++++++++++++++ 3 files changed, 57 insertions(+), 17 deletions(-) create mode 100644 Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index f2f81cf114c..e82c1701a68 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -488,18 +488,12 @@ public bool SnapCardinals [DataField("enableOverrideDir")] public bool EnableDirectionOverride; - #endregion - - /// - /// Fills in a layer's values using some . - /// + [Obsolete("Use SpriteSystem.LayerSetData() instead.")] public void LayerSetData(int index, PrototypeLayerData layerDatum) - { - if (TryGetLayer(index, out var layer)) - LayerSetData(layer, index, layerDatum); - } + => Sys.LayerSetData((Owner, this), index, layerDatum); - private void LayerSetData(Layer layer, int index, PrototypeLayerData layerDatum) + [Obsolete("Use SpriteSystem.LayerSetData() instead.")] + internal void LayerSetData(Layer layer, int index, PrototypeLayerData layerDatum) { if (!string.IsNullOrWhiteSpace(layerDatum.RsiPath)) { @@ -623,16 +617,17 @@ private void LayerSetData(Layer layer, int index, PrototypeLayerData layerDatum) // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract Sys?.RebuildBounds((Owner, this)); - } - private object ParseKey(string keyString) - { - if (reflection.TryParseEnumReference(keyString, out var @enum)) - return @enum; + object ParseKey(string keyString) + { + if (reflection.TryParseEnumReference(keyString, out var @enum)) + return @enum; - return keyString; + return keyString; + } } + [Obsolete("Use SpriteSystem.LayerSetData() instead.")] public void LayerSetData(object layerKey, PrototypeLayerData data) { if (!LayerMapTryGet(layerKey, out var layer, true)) @@ -641,6 +636,8 @@ public void LayerSetData(object layerKey, PrototypeLayerData data) LayerSetData(layer, data); } + #endregion + public void LayerSetShader(int layer, ShaderInstance? shader, string? prototype = null) { if (!TryGetLayer(layer, out var theLayer, true)) diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs index 27c2fd5d890..7f52b0db5ef 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs @@ -9,7 +9,8 @@ namespace Robust.Client.GameObjects; -// This partial class contains various public methods for manipulating layers. +// 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) diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs new file mode 100644 index 00000000000..59fac5a2ec2 --- /dev/null +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs @@ -0,0 +1,42 @@ +using System; +using Robust.Shared.GameObjects; +using Robust.Shared.Utility; +using static Robust.Client.GameObjects.SpriteComponent; +#pragma warning disable CS0618 // Type or member is obsolete + +namespace Robust.Client.GameObjects; + +// This partial class contains various public methods for modifying a layer's properties. +public sealed partial class SpriteSystem +{ + #region LayerSetData + + public void LayerSetData(Entity sprite, int index, PrototypeLayerData data) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (TryGetLayer(sprite, index, out var layer, true)) + LayerSetData(sprite!, layer, index, data); + } + + internal void LayerSetData(Entity sprite, Layer layer, int index, PrototypeLayerData data) + { + DebugTools.AssertEqual(sprite.Comp.Layers[index], layer); + sprite.Comp.LayerSetData(layer, index, data); + } + + public void LayerSetData(Entity sprite, string key, PrototypeLayerData data) + { + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetData(sprite, index, data); + } + + public void LayerSetData(Entity sprite, Enum key, PrototypeLayerData data) + { + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetData(sprite, index, data); + } + + #endregion +} From 33d3b7e34377d61d1769b8338f31ffbfad2c28a5 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Mon, 13 Jan 2025 19:21:32 +1300 Subject: [PATCH 10/36] ISerializationHooks continue to haunt me --- .../GameObjects/Components/Renderable/SpriteComponent.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index e82c1701a68..f5b5ec0c8ee 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -616,7 +616,8 @@ internal void LayerSetData(Layer layer, int index, PrototypeLayerData layerDatum } // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract - Sys?.RebuildBounds((Owner, this)); + if (Owner != EntityUid.Invalid) + Sys?.RebuildBounds((Owner, this)); object ParseKey(string keyString) { From 3b31b49e2c3f23e5433111db0b8fe716590d3a59 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Mon, 13 Jan 2025 19:23:02 +1300 Subject: [PATCH 11/36] Relocate SetShader --- .../Components/Renderable/SpriteComponent.cs | 88 ++++++++++--------- 1 file changed, 45 insertions(+), 43 deletions(-) diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index f5b5ec0c8ee..ab94c742b6a 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -239,6 +239,11 @@ public ShaderInstance? PostShader [ViewVariables(VVAccess.ReadWrite)] public bool IsInert { get; internal set; } + public ISpriteLayer this[int layer] => Layers[layer]; + public ISpriteLayer this[Index layer] => Layers[layer]; + public ISpriteLayer this[object layerKey] => this[LayerMap[layerKey]]; + public IEnumerable AllLayers => Layers; + void ISerializationHooks.AfterDeserialization() { // Please somebody burn this to the ground. There is so much spaghetti. @@ -639,45 +644,6 @@ public void LayerSetData(object layerKey, PrototypeLayerData data) #endregion - public void LayerSetShader(int layer, ShaderInstance? shader, string? prototype = null) - { - if (!TryGetLayer(layer, out var theLayer, true)) - return; - - theLayer.Shader = shader; - theLayer.ShaderPrototype = prototype; - } - - public void LayerSetShader(object layerKey, ShaderInstance shader, string? prototype = null) - { - if (!LayerMapTryGet(layerKey, out var layer, true)) - return; - - LayerSetShader(layer, shader, prototype); - } - - public void LayerSetShader(int layer, string shaderName) - { - if (!prototypes.TryIndex(shaderName, out var prototype)) - { - Logger.ErrorS(LogCategory, "Shader prototype '{0}' does not exist. Trace:\n{1}", shaderName, - Environment.StackTrace); - - LayerSetShader(layer, null, null); - return; - } - - LayerSetShader(layer, prototype.Instance(), shaderName); - } - - public void LayerSetShader(object layerKey, string shaderName) - { - if (!LayerMapTryGet(layerKey, out var layer, true)) - return; - - LayerSetShader(layer, shaderName); - } - public void LayerSetSprite(int layer, SpriteSpecifier specifier) { switch (specifier) @@ -1050,10 +1016,46 @@ public RSI.StateId LayerGetState(int layer) return this[layerKey].ActualRsi; } - public ISpriteLayer this[int layer] => Layers[layer]; - public ISpriteLayer this[Index layer] => Layers[layer]; - public ISpriteLayer this[object layerKey] => this[LayerMap[layerKey]]; - public IEnumerable AllLayers => Layers; + public void LayerSetShader(int layer, ShaderInstance? shader, string? prototype = null) + { + if (!TryGetLayer(layer, out var theLayer, true)) + return; + + theLayer.Shader = shader; + theLayer.ShaderPrototype = prototype; + } + + public void LayerSetShader(object layerKey, ShaderInstance shader, string? prototype = null) + { + if (!LayerMapTryGet(layerKey, out var layer, true)) + return; + + LayerSetShader(layer, shader, prototype); + } + + public void LayerSetShader(int layer, string shaderName) + { + if (!prototypes.TryIndex(shaderName, out var prototype)) + { + Logger.ErrorS(LogCategory, + "Shader prototype '{0}' does not exist. Trace:\n{1}", + shaderName, + Environment.StackTrace); + + LayerSetShader(layer, null, null); + return; + } + + LayerSetShader(layer, prototype.Instance(), shaderName); + } + + public void LayerSetShader(object layerKey, string shaderName) + { + if (!LayerMapTryGet(layerKey, out var layer, true)) + return; + + LayerSetShader(layer, shaderName); + } // Lobby SpriteView rendering path public void Render(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Direction? overrideDirection = null, Vector2 position = default) From d14931c31edd45d575b9935a857f1dd2ecb2d51b Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Mon, 13 Jan 2025 19:26:51 +1300 Subject: [PATCH 12/36] LayerSetSprite --- .../Components/Renderable/SpriteComponent.cs | 6 ++- .../SpriteSystem.LayerSetters.cs | 41 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index ab94c742b6a..fad1e972829 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -642,8 +642,7 @@ public void LayerSetData(object layerKey, PrototypeLayerData data) LayerSetData(layer, data); } - #endregion - + [Obsolete("Use SpriteSystem.LayerSetSprite() instead.")] public void LayerSetSprite(int layer, SpriteSpecifier specifier) { switch (specifier) @@ -659,6 +658,7 @@ public void LayerSetSprite(int layer, SpriteSpecifier specifier) } } + [Obsolete("Use SpriteSystem.LayerSetSprite() instead.")] public void LayerSetSprite(object layerKey, SpriteSpecifier specifier) { if (!LayerMapTryGet(layerKey, out var layer, true)) @@ -667,6 +667,8 @@ public void LayerSetSprite(object layerKey, SpriteSpecifier specifier) LayerSetSprite(layer, specifier); } + #endregion + public void LayerSetTexture(int layer, Texture? texture) { if (!TryGetLayer(layer, out var theLayer, true)) diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs index 59fac5a2ec2..ad6738ec664 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs @@ -39,4 +39,45 @@ public void LayerSetData(Entity sprite, Enum key, PrototypeLay } #endregion + + #region LayerSetSprite + + public void LayerSetSprite(Entity sprite, int index, SpriteSpecifier specifier) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (!TryGetLayer(sprite, index, out var layer, true)) + return; + + switch (specifier) + { + case SpriteSpecifier.Texture tex: + LayerSetTexture(sprite, layer, tex.TexturePath); + break; + case SpriteSpecifier.Rsi rsi: + LayerSetState(sprite, layer, rsi.RsiState, rsi.RsiPath); + break; + default: + throw new NotImplementedException(); + } + } + + public void LayerSetSprite(Entity sprite, string key, SpriteSpecifier specifier) + { + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetSprite(sprite, index, specifier); + } + + public void LayerSetSprite(Entity sprite, Enum key, SpriteSpecifier specifier) + { + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetSprite(sprite, index, specifier); + } + + #endregion + + #region LayerSetTexture + + #endregion } From 1484d43fcc2888a6d8e735aeb3180eb41c7c4352 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Mon, 13 Jan 2025 19:49:17 +1300 Subject: [PATCH 13/36] LayerSetTexture --- .../Components/Renderable/SpriteComponent.cs | 10 ++- .../SpriteSystem.LayerSetters.cs | 63 ++++++++++++++++--- 2 files changed, 63 insertions(+), 10 deletions(-) diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index fad1e972829..3b24f74ab09 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -667,8 +667,7 @@ public void LayerSetSprite(object layerKey, SpriteSpecifier specifier) LayerSetSprite(layer, specifier); } - #endregion - + [Obsolete("Use SpriteSystem.LayerSetTexture() instead.")] public void LayerSetTexture(int layer, Texture? texture) { if (!TryGetLayer(layer, out var theLayer, true)) @@ -679,6 +678,7 @@ public void LayerSetTexture(int layer, Texture? texture) Sys.RebuildBounds((Owner, this)); } + [Obsolete("Use SpriteSystem.LayerSetTexture() instead.")] public void LayerSetTexture(object layerKey, Texture texture) { if (!LayerMapTryGet(layerKey, out var layer, true)) @@ -687,16 +687,19 @@ public void LayerSetTexture(object layerKey, Texture texture) LayerSetTexture(layer, texture); } + [Obsolete("Use SpriteSystem.LayerSetTexture() instead.")] public void LayerSetTexture(int layer, string texturePath) { LayerSetTexture(layer, new ResPath(texturePath)); } + [Obsolete("Use SpriteSystem.LayerSetTexture() instead.")] public void LayerSetTexture(object layerKey, string texturePath) { LayerSetTexture(layerKey, new ResPath(texturePath)); } + [Obsolete("Use SpriteSystem.LayerSetTexture() instead.")] public void LayerSetTexture(int layer, ResPath texturePath) { if (!resourceCache.TryGetResource(TextureRoot / texturePath, out var texture)) @@ -715,6 +718,7 @@ public void LayerSetTexture(int layer, ResPath texturePath) LayerSetTexture(layer, texture?.Texture); } + [Obsolete("Use SpriteSystem.LayerSetTexture() instead.")] public void LayerSetTexture(object layerKey, ResPath texturePath) { if (!LayerMapTryGet(layerKey, out var layer, true)) @@ -723,6 +727,8 @@ public void LayerSetTexture(object layerKey, ResPath texturePath) LayerSetTexture(layer, texturePath); } + #endregion + public void LayerSetState(int layer, RSI.StateId stateId) { if (!TryGetLayer(layer, out var theLayer, true)) diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs index ad6738ec664..fab633a15e3 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs @@ -1,5 +1,8 @@ using System; +using Robust.Client.Graphics; +using Robust.Client.ResourceManagement; using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.TypeSerializers.Implementations; using Robust.Shared.Utility; using static Robust.Client.GameObjects.SpriteComponent; #pragma warning disable CS0618 // Type or member is obsolete @@ -44,19 +47,13 @@ public void LayerSetData(Entity sprite, Enum key, PrototypeLay public void LayerSetSprite(Entity sprite, int index, SpriteSpecifier specifier) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - - if (!TryGetLayer(sprite, index, out var layer, true)) - return; - switch (specifier) { case SpriteSpecifier.Texture tex: - LayerSetTexture(sprite, layer, tex.TexturePath); + LayerSetTexture(sprite, index, tex.TexturePath); break; case SpriteSpecifier.Rsi rsi: - LayerSetState(sprite, layer, rsi.RsiState, rsi.RsiPath); + //LayerSetState(sprite, layer, rsi.RsiState, rsi.RsiPath); break; default: throw new NotImplementedException(); @@ -79,5 +76,55 @@ public void LayerSetSprite(Entity sprite, Enum key, SpriteSpec #region LayerSetTexture + public void LayerSetTexture(Entity sprite, int index, Texture? texture) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (!TryGetLayer(sprite, index, out var layer, true)) + return; + + layer.State = default; + layer.Texture = texture; + QueueUpdateIsInert(sprite!); + RebuildBounds(sprite!); + } + + public void LayerSetTexture(Entity sprite, string key, Texture? texture) + { + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetTexture(sprite, index, texture); + } + + public void LayerSetTexture(Entity sprite, Enum key, Texture? texture) + { + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetTexture(sprite, index, texture); + } + + public void LayerSetTexture(Entity sprite, int index, ResPath path) + { + if (!_resourceCache.TryGetResource(SpriteSpecifierSerializer.TextureRoot / path, out var texture)) + { + 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}"); + } + + LayerSetTexture(sprite, index, texture?.Texture); + } + + public void LayerSetTexture(Entity sprite, string key, ResPath texture) + { + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetTexture(sprite, index, texture); + } + + public void LayerSetTexture(Entity sprite, Enum key, ResPath texture) + { + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetTexture(sprite, index, texture); + } + #endregion } From 7212a2067a846794b255b08ef0bc9611e19a81a5 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Mon, 13 Jan 2025 19:54:49 +1300 Subject: [PATCH 14/36] yipeeeee --- .../Components/Renderable/SpriteComponent.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index 3b24f74ab09..343cf1fe196 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -1358,8 +1358,11 @@ public bool Visible _visible = value; // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract - _parent.Sys?.QueueUpdateIsInert((_parent.Owner, _parent)); - _parent.Sys?.RebuildBounds((_parent.Owner, _parent)); + if (_parent.Owner != EntityUid.Invalid) + _parent.Sys?.QueueUpdateIsInert((_parent.Owner, _parent)); + + if (_parent.Owner != EntityUid.Invalid) + _parent.Sys?.RebuildBounds((_parent.Owner, _parent)); } } @@ -1380,7 +1383,8 @@ public bool AutoAnimated return; _autoAnimated = value; // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract - _parent.Sys?.QueueUpdateIsInert((_parent.Owner, _parent)); + if (_parent.Owner != EntityUid.Invalid) + _parent.Sys?.QueueUpdateIsInert((_parent.Owner, _parent)); } } @@ -1395,7 +1399,8 @@ public Vector2 Offset _offset = value; UpdateLocalMatrix(); // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract - _parent.Sys?.RebuildBounds((_parent.Owner, _parent)); + if (_parent.Owner != EntityUid.Invalid) + _parent.Sys?.RebuildBounds((_parent.Owner, _parent)); } } From ba54d922d1f7fbc97440f845218e8532c30f4525 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Mon, 13 Jan 2025 20:28:01 +1300 Subject: [PATCH 15/36] LayerSetRsi --- .../Components/Renderable/SpriteComponent.cs | 131 ++++------------ .../SpriteSystem.LayerSetters.cs | 144 +++++++++++++++++- 2 files changed, 171 insertions(+), 104 deletions(-) diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index 343cf1fe196..3aa7cd0d57b 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -644,19 +644,7 @@ public void LayerSetData(object layerKey, PrototypeLayerData data) [Obsolete("Use SpriteSystem.LayerSetSprite() instead.")] public void LayerSetSprite(int layer, SpriteSpecifier specifier) - { - switch (specifier) - { - case SpriteSpecifier.Texture tex: - LayerSetTexture(layer, tex.TexturePath); - break; - case SpriteSpecifier.Rsi rsi: - LayerSetState(layer, rsi.RsiState, rsi.RsiPath); - break; - default: - throw new NotImplementedException(); - } - } + => Sys.LayerSetSprite((Owner, this), layer, specifier); [Obsolete("Use SpriteSystem.LayerSetSprite() instead.")] public void LayerSetSprite(object layerKey, SpriteSpecifier specifier) @@ -669,14 +657,7 @@ public void LayerSetSprite(object layerKey, SpriteSpecifier specifier) [Obsolete("Use SpriteSystem.LayerSetTexture() instead.")] public void LayerSetTexture(int layer, Texture? texture) - { - if (!TryGetLayer(layer, out var theLayer, true)) - return; - theLayer.SetTexture(texture); - - Sys.QueueUpdateIsInert((Owner, this)); - Sys.RebuildBounds((Owner, this)); - } + => Sys.LayerSetTexture((Owner, this), layer, texture); [Obsolete("Use SpriteSystem.LayerSetTexture() instead.")] public void LayerSetTexture(object layerKey, Texture texture) @@ -701,22 +682,7 @@ public void LayerSetTexture(object layerKey, string texturePath) [Obsolete("Use SpriteSystem.LayerSetTexture() instead.")] public void LayerSetTexture(int layer, ResPath texturePath) - { - if (!resourceCache.TryGetResource(TextureRoot / texturePath, out var texture)) - { - if (texturePath.Extension == "rsi") - { - Logger.ErrorS(LogCategory, - "Expected texture but got rsi '{0}', did you mean 'sprite:' instead of 'texture:'?", - texturePath); - } - - Logger.ErrorS(LogCategory, "Unable to load texture '{0}'. Trace:\n{1}", texturePath, - Environment.StackTrace); - } - - LayerSetTexture(layer, texture?.Texture); - } + => Sys.LayerSetTexture((Owner, this), layer, texturePath); [Obsolete("Use SpriteSystem.LayerSetTexture() instead.")] public void LayerSetTexture(object layerKey, ResPath texturePath) @@ -727,16 +693,11 @@ public void LayerSetTexture(object layerKey, ResPath texturePath) LayerSetTexture(layer, texturePath); } - #endregion - + [Obsolete("Use SpriteSystem.LayerSetRsiState() instead.")] public void LayerSetState(int layer, RSI.StateId stateId) - { - if (!TryGetLayer(layer, out var theLayer, true)) - return; - theLayer.SetState(stateId); - Sys.RebuildBounds((Owner, this)); - } + => Sys.LayerSetRsiState((Owner, this), layer, stateId); + [Obsolete("Use SpriteSystem.LayerSetRsiState() instead.")] public void LayerSetState(object layerKey, RSI.StateId stateId) { if (!LayerMapTryGet(layerKey, out var layer, true)) @@ -745,38 +706,11 @@ public void LayerSetState(object layerKey, RSI.StateId stateId) LayerSetState(layer, stateId); } + [Obsolete("Use SpriteSystem.LayerSetRsi() instead.")] public void LayerSetState(int layer, RSI.StateId stateId, RSI? rsi) - { - if (!TryGetLayer(layer, out var theLayer, true)) - return; - theLayer.State = stateId; - theLayer.RSI = rsi; - var actualRsi = theLayer.RSI ?? BaseRSI; - if (actualRsi == null) - { - Logger.ErrorS(LogCategory, "No RSI to pull new state from! Trace:\n{0}", Environment.StackTrace); - theLayer.Texture = null; - } - else - { - if (actualRsi.TryGetState(stateId, out var state)) - { - theLayer.AnimationFrame = 0; - theLayer.AnimationTime = 0; - theLayer.AnimationTimeLeft = state.GetDelay(0); - } - else - { - Logger.ErrorS(LogCategory, "State '{0}' does not exist in RSI {1}. Trace:\n{2}", stateId, - actualRsi.Path, Environment.StackTrace); - theLayer.Texture = null; - } - } - - Sys.QueueUpdateIsInert((Owner, this)); - Sys.RebuildBounds((Owner, this)); - } + => Sys.LayerSetRsi((Owner, this), layer, rsi, stateId); + [Obsolete("Use SpriteSystem.LayerSetRsi() instead.")] public void LayerSetState(object layerKey, RSI.StateId stateId, RSI rsi) { if (!LayerMapTryGet(layerKey, out var layer, true)) @@ -785,26 +719,23 @@ public void LayerSetState(object layerKey, RSI.StateId stateId, RSI rsi) LayerSetState(layer, stateId, rsi); } + [Obsolete("Use SpriteSystem.LayerSetRsiState() instead.")] public void LayerSetState(int layer, RSI.StateId stateId, string rsiPath) { LayerSetState(layer, stateId, new ResPath(rsiPath)); } + [Obsolete("Use SpriteSystem.LayerSetRsi() instead.")] public void LayerSetState(object layerKey, RSI.StateId stateId, string rsiPath) { LayerSetState(layerKey, stateId, new ResPath(rsiPath)); } + [Obsolete("Use SpriteSystem.LayerSetRsi() instead.")] public void LayerSetState(int layer, RSI.StateId stateId, ResPath rsiPath) - { - if (!resourceCache.TryGetResource(TextureRoot / rsiPath, out var res)) - { - Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath, Environment.StackTrace); - } - - LayerSetState(layer, stateId, res?.RSI); - } + => Sys.LayerSetRsi((Owner, this), layer, rsiPath, stateId); + [Obsolete("Use SpriteSystem.LayerSetRsi() instead.")] public void LayerSetState(object layerKey, RSI.StateId stateId, ResPath rsiPath) { if (!LayerMapTryGet(layerKey, out var layer, true)) @@ -813,14 +744,11 @@ public void LayerSetState(object layerKey, RSI.StateId stateId, ResPath rsiPath) LayerSetState(layer, stateId, rsiPath); } + [Obsolete("Use SpriteSystem.LayerSetRsi() instead.")] public void LayerSetRSI(int layer, RSI? rsi) - { - if (!TryGetLayer(layer, out var theLayer, true)) - return; - theLayer.SetRsi(rsi); - Sys.RebuildBounds((Owner, this)); - } + => Sys.LayerSetRsi((Owner, this), layer, rsi); + [Obsolete("Use SpriteSystem.LayerSetRsi() instead.")] public void LayerSetRSI(object layerKey, RSI rsi) { if (!LayerMapTryGet(layerKey, out var layer, true)) @@ -829,26 +757,23 @@ public void LayerSetRSI(object layerKey, RSI rsi) LayerSetRSI(layer, rsi); } + [Obsolete("Use SpriteSystem.LayerSetRsi() instead.")] public void LayerSetRSI(int layer, string rsiPath) { LayerSetRSI(layer, new ResPath(rsiPath)); } + [Obsolete("Use SpriteSystem.LayerSetRsi() instead.")] public void LayerSetRSI(object layerKey, string rsiPath) { LayerSetRSI(layerKey, new ResPath(rsiPath)); } + [Obsolete("Use SpriteSystem.LayerSetRsi() instead.")] public void LayerSetRSI(int layer, ResPath rsiPath) - { - if (!resourceCache.TryGetResource(TextureRoot / rsiPath, out var res)) - { - Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath, Environment.StackTrace); - } - - LayerSetRSI(layer, res?.RSI); - } + => Sys.LayerSetRsi((Owner, this), layer, rsiPath); + [Obsolete("Use SpriteSystem.LayerSetRsi() instead.")] public void LayerSetRSI(object layerKey, ResPath rsiPath) { if (!LayerMapTryGet(layerKey, out var layer, true)) @@ -857,6 +782,8 @@ public void LayerSetRSI(object layerKey, ResPath rsiPath) LayerSetRSI(layer, rsiPath); } + #endregion + public void LayerSetScale(int layer, Vector2 scale) { if (!TryGetLayer(layer, out var theLayer, true)) @@ -1273,16 +1200,16 @@ [ViewVariables] public RSI? RSI } } - private RSI.StateId _state; + internal RSI.StateId StateId; [ViewVariables] public RSI.StateId State { - get => _state; + get => StateId; set { - if (_state == value) + if (StateId == value) return; - _state = value; + StateId = value; UpdateActualState(); } } @@ -1304,7 +1231,7 @@ [ViewVariables] public RSI.StateId State /// [ViewVariables] public bool Cycle; - private RSI.State? _actualState; + internal RSI.State? _actualState; [ViewVariables] public RSI.State? ActualState => _actualState; public Matrix3x2 LocalMatrix = Matrix3x2.Identity; diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs index fab633a15e3..8d2221b021c 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs @@ -2,9 +2,11 @@ using Robust.Client.Graphics; using Robust.Client.ResourceManagement; using Robust.Shared.GameObjects; -using Robust.Shared.Serialization.TypeSerializers.Implementations; using Robust.Shared.Utility; using static Robust.Client.GameObjects.SpriteComponent; +using static Robust.Client.Graphics.RSI; +using static Robust.Shared.Serialization.TypeSerializers.Implementations.SpriteSpecifierSerializer; + #pragma warning disable CS0618 // Type or member is obsolete namespace Robust.Client.GameObjects; @@ -31,12 +33,18 @@ internal void LayerSetData(Entity sprite, Layer layer, int inde public void LayerSetData(Entity sprite, string key, PrototypeLayerData data) { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + if (LayerMapTryGet(sprite, key, out var index, true)) LayerSetData(sprite, index, data); } public void LayerSetData(Entity sprite, Enum key, PrototypeLayerData data) { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + if (LayerMapTryGet(sprite, key, out var index, true)) LayerSetData(sprite, index, data); } @@ -62,12 +70,18 @@ public void LayerSetSprite(Entity sprite, int index, SpriteSpe public void LayerSetSprite(Entity sprite, string key, SpriteSpecifier specifier) { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + if (LayerMapTryGet(sprite, key, out var index, true)) LayerSetSprite(sprite, index, specifier); } public void LayerSetSprite(Entity sprite, Enum key, SpriteSpecifier specifier) { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + if (LayerMapTryGet(sprite, key, out var index, true)) LayerSetSprite(sprite, index, specifier); } @@ -92,19 +106,25 @@ public void LayerSetTexture(Entity sprite, int index, Texture? public void LayerSetTexture(Entity sprite, string key, Texture? texture) { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + if (LayerMapTryGet(sprite, key, out var index, true)) LayerSetTexture(sprite, index, texture); } public void LayerSetTexture(Entity sprite, Enum key, Texture? texture) { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + if (LayerMapTryGet(sprite, key, out var index, true)) LayerSetTexture(sprite, index, texture); } public void LayerSetTexture(Entity sprite, int index, ResPath path) { - if (!_resourceCache.TryGetResource(SpriteSpecifierSerializer.TextureRoot / path, out var texture)) + if (!_resourceCache.TryGetResource(TextureRoot / path, out var texture)) { if (path.Extension == "rsi") Log.Error($"Expected texture but got rsi '{path}', did you mean 'sprite:' instead of 'texture:'?"); @@ -116,15 +136,135 @@ public void LayerSetTexture(Entity sprite, int index, ResPath public void LayerSetTexture(Entity sprite, string key, ResPath texture) { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + if (LayerMapTryGet(sprite, key, out var index, true)) LayerSetTexture(sprite, index, texture); } public void LayerSetTexture(Entity sprite, Enum key, ResPath texture) { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + if (LayerMapTryGet(sprite, key, out var index, true)) LayerSetTexture(sprite, index, texture); } #endregion + + #region LayerSetRsiState + + public void LayerSetRsiState(Entity sprite, int index, StateId state) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (!TryGetLayer(sprite, index, out var layer, true)) + return; + + layer.SetState(state); + RebuildBounds(sprite!); + } + + public void LayerSetRsiState(Entity sprite, string key, StateId state) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetRsiState(sprite, index, state); + } + + public void LayerSetRsiState(Entity sprite, Enum key, StateId state) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetRsiState(sprite, index, state); + } + + #endregion + + #region LayerSetRsi + + public void LayerSetRsi(Entity sprite, int index, RSI? rsi, StateId? state = null) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (!TryGetLayer(sprite, index, out var layer, true)) + return; + + layer.RSI = rsi; + if (state != null) + layer.StateId = state.Value; + + layer.AnimationFrame = 0; + layer.AnimationTime = 0; + + var actualRsi = layer.RSI ?? sprite.Comp.BaseRSI; + if (actualRsi == null) + { + Log.Error($"Entity {ToPrettyString(sprite)} has no RSI to pull new state from! Trace:\n{Environment.StackTrace}"); + } + else + { + if (actualRsi.TryGetState(layer.StateId, out layer._actualState)) + layer.AnimationTimeLeft = layer._actualState.GetDelay(0); + else + Log.Error($"Entity {ToPrettyString(sprite)}'s state '{state}' does not exist in RSI {actualRsi.Path}. Trace:\n{Environment.StackTrace}"); + } + + RebuildBounds(sprite!); + QueueUpdateIsInert(sprite!); + } + + public void LayerSetRsi(Entity sprite, string key, RSI? rsi, StateId? state = null) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetRsi(sprite, index, rsi, state); + } + + public void LayerSetRsi(Entity sprite, Enum key, RSI? rsi, StateId? state = null) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetRsi(sprite, index, rsi, state); + } + + public void LayerSetRsi(Entity sprite, int index, ResPath rsi, StateId? state = null) + { + if (!_resourceCache.TryGetResource(TextureRoot / rsi, out var res)) + Log.Error($"Unable to load RSI '{rsi}' for entity {ToPrettyString(sprite)}. Trace:\n{Environment.StackTrace}"); + + LayerSetRsi(sprite, index, res?.RSI, state); + } + + public void LayerSetRsi(Entity sprite, string key, ResPath rsi, StateId? state = null) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetRsi(sprite, index, rsi, state); + } + + public void LayerSetRsi(Entity sprite, Enum key, ResPath rsi, StateId? state = null) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetRsi(sprite, index, rsi, state); + } + + #endregion } From 511165583301ef316c801772d9c84893c6269cba Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Mon, 13 Jan 2025 20:37:03 +1300 Subject: [PATCH 16/36] Remove GetFallbackState --- .../Components/Renderable/SpriteComponent.cs | 27 ++++++------------- Robust.Client/Utility/SpriteSpecifierExt.cs | 8 +++--- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index 3aa7cd0d57b..7260211ef91 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -1069,7 +1069,7 @@ public int GetLayerDirectionCount(ISpriteLayer layer) var rsi = layer.Rsi ?? BaseRSI; if (rsi == null || !rsi.TryGetState(layer.RsiState, out var state)) { - state = GetFallbackState(resourceCache); + state = Sys.GetFallbackState(); } return state.RsiDirections switch @@ -1081,13 +1081,6 @@ public int GetLayerDirectionCount(ISpriteLayer layer) }; } - [Obsolete("Use SpriteSystem instead.")] - internal static RSI.State GetFallbackState(IResourceCache cache) - { - var rsi = cache.GetResource("/Textures/error.rsi").RSI; - return rsi["error"]; - } - public string GetDebugString() { var builder = new StringBuilder(); @@ -1547,14 +1540,14 @@ public void SetState(RSI.StateId stateId) var rsi = ActualRsi; if (rsi == null) { - state = GetFallbackState(_parent.resourceCache); + state = _parent.Sys.GetFallbackState(); Logger.ErrorS(LogCategory, "No RSI to pull new state from! Trace:\n{0}", Environment.StackTrace); } else { if (!rsi.TryGetState(stateId, out state)) { - state = GetFallbackState(_parent.resourceCache); + state = _parent.Sys.GetFallbackState(); Logger.ErrorS(LogCategory, "State '{0}' does not exist in RSI. Trace:\n{1}", stateId, Environment.StackTrace); } @@ -1653,7 +1646,7 @@ internal void UpdateActualState() var rsi = RSI ?? _parent.BaseRSI; if (rsi == null || !rsi.TryGetState(State, out _actualState)) { - _actualState = GetFallbackState(_parent.resourceCache); + _actualState = _parent.Sys?.GetFallbackState(); } } @@ -1907,7 +1900,7 @@ public IRsiStateLike? Icon var rsi = layer.RSI ?? BaseRSI; if (rsi == null || !rsi.TryGetState(layer.State, out var state)) { - state = GetFallbackState(resourceCache); + state = Sys?.GetFallbackState(); } return state; @@ -1976,22 +1969,18 @@ public static IEnumerable GetPrototypeTextures(Enti [Obsolete("Use SpriteSystem")] public static IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache) { + var sys = IoCManager.Resolve().GetEntitySystem(); // TODO when moving to a non-static method in a system, pass in IComponentFactory if (prototype.TryGetComponent(out IconComponent? icon)) - { - var sys = IoCManager.Resolve().GetEntitySystem(); return sys.GetIcon(icon); - } if (!prototype.Components.ContainsKey("Sprite")) - { - return GetFallbackState(resourceCache); - } + return sys.GetFallbackState(); var entityManager = IoCManager.Resolve(); var dummy = entityManager.SpawnEntity(prototype.ID, MapCoordinates.Nullspace); var spriteComponent = entityManager.EnsureComponent(dummy); - var result = spriteComponent.Icon ?? GetFallbackState(resourceCache); + var result = spriteComponent.Icon ?? sys.GetFallbackState(); entityManager.DeleteEntity(dummy); return result; diff --git a/Robust.Client/Utility/SpriteSpecifierExt.cs b/Robust.Client/Utility/SpriteSpecifierExt.cs index 412857a41ef..c55ae24c587 100644 --- a/Robust.Client/Utility/SpriteSpecifierExt.cs +++ b/Robust.Client/Utility/SpriteSpecifierExt.cs @@ -27,10 +27,11 @@ public static Texture GetTexture(this SpriteSpecifier.Texture texSpecifier, IRes [Obsolete("Use SpriteSystem")] public static RSI.State GetState(this SpriteSpecifier.Rsi rsiSpecifier, IResourceCache cache) { + var sys = IoCManager.Resolve().GetEntitySystem(); if (!cache.TryGetResource(SpriteSpecifierSerializer.TextureRoot / rsiSpecifier.RsiPath, out var theRsi)) { Logger.Error("SpriteSpecifier failed to load RSI {0}", rsiSpecifier.RsiPath); - return SpriteComponent.GetFallbackState(cache); + return sys.GetFallbackState(); } if (theRsi.RSI.TryGetState(rsiSpecifier.RsiState, out var state)) @@ -39,7 +40,7 @@ public static RSI.State GetState(this SpriteSpecifier.Rsi rsiSpecifier, IResourc } Logger.Error($"SpriteSpecifier has invalid RSI state '{rsiSpecifier.RsiState}' for RSI: {rsiSpecifier.RsiPath}"); - return SpriteComponent.GetFallbackState(cache); + return sys.GetFallbackState(); } [Obsolete("Use SpriteSystem")] @@ -67,10 +68,11 @@ public static IRsiStateLike RsiStateLike(this SpriteSpecifier specifier) case SpriteSpecifier.EntityPrototype prototypeIcon: var protMgr = IoCManager.Resolve(); + var sys = IoCManager.Resolve().GetEntitySystem(); if (!protMgr.TryIndex(prototypeIcon.EntityPrototypeId, out var prototype)) { Logger.Error("Failed to load PrototypeIcon {0}", prototypeIcon.EntityPrototypeId); - return SpriteComponent.GetFallbackState(resC); + return sys.GetFallbackState(); } return SpriteComponent.GetPrototypeIcon(prototype, resC); From 2c8a421f03b2428c4d49d050d4fedc2f3395b653 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Mon, 13 Jan 2025 20:50:24 +1300 Subject: [PATCH 17/36] LayerSet Scale,Rotation,Color,Visible --- .../Components/Renderable/SpriteComponent.cs | 46 ++---- .../SpriteSystem.LayerSetters.cs | 140 ++++++++++++++++++ .../EntitySystems/SpriteSystem.Setters.cs | 18 ++- 3 files changed, 168 insertions(+), 36 deletions(-) diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index 7260211ef91..f29bf20f205 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -148,7 +148,6 @@ public Color Color public Matrix3x2 LocalMatrix = Matrix3x2.Identity; - #region SpriteTree [ViewVariables] public DynamicTree>? Tree { get; set; } @@ -157,7 +156,6 @@ public Color Color public bool AddToTree => Visible && !ContainerOccluded && Layers.Count > 0; public bool TreeUpdateQueued { get; set; } - #endregion internal RSI? _baseRsi; @@ -782,16 +780,11 @@ public void LayerSetRSI(object layerKey, ResPath rsiPath) LayerSetRSI(layer, rsiPath); } - #endregion - + [Obsolete("Use SpriteSystem.LayerSetScale() instead.")] public void LayerSetScale(int layer, Vector2 scale) - { - if (!TryGetLayer(layer, out var theLayer, true)) - return; - theLayer.Scale = scale; - Sys.RebuildBounds((Owner, this)); - } + => Sys.LayerSetScale((Owner, this), layer, scale); + [Obsolete("Use SpriteSystem.LayerSetScale() instead.")] public void LayerSetScale(object layerKey, Vector2 scale) { if (!LayerMapTryGet(layerKey, out var layer, true)) @@ -800,15 +793,11 @@ public void LayerSetScale(object layerKey, Vector2 scale) LayerSetScale(layer, scale); } - + [Obsolete("Use SpriteSystem.LayerSetRotation() instead.")] public void LayerSetRotation(int layer, Angle rotation) - { - if (!TryGetLayer(layer, out var theLayer, true)) - return; - theLayer.Rotation = rotation; - Sys.RebuildBounds((Owner, this)); - } + => Sys.LayerSetRotation((Owner, this), layer, rotation); + [Obsolete("Use SpriteSystem.LayerSetRotation() instead.")] public void LayerSetRotation(object layerKey, Angle rotation) { if (!LayerMapTryGet(layerKey, out var layer, true)) @@ -817,14 +806,11 @@ public void LayerSetRotation(object layerKey, Angle rotation) LayerSetRotation(layer, rotation); } + [Obsolete("Use SpriteSystem.LayerSetVisible() instead.")] public void LayerSetVisible(int layer, bool visible) - { - if (!TryGetLayer(layer, out var theLayer, true)) - return; - - theLayer.Visible = visible; - } + => Sys.LayerSetVisible((Owner, this), layer, visible); + [Obsolete("Use SpriteSystem.LayerSetVisible() instead.")] public void LayerSetVisible(object layerKey, bool visible) { if (!LayerMapTryGet(layerKey, out var layer, true)) @@ -833,15 +819,11 @@ public void LayerSetVisible(object layerKey, bool visible) LayerSetVisible(layer, visible); } + [Obsolete("Use SpriteSystem.LayerSetColor() instead.")] public void LayerSetColor(int layer, Color color) - { - if (!TryGetLayer(layer, out var theLayer, true)) - return; - - theLayer.Color = color; - Sys.RebuildBounds((Owner, this)); - } + => Sys.LayerSetColor((Owner, this), layer, color); + [Obsolete("Use SpriteSystem.LayerSetColor() instead.")] public void LayerSetColor(object layerKey, Color color) { if (!LayerMapTryGet(layerKey, out var layer, true)) @@ -850,6 +832,8 @@ public void LayerSetColor(object layerKey, Color color) LayerSetColor(layer, color); } + #endregion + public void LayerSetDirOffset(int layer, DirectionOffset offset) { if (!TryGetLayer(layer, out var theLayer, true)) @@ -1266,7 +1250,7 @@ public Angle Rotation } internal Angle _rotation = Angle.Zero; - private bool _visible = true; + internal bool _visible = true; [ViewVariables(VVAccess.ReadWrite)] public bool Visible { diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs index 8d2221b021c..20b3907ed9b 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs @@ -1,7 +1,9 @@ using System; +using System.Numerics; using Robust.Client.Graphics; using Robust.Client.ResourceManagement; using Robust.Shared.GameObjects; +using Robust.Shared.Maths; using Robust.Shared.Utility; using static Robust.Client.GameObjects.SpriteComponent; using static Robust.Client.Graphics.RSI; @@ -267,4 +269,142 @@ public void LayerSetRsi(Entity sprite, Enum key, ResPath rsi, } #endregion + + #region Properties + + public void LayerSetScale(Entity sprite, int index, Vector2 value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (!TryGetLayer(sprite, index, out var layer, true)) + return; + + if (layer._scale.EqualsApprox(value)) + return; + + if (!ValidateScale(sprite.Owner, value)) + return; + + layer._scale = value; + layer.UpdateLocalMatrix(); + RebuildBounds(sprite!); + } + + public void LayerSetScale(Entity sprite, string key, Vector2 value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetScale(sprite, index, value); + } + + public void LayerSetScale(Entity sprite, Enum key, Vector2 value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetScale(sprite, index, value); + } + + public void LayerSetRotation(Entity sprite, int index, Angle value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (!TryGetLayer(sprite, index, out var layer, true)) + return; + + if (layer._rotation.EqualsApprox(value)) + return; + + layer._rotation = value; + layer.UpdateLocalMatrix(); + RebuildBounds(sprite!); + } + + public void LayerSetRotation(Entity sprite, string key, Angle value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetRotation(sprite, index, value); + } + + public void LayerSetRotation(Entity sprite, Enum key, Angle value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetRotation(sprite, index, value); + } + + public void LayerSetVisible(Entity sprite, int index, bool value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (!TryGetLayer(sprite, index, out var layer, true)) + return; + + if (layer._visible == value) + return; + + layer._visible = value; + QueueUpdateIsInert(sprite!); + RebuildBounds(sprite!); + } + + public void LayerSetVisible(Entity sprite, string key, bool value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetVisible(sprite, index, value); + } + + public void LayerSetVisible(Entity sprite, Enum key, bool value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetVisible(sprite, index, value); + } + + public void LayerSetColor(Entity sprite, int index, Color value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (!TryGetLayer(sprite, index, out var layer, true)) + return; + + layer.Color = value; + } + + public void LayerSetColor(Entity sprite, string key, Color value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetColor(sprite, index, value); + } + + public void LayerSetColor(Entity sprite, Enum key, Color value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetColor(sprite, index, value); + } + + #endregion } diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Setters.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Setters.cs index ffaf728363a..42e43cd627c 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Setters.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Setters.cs @@ -9,18 +9,26 @@ namespace Robust.Client.GameObjects; // This partial class contains various public methods for setting sprite component data. public sealed partial class SpriteSystem { + private bool ValidateScale(EntityUid uid, Vector2 scale) + { + if (!(MathF.Abs(scale.X) < 0.005f) && !(MathF.Abs(scale.Y) < 0.005f)) + return true; + + // Scales of ~0.0025 or lower can lead to singular matrices due to rounding errors. + Log.Error( + $"Attempted to set layer sprite scale to very small values. Entity: {ToPrettyString(uid)}. Scale: {scale}"); + + return false; + } + #region Transform public void SetScale(Entity sprite, Vector2 value) { if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) return; - if (MathF.Abs(value.X) < 0.005f || MathF.Abs(value.Y) < 0.005f) - { - // Scales of ~0.0025 or lower can lead to singular matrices due to rounding errors. - Log.Error($"Attempted to set layer sprite scale to very small values. Entity: {ToPrettyString(sprite)}. Scale: {value}"); + if (!ValidateScale(sprite.Owner, value)) return; - } sprite.Comp._bounds = sprite.Comp._bounds.Scale(value / sprite.Comp.scale); sprite.Comp.scale = value; From 5b39b2d8b5a16a64fc0c37133be4c681a8bf221b Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Mon, 13 Jan 2025 21:19:22 +1300 Subject: [PATCH 18/36] Fix LayerSetRsi --- .../Components/Renderable/SpriteComponent.cs | 4 +- .../SpriteSystem.LayerSetters.cs | 60 +++++++++++-------- 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index f29bf20f205..6ad25799f60 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -1157,13 +1157,13 @@ public enum DirectionOffset : byte public sealed class Layer : ISpriteLayer, ISerializationHooks { - [ViewVariables] private readonly SpriteComponent _parent; + [ViewVariables] internal readonly SpriteComponent _parent; [ViewVariables] public string? ShaderPrototype; [ViewVariables] public ShaderInstance? Shader; [ViewVariables] public Texture? Texture; - private RSI? _rsi; + internal RSI? _rsi; [ViewVariables] public RSI? RSI { get => _rsi; diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs index 20b3907ed9b..7fe6c802923 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs @@ -166,8 +166,40 @@ public void LayerSetRsiState(Entity sprite, int index, StateId if (!TryGetLayer(sprite, index, out var layer, true)) return; - layer.SetState(state); - RebuildBounds(sprite!); + LayerSetRsiState(sprite!, layer, state); + } + + public void LayerSetRsiState(Entity sprite, Layer layer, StateId state, bool refresh = false) + { + if (layer._parent != sprite.Comp) + throw new InvalidOperationException($"The given layer does not belong this entity."); + + if (layer.StateId == state && !refresh) + return; + + layer.StateId = state; + + if (!layer.StateId.IsValid) + { + layer._actualState = null; + } + else if (layer.ActualRsi is not {} rsi) + { + Log.Error($"{ToPrettyString(sprite)} has no RSI to pull new state from! Trace:\n{Environment.StackTrace}"); + layer._actualState = GetFallbackState(); + } + else if (!rsi.TryGetState(layer.StateId, out layer._actualState)) + { + layer._actualState = GetFallbackState(); + Log.Error($"{ToPrettyString(sprite)}'s state '{state}' does not exist in RSI {rsi.Path}. Trace:\n{Environment.StackTrace}"); + } + + layer.AnimationFrame = 0; + layer.AnimationTime = 0; + layer.AnimationTimeLeft = layer._actualState?.GetDelay(0) ?? 0f; + + RebuildBounds(sprite); + QueueUpdateIsInert(sprite); } public void LayerSetRsiState(Entity sprite, string key, StateId state) @@ -200,28 +232,8 @@ public void LayerSetRsi(Entity sprite, int index, RSI? rsi, St if (!TryGetLayer(sprite, index, out var layer, true)) return; - layer.RSI = rsi; - if (state != null) - layer.StateId = state.Value; - - layer.AnimationFrame = 0; - layer.AnimationTime = 0; - - var actualRsi = layer.RSI ?? sprite.Comp.BaseRSI; - if (actualRsi == null) - { - Log.Error($"Entity {ToPrettyString(sprite)} has no RSI to pull new state from! Trace:\n{Environment.StackTrace}"); - } - else - { - if (actualRsi.TryGetState(layer.StateId, out layer._actualState)) - layer.AnimationTimeLeft = layer._actualState.GetDelay(0); - else - Log.Error($"Entity {ToPrettyString(sprite)}'s state '{state}' does not exist in RSI {actualRsi.Path}. Trace:\n{Environment.StackTrace}"); - } - - RebuildBounds(sprite!); - QueueUpdateIsInert(sprite!); + layer._rsi = rsi; + LayerSetRsiState(sprite!, layer, state ?? layer.StateId, refresh: true); } public void LayerSetRsi(Entity sprite, string key, RSI? rsi, StateId? state = null) From e34ae6c9860a7ed0af893f9f58096e825f21db4a Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Mon, 13 Jan 2025 21:51:34 +1300 Subject: [PATCH 19/36] LayerSetOffset --- .../Components/Renderable/SpriteComponent.cs | 30 +++++++--------- .../SpriteSystem.LayerSetters.cs | 34 +++++++++++++++++++ 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index 6ad25799f60..334946f56f6 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -806,6 +806,19 @@ public void LayerSetRotation(object layerKey, Angle rotation) LayerSetRotation(layer, rotation); } + [Obsolete("Use SpriteSystem.LayerSetOffset() instead.")] + public void LayerSetOffset(int layer, Vector2 layerOffset) + => Sys.LayerSetOffset((Owner, this), layer, layerOffset); + + [Obsolete("Use SpriteSystem.LayerSetOffset() instead.")] + public void LayerSetOffset(object layerKey, Vector2 layerOffset) + { + if (!LayerMapTryGet(layerKey, out var layer, true)) + return; + + LayerSetOffset(layer, layerOffset); + } + [Obsolete("Use SpriteSystem.LayerSetVisible() instead.")] public void LayerSetVisible(int layer, bool visible) => Sys.LayerSetVisible((Owner, this), layer, visible); @@ -883,23 +896,6 @@ public void LayerSetAutoAnimated(object layerKey, bool autoAnimated) LayerSetAutoAnimated(layer, autoAnimated); } - public void LayerSetOffset(int layer, Vector2 layerOffset) - { - if (!TryGetLayer(layer, out var theLayer, true)) - return; - - theLayer.Offset = layerOffset; - Sys.RebuildBounds((Owner, this)); - } - - public void LayerSetOffset(object layerKey, Vector2 layerOffset) - { - if (!LayerMapTryGet(layerKey, out var layer, true)) - return; - - LayerSetOffset(layer, layerOffset); - } - public void LayerSetRenderingStrategy(int layer, LayerRenderingStrategy renderingStrategy) { if (!TryGetLayer(layer, out var theLayer, true)) diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs index 7fe6c802923..bd304578e7f 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs @@ -355,6 +355,40 @@ public void LayerSetRotation(Entity sprite, Enum key, Angle va LayerSetRotation(sprite, index, value); } + public void LayerSetOffset(Entity sprite, int index, Vector2 value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (!TryGetLayer(sprite, index, out var layer, true)) + return; + + if (layer._offset.EqualsApprox(value)) + return; + + layer._offset = value; + layer.UpdateLocalMatrix(); + RebuildBounds(sprite!); + } + + public void LayerSetOffset(Entity sprite, string key, Vector2 value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetOffset(sprite, index, value); + } + + public void LayerSetOffset(Entity sprite, Enum key, Vector2 value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetOffset(sprite, index, value); + } + public void LayerSetVisible(Entity sprite, int index, bool value) { if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) From 3209113c454e58dfb86780cb9948b1197f4ad4dd Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Mon, 13 Jan 2025 21:54:54 +1300 Subject: [PATCH 20/36] LayerSetDirOffset --- .../Components/Renderable/SpriteComponent.cs | 14 ++++----- .../SpriteSystem.LayerSetters.cs | 29 +++++++++++++++++++ 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index 334946f56f6..277e64d474b 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -845,17 +845,11 @@ public void LayerSetColor(object layerKey, Color color) LayerSetColor(layer, color); } - #endregion - + [Obsolete("Use SpriteSystem.LayerSetDirOffset() instead.")] public void LayerSetDirOffset(int layer, DirectionOffset offset) - { - if (!TryGetLayer(layer, out var theLayer, true)) - return; - - theLayer.DirOffset = offset; - Sys.RebuildBounds((Owner, this)); - } + => Sys.LayerSetDirOffset((Owner, this), layer, offset); + [Obsolete("Use SpriteSystem.LayerSetDirOffset() instead.")] public void LayerSetDirOffset(object layerKey, DirectionOffset offset) { if (!LayerMapTryGet(layerKey, out var layer, true)) @@ -864,6 +858,8 @@ public void LayerSetDirOffset(object layerKey, DirectionOffset offset) LayerSetDirOffset(layer, offset); } + #endregion + public void LayerSetAnimationTime(int layer, float animationTime) { if (!TryGetLayer(layer, out var theLayer, true)) diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs index bd304578e7f..1ec017544d0 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs @@ -452,5 +452,34 @@ public void LayerSetColor(Entity sprite, Enum key, Color value LayerSetColor(sprite, index, value); } + public void LayerSetDirOffset(Entity sprite, int index, DirectionOffset value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (!TryGetLayer(sprite, index, out var layer, true)) + return; + + layer.DirOffset = value; + } + + public void LayerSetDirOffset(Entity sprite, string key, DirectionOffset value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetDirOffset(sprite, index, value); + } + + public void LayerSetDirOffset(Entity sprite, Enum key, DirectionOffset value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetDirOffset(sprite, index, value); + } + #endregion } From 4059cfae5c3dababe8200d4a100566c070d1076b Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Mon, 13 Jan 2025 22:31:45 +1300 Subject: [PATCH 21/36] Add overrides that take in a Layer --- .../Components/Renderable/SpriteComponent.cs | 11 +- .../SpriteSystem.LayerSetters.cs | 132 +++++++++++++----- .../EntitySystems/SpriteSystem.Setters.cs | 4 +- 3 files changed, 104 insertions(+), 43 deletions(-) diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index 277e64d474b..b6660a4bd52 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -1149,7 +1149,9 @@ public enum DirectionOffset : byte public sealed class Layer : ISpriteLayer, ISerializationHooks { - [ViewVariables] internal readonly SpriteComponent _parent; + internal SpriteComponent _parent => Owner.Comp; + + [ViewVariables] public readonly Entity Owner; [ViewVariables] public string? ShaderPrototype; [ViewVariables] public ShaderInstance? Shader; @@ -1268,7 +1270,7 @@ public bool Visible [ViewVariables(VVAccess.ReadWrite)] public Color Color { get; set; } = Color.White; - private bool _autoAnimated = true; + internal bool _autoAnimated = true; [ViewVariables(VVAccess.ReadWrite)] public bool AutoAnimated { @@ -1320,12 +1322,13 @@ public Vector2 Offset public Layer(SpriteComponent parent) { - _parent = parent; + Owner = (parent.Owner, parent); } public Layer(Layer toClone, SpriteComponent parentSprite) { - _parent = parentSprite; + Owner = (parentSprite.Owner, parentSprite); + if (toClone.Shader != null) { Shader = toClone.Shader.Mutable ? toClone.Shader.Duplicate() : toClone.Shader; diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs index 1ec017544d0..6649747b870 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs @@ -16,7 +16,7 @@ namespace Robust.Client.GameObjects; // This partial class contains various public methods for modifying a layer's properties. public sealed partial class SpriteSystem { - #region LayerSetData + #region SetData public void LayerSetData(Entity sprite, int index, PrototypeLayerData data) { @@ -24,13 +24,16 @@ public void LayerSetData(Entity sprite, int index, PrototypeLa return; if (TryGetLayer(sprite, index, out var layer, true)) - LayerSetData(sprite!, layer, index, data); + LayerSetData(layer, data); } - internal void LayerSetData(Entity sprite, Layer layer, int index, PrototypeLayerData data) + public void LayerSetData(Layer layer, PrototypeLayerData data) { - DebugTools.AssertEqual(sprite.Comp.Layers[index], layer); - sprite.Comp.LayerSetData(layer, index, data); + // TODO SPRITE store layer index. + var index = layer._parent.Layers.IndexOf(layer); + + // TODO SPRITE ECS + layer._parent.LayerSetData(layer, index, data); } public void LayerSetData(Entity sprite, string key, PrototypeLayerData data) @@ -53,7 +56,7 @@ public void LayerSetData(Entity sprite, Enum key, PrototypeLay #endregion - #region LayerSetSprite + #region SpriteSpecifier public void LayerSetSprite(Entity sprite, int index, SpriteSpecifier specifier) { @@ -61,9 +64,11 @@ public void LayerSetSprite(Entity sprite, int index, SpriteSpe { case SpriteSpecifier.Texture tex: LayerSetTexture(sprite, index, tex.TexturePath); + break; case SpriteSpecifier.Rsi rsi: - //LayerSetState(sprite, layer, rsi.RsiState, rsi.RsiPath); + LayerSetRsi(sprite, index, rsi.RsiPath, rsi.RsiState); + break; default: throw new NotImplementedException(); @@ -90,20 +95,21 @@ public void LayerSetSprite(Entity sprite, Enum key, SpriteSpec #endregion - #region LayerSetTexture + #region Texture public void LayerSetTexture(Entity sprite, int index, Texture? texture) { if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) return; - if (!TryGetLayer(sprite, index, out var layer, true)) - return; + if (TryGetLayer(sprite, index, out var layer, true)) + LayerSetTexture(layer, texture); + } - layer.State = default; + public void LayerSetTexture(Layer layer, Texture? texture) + { + LayerSetRsiState(layer, StateId.Invalid, refresh: true); layer.Texture = texture; - QueueUpdateIsInert(sprite!); - RebuildBounds(sprite!); } public void LayerSetTexture(Entity sprite, string key, Texture? texture) @@ -156,24 +162,19 @@ public void LayerSetTexture(Entity sprite, Enum key, ResPath t #endregion - #region LayerSetRsiState + #region RsiState public void LayerSetRsiState(Entity sprite, int index, StateId state) { if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) return; - if (!TryGetLayer(sprite, index, out var layer, true)) - return; - - LayerSetRsiState(sprite!, layer, state); + if (TryGetLayer(sprite, index, out var layer, true)) + LayerSetRsiState(layer, state); } - public void LayerSetRsiState(Entity sprite, Layer layer, StateId state, bool refresh = false) + public void LayerSetRsiState(Layer layer, StateId state, bool refresh = false) { - if (layer._parent != sprite.Comp) - throw new InvalidOperationException($"The given layer does not belong this entity."); - if (layer.StateId == state && !refresh) return; @@ -185,21 +186,21 @@ public void LayerSetRsiState(Entity sprite, Layer layer, StateI } else if (layer.ActualRsi is not {} rsi) { - Log.Error($"{ToPrettyString(sprite)} has no RSI to pull new state from! Trace:\n{Environment.StackTrace}"); + Log.Error($"{ToPrettyString(layer.Owner)} has no RSI to pull new state from! Trace:\n{Environment.StackTrace}"); layer._actualState = GetFallbackState(); } else if (!rsi.TryGetState(layer.StateId, out layer._actualState)) { layer._actualState = GetFallbackState(); - Log.Error($"{ToPrettyString(sprite)}'s state '{state}' does not exist in RSI {rsi.Path}. Trace:\n{Environment.StackTrace}"); + Log.Error($"{ToPrettyString(layer.Owner)}'s state '{state}' does not exist in RSI {rsi.Path}. Trace:\n{Environment.StackTrace}"); } layer.AnimationFrame = 0; layer.AnimationTime = 0; layer.AnimationTimeLeft = layer._actualState?.GetDelay(0) ?? 0f; - RebuildBounds(sprite); - QueueUpdateIsInert(sprite); + RebuildBounds(layer.Owner); + QueueUpdateIsInert(layer.Owner); } public void LayerSetRsiState(Entity sprite, string key, StateId state) @@ -222,7 +223,7 @@ public void LayerSetRsiState(Entity sprite, Enum key, StateId #endregion - #region LayerSetRsi + #region Rsi public void LayerSetRsi(Entity sprite, int index, RSI? rsi, StateId? state = null) { @@ -233,7 +234,13 @@ public void LayerSetRsi(Entity sprite, int index, RSI? rsi, St return; layer._rsi = rsi; - LayerSetRsiState(sprite!, layer, state ?? layer.StateId, refresh: true); + LayerSetRsiState(layer, state ?? layer.StateId, refresh: true); + } + + public void LayerSetRsi(Entity sprite, Layer layer, RSI? rsi, StateId? state = null) + { + layer._rsi = rsi; + LayerSetRsiState(layer, state ?? layer.StateId, refresh: true); } public void LayerSetRsi(Entity sprite, string key, RSI? rsi, StateId? state = null) @@ -282,25 +289,28 @@ public void LayerSetRsi(Entity sprite, Enum key, ResPath rsi, #endregion - #region Properties + #region Scale public void LayerSetScale(Entity sprite, int index, Vector2 value) { if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) return; - if (!TryGetLayer(sprite, index, out var layer, true)) - return; + if (TryGetLayer(sprite, index, out var layer, true)) + LayerSetScale(layer, value); + } + public void LayerSetScale(Layer layer, Vector2 value) + { if (layer._scale.EqualsApprox(value)) return; - if (!ValidateScale(sprite.Owner, value)) + if (!ValidateScale(layer.Owner, value)) return; layer._scale = value; layer.UpdateLocalMatrix(); - RebuildBounds(sprite!); + RebuildBounds(layer.Owner); } public void LayerSetScale(Entity sprite, string key, Vector2 value) @@ -321,6 +331,10 @@ public void LayerSetScale(Entity sprite, Enum key, Vector2 val LayerSetScale(sprite, index, value); } + #endregion + + #region Rotation + public void LayerSetRotation(Entity sprite, int index, Angle value) { if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) @@ -329,12 +343,17 @@ public void LayerSetRotation(Entity sprite, int index, Angle v if (!TryGetLayer(sprite, index, out var layer, true)) return; + LayerSetRotation(layer, value); + } + + public void LayerSetRotation(Layer layer, Angle value) + { if (layer._rotation.EqualsApprox(value)) return; layer._rotation = value; layer.UpdateLocalMatrix(); - RebuildBounds(sprite!); + RebuildBounds(layer.Owner); } public void LayerSetRotation(Entity sprite, string key, Angle value) @@ -355,6 +374,10 @@ public void LayerSetRotation(Entity sprite, Enum key, Angle va LayerSetRotation(sprite, index, value); } + #endregion + + #region Offset + public void LayerSetOffset(Entity sprite, int index, Vector2 value) { if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) @@ -363,12 +386,17 @@ public void LayerSetOffset(Entity sprite, int index, Vector2 v if (!TryGetLayer(sprite, index, out var layer, true)) return; + LayerSetOffset(layer, value); + } + + private void LayerSetOffset(Layer layer, Vector2 value) + { if (layer._offset.EqualsApprox(value)) return; layer._offset = value; layer.UpdateLocalMatrix(); - RebuildBounds(sprite!); + RebuildBounds(layer.Owner); } public void LayerSetOffset(Entity sprite, string key, Vector2 value) @@ -389,6 +417,10 @@ public void LayerSetOffset(Entity sprite, Enum key, Vector2 va LayerSetOffset(sprite, index, value); } + #endregion + + #region Visible + public void LayerSetVisible(Entity sprite, int index, bool value) { if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) @@ -397,12 +429,17 @@ public void LayerSetVisible(Entity sprite, int index, bool val if (!TryGetLayer(sprite, index, out var layer, true)) return; + LayerSetVisible(layer, value); + } + + private void LayerSetVisible(Layer layer, bool value) + { if (layer._visible == value) return; layer._visible = value; - QueueUpdateIsInert(sprite!); - RebuildBounds(sprite!); + QueueUpdateIsInert(layer.Owner); + RebuildBounds(layer.Owner); } public void LayerSetVisible(Entity sprite, string key, bool value) @@ -423,6 +460,10 @@ public void LayerSetVisible(Entity sprite, Enum key, bool valu LayerSetVisible(sprite, index, value); } + #endregion + + #region Color + public void LayerSetColor(Entity sprite, int index, Color value) { if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) @@ -431,6 +472,13 @@ public void LayerSetColor(Entity sprite, int index, Color valu if (!TryGetLayer(sprite, index, out var layer, true)) return; + LayerSetColor(layer, value); + layer.Color = value; + } + + private void LayerSetColor(Layer layer, Color value) + { + //Yes this is trivial, but this is here mainly for future proofing. layer.Color = value; } @@ -452,6 +500,10 @@ public void LayerSetColor(Entity sprite, Enum key, Color value LayerSetColor(sprite, index, value); } + #endregion + + #region DirOffset + public void LayerSetDirOffset(Entity sprite, int index, DirectionOffset value) { if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) @@ -460,6 +512,12 @@ public void LayerSetDirOffset(Entity sprite, int index, Direct if (!TryGetLayer(sprite, index, out var layer, true)) return; + LayerSetDirOffset(layer, value); + } + + private void LayerSetDirOffset(Layer layer, DirectionOffset value) + { + //Yes this is trivial, but this is here mainly for future proofing. layer.DirOffset = value; } diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Setters.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Setters.cs index 42e43cd627c..ff6f927ff8f 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Setters.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Setters.cs @@ -9,14 +9,14 @@ namespace Robust.Client.GameObjects; // This partial class contains various public methods for setting sprite component data. public sealed partial class SpriteSystem { - private bool ValidateScale(EntityUid uid, Vector2 scale) + private bool ValidateScale(Entity sprite, Vector2 scale) { if (!(MathF.Abs(scale.X) < 0.005f) && !(MathF.Abs(scale.Y) < 0.005f)) return true; // Scales of ~0.0025 or lower can lead to singular matrices due to rounding errors. Log.Error( - $"Attempted to set layer sprite scale to very small values. Entity: {ToPrettyString(uid)}. Scale: {scale}"); + $"Attempted to set layer sprite scale to very small values. Entity: {ToPrettyString(sprite)}. Scale: {scale}"); return false; } From 35d17983145ffd17a23955f0450e3aca4ac84679 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Mon, 13 Jan 2025 22:31:55 +1300 Subject: [PATCH 22/36] LayerSetAnimationTime --- .../Components/Renderable/SpriteComponent.cs | 22 ++- .../SpriteSystem.LayerSetters.cs | 136 +++++++++++++++--- .../EntitySystems/SpriteSystem.Setters.cs | 2 +- 3 files changed, 123 insertions(+), 37 deletions(-) diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index b6660a4bd52..989b3783b66 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -858,16 +858,11 @@ public void LayerSetDirOffset(object layerKey, DirectionOffset offset) LayerSetDirOffset(layer, offset); } - #endregion - + [Obsolete("Use SpriteSystem.LayerSetAnimationTime() instead.")] public void LayerSetAnimationTime(int layer, float animationTime) - { - if (!TryGetLayer(layer, out var theLayer, true)) - return; - - theLayer.SetAnimationTime(animationTime); - } + => Sys.LayerSetAnimationTime((Owner, this), layer, animationTime); + [Obsolete("Use SpriteSystem.LayerSetAnimationTime() instead.")] public void LayerSetAnimationTime(object layerKey, float animationTime) { if (!LayerMapTryGet(layerKey, out var layer, true)) @@ -876,14 +871,11 @@ public void LayerSetAnimationTime(object layerKey, float animationTime) LayerSetAnimationTime(layer, animationTime); } + [Obsolete("Use SpriteSystem.LayerSetAutoAnimated() instead.")] public void LayerSetAutoAnimated(int layer, bool autoAnimated) - { - if (!TryGetLayer(layer, out var theLayer, true)) - return; - - theLayer.AutoAnimated = autoAnimated; - } + => Sys.LayerSetAutoAnimated((Owner, this), layer, autoAnimated); + [Obsolete("Use SpriteSystem.LayerSetAutoAnimated() instead.")] public void LayerSetAutoAnimated(object layerKey, bool autoAnimated) { if (!LayerMapTryGet(layerKey, out var layer, true)) @@ -892,6 +884,8 @@ public void LayerSetAutoAnimated(object layerKey, bool autoAnimated) LayerSetAutoAnimated(layer, autoAnimated); } + #endregion + public void LayerSetRenderingStrategy(int layer, LayerRenderingStrategy renderingStrategy) { if (!TryGetLayer(layer, out var theLayer, true)) diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs index 6649747b870..4673c678589 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs @@ -64,12 +64,12 @@ public void LayerSetSprite(Entity sprite, int index, SpriteSpe { case SpriteSpecifier.Texture tex: LayerSetTexture(sprite, index, tex.TexturePath); - break; + case SpriteSpecifier.Rsi rsi: LayerSetRsi(sprite, index, rsi.RsiPath, rsi.RsiState); - break; + default: throw new NotImplementedException(); } @@ -340,10 +340,8 @@ public void LayerSetRotation(Entity sprite, int index, Angle v if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) return; - if (!TryGetLayer(sprite, index, out var layer, true)) - return; - - LayerSetRotation(layer, value); + if (TryGetLayer(sprite, index, out var layer, true)) + LayerSetRotation(layer, value); } public void LayerSetRotation(Layer layer, Angle value) @@ -383,13 +381,11 @@ public void LayerSetOffset(Entity sprite, int index, Vector2 v if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) return; - if (!TryGetLayer(sprite, index, out var layer, true)) - return; - - LayerSetOffset(layer, value); + if (TryGetLayer(sprite, index, out var layer, true)) + LayerSetOffset(layer, value); } - private void LayerSetOffset(Layer layer, Vector2 value) + public void LayerSetOffset(Layer layer, Vector2 value) { if (layer._offset.EqualsApprox(value)) return; @@ -426,13 +422,11 @@ public void LayerSetVisible(Entity sprite, int index, bool val if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) return; - if (!TryGetLayer(sprite, index, out var layer, true)) - return; - - LayerSetVisible(layer, value); + if (TryGetLayer(sprite, index, out var layer, true)) + LayerSetVisible(layer, value); } - private void LayerSetVisible(Layer layer, bool value) + public void LayerSetVisible(Layer layer, bool value) { if (layer._visible == value) return; @@ -476,7 +470,7 @@ public void LayerSetColor(Entity sprite, int index, Color valu layer.Color = value; } - private void LayerSetColor(Layer layer, Color value) + public void LayerSetColor(Layer layer, Color value) { //Yes this is trivial, but this is here mainly for future proofing. layer.Color = value; @@ -509,13 +503,11 @@ public void LayerSetDirOffset(Entity sprite, int index, Direct if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) return; - if (!TryGetLayer(sprite, index, out var layer, true)) - return; - - LayerSetDirOffset(layer, value); + if (TryGetLayer(sprite, index, out var layer, true)) + LayerSetDirOffset(layer, value); } - private void LayerSetDirOffset(Layer layer, DirectionOffset value) + public void LayerSetDirOffset(Layer layer, DirectionOffset value) { //Yes this is trivial, but this is here mainly for future proofing. layer.DirOffset = value; @@ -540,4 +532,104 @@ public void LayerSetDirOffset(Entity sprite, Enum key, Directi } #endregion + + #region AnimationTime + + public void LayerSetAnimationTime(Entity sprite, int index, float value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (TryGetLayer(sprite, index, out var layer, true)) + LayerSetAnimationTime(layer, value); + } + + public void LayerSetAnimationTime(Layer layer, float value) + { + if (!layer.StateId.IsValid) + return; + + if (layer.ActualRsi is not {} rsi) + return; + + var state = rsi[layer.StateId]; + if (value > layer.AnimationTime) + { + // Handle advancing differently from going backwards. + layer.AnimationTimeLeft -= (value - layer.AnimationTime); + } + else + { + // Going backwards we re-calculate from zero. + // Definitely possible to optimize this for going backwards but I'm too lazy to figure that out. + layer.AnimationTimeLeft = -value + state.GetDelay(0); + layer.AnimationFrame = 0; + } + + layer.AnimationTime = value; + layer.AdvanceFrameAnimation(state); + layer.SetAnimationTime(value); + } + + public void LayerSetAnimationTime(Entity sprite, string key, float value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetAnimationTime(sprite, index, value); + } + + public void LayerSetAnimationTime(Entity sprite, Enum key, float value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetAnimationTime(sprite, index, value); + } + + #endregion + + #region AutoAnimated + + public void LayerSetAutoAnimated(Entity sprite, int index, bool value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (!TryGetLayer(sprite, index, out var layer, true)) + return; + + LayerSetAutoAnimated(layer, value); + } + + public void LayerSetAutoAnimated(Layer layer, bool value) + { + if (layer._autoAnimated == value) + return; + + layer._autoAnimated = value; + QueueUpdateIsInert(layer.Owner); + } + + public void LayerSetAutoAnimated(Entity sprite, string key, bool value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetAutoAnimated(sprite, index, value); + } + + public void LayerSetAutoAnimated(Entity sprite, Enum key, bool value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetAutoAnimated(sprite, index, value); + } + + #endregion } diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Setters.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Setters.cs index ff6f927ff8f..6e3884a23e7 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Setters.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Setters.cs @@ -27,7 +27,7 @@ public void SetScale(Entity sprite, Vector2 value) if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) return; - if (!ValidateScale(sprite.Owner, value)) + if (!ValidateScale(sprite!, value)) return; sprite.Comp._bounds = sprite.Comp._bounds.Scale(value / sprite.Comp.scale); From 299ae1c1b824fbe9c175ab71b31b8bb9ae53001e Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Mon, 13 Jan 2025 22:59:31 +1300 Subject: [PATCH 23/36] LayerSetRenderingStrategy --- .../Components/Renderable/SpriteComponent.cs | 14 +++---- .../SpriteSystem.LayerSetters.cs | 42 +++++++++++++++++-- 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index 989b3783b66..d181b9c6986 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -884,16 +884,11 @@ public void LayerSetAutoAnimated(object layerKey, bool autoAnimated) LayerSetAutoAnimated(layer, autoAnimated); } - #endregion - + [Obsolete("Use SpriteSystem.LayerSetRenderingStrategy() instead.")] public void LayerSetRenderingStrategy(int layer, LayerRenderingStrategy renderingStrategy) - { - if (!TryGetLayer(layer, out var theLayer, true)) - return; - - theLayer.RenderingStrategy = renderingStrategy; - } + => Sys.LayerSetRenderingStrategy((Owner, this), layer, renderingStrategy); + [Obsolete("Use SpriteSystem.LayerSetRenderingStrategy() instead.")] public void LayerSetRenderingStrategy(object layerKey, LayerRenderingStrategy renderingStrategy) { if (!LayerMapTryGet(layerKey, out var layer, true)) @@ -902,6 +897,9 @@ public void LayerSetRenderingStrategy(object layerKey, LayerRenderingStrategy re LayerSetRenderingStrategy(layer, renderingStrategy); } + #endregion + + /// public RSI.StateId LayerGetState(int layer) { diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs index 4673c678589..4c4bb6a97a5 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs @@ -598,10 +598,8 @@ public void LayerSetAutoAnimated(Entity sprite, int index, boo if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) return; - if (!TryGetLayer(sprite, index, out var layer, true)) - return; - - LayerSetAutoAnimated(layer, value); + if (TryGetLayer(sprite, index, out var layer, true)) + LayerSetAutoAnimated(layer, value); } public void LayerSetAutoAnimated(Layer layer, bool value) @@ -632,4 +630,40 @@ public void LayerSetAutoAnimated(Entity sprite, Enum key, bool } #endregion + + #region LayerSetRenderingStrategy + + public void LayerSetRenderingStrategy(Entity sprite, int index, LayerRenderingStrategy value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (TryGetLayer(sprite, index, out var layer, true)) + LayerSetRenderingStrategy(layer, value); + } + + public void LayerSetRenderingStrategy(Layer layer, LayerRenderingStrategy value) + { + layer.RenderingStrategy = value; + } + + public void LayerSetRenderingStrategy(Entity sprite, string key, LayerRenderingStrategy value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetRenderingStrategy(sprite, index, value); + } + + public void LayerSetRenderingStrategy(Entity sprite, Enum key, LayerRenderingStrategy value) + { + if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + return; + + if (LayerMapTryGet(sprite, key, out var index, true)) + LayerSetRenderingStrategy(sprite, index, value); + } + + #endregion } From 2eb303c255510ec0baf58b8bc48579dbca1ddfe0 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Mon, 13 Jan 2025 23:49:40 +1300 Subject: [PATCH 24/36] Reduce Resolves, Add Layer.Index --- .../Components/Renderable/SpriteComponent.cs | 26 +- .../EntitySystems/SpriteSystem.Layer.cs | 68 ++- .../EntitySystems/SpriteSystem.LayerMap.cs | 38 +- .../SpriteSystem.LayerSetters.cs | 504 +++++++----------- 4 files changed, 284 insertions(+), 352 deletions(-) diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index d181b9c6986..c49da287772 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -282,9 +282,11 @@ void ISerializationHooks.AfterDeserialization() Layers.Clear(); foreach (var datum in layerDatums) { - var layer = new Layer(this); + var layer = new Layer(); Layers.Add(layer); - LayerSetData(layer, Layers.Count - 1, datum); + layer.Owner = (Owner, this); + layer.Index = Layers.Count - 1; + LayerSetData(layer, datum); } } @@ -381,7 +383,7 @@ public int LayerMapReserveBlank(object key) [Obsolete("Use SpriteSystem.AddBlankLayer() instead.")] public int AddBlankLayer(int? newIndex = null) - => Sys.AddBlankLayer((Owner, this), newIndex); + => Sys.AddBlankLayer((Owner, this), newIndex).Index; [Obsolete("Use SpriteSystem.AddLayer() instead.")] public int AddLayer(PrototypeLayerData layerDatum, int? newIndex = null) @@ -496,8 +498,10 @@ public void LayerSetData(int index, PrototypeLayerData layerDatum) => Sys.LayerSetData((Owner, this), index, layerDatum); [Obsolete("Use SpriteSystem.LayerSetData() instead.")] - internal void LayerSetData(Layer layer, int index, PrototypeLayerData layerDatum) + internal void LayerSetData(Layer layer, PrototypeLayerData layerDatum) { + DebugTools.AssertEqual(layer, layer.Owner.Comp.Layers[layer.Index]); + if (!string.IsNullOrWhiteSpace(layerDatum.RsiPath)) { var path = TextureRoot / layerDatum.RsiPath; @@ -584,12 +588,12 @@ internal void LayerSetData(Layer layer, int index, PrototypeLayerData layerDatum if (LayerMap.TryGetValue(key, out var mappedIndex)) { - if (mappedIndex != index) + if (mappedIndex != layer.Index) Logger.ErrorS(LogCategory, "Duplicate layer map key definition: {0}", key); continue; } - LayerMap[key] = index; + LayerMap[key] = layer.Index; } } @@ -1143,7 +1147,8 @@ public sealed class Layer : ISpriteLayer, ISerializationHooks { internal SpriteComponent _parent => Owner.Comp; - [ViewVariables] public readonly Entity Owner; + [ViewVariables] public Entity Owner; + [ViewVariables] public int Index; [ViewVariables] public string? ShaderPrototype; [ViewVariables] public ShaderInstance? Shader; @@ -1312,15 +1317,12 @@ public Vector2 Offset [ViewVariables(VVAccess.ReadWrite)] public CopyToShaderParameters? CopyToShaderParameters; - public Layer(SpriteComponent parent) + public Layer() { - Owner = (parent.Owner, parent); } - public Layer(Layer toClone, SpriteComponent parentSprite) + public Layer(Layer toClone, SpriteComponent _) { - Owner = (parentSprite.Owner, parentSprite); - if (toClone.Shader != null) { Shader = toClone.Shader.Mutable ? toClone.Shader.Duplicate() : toClone.Shader; diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs index 7f52b0db5ef..70c2e0edf06 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs @@ -35,6 +35,8 @@ public bool TryGetLayer( 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; } @@ -59,16 +61,16 @@ public bool RemoveLayer( if (!_query.Resolve(sprite.Owner, ref sprite.Comp, logMissing)) return false; - if (index < 0 || index >= sprite.Comp.Layers.Count) - { - if (logMissing) - Log.Error($"Layer index '{index}' on entity {ToPrettyString(sprite)} does not exist! Trace:\n{Environment.StackTrace}"); + if (!TryGetLayer(sprite, index, out layer, logMissing)) return false; - } - layer = sprite.Comp.Layers[index]; 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) { @@ -80,6 +82,16 @@ public bool RemoveLayer( } } + layer.Owner = default; + layer.Index = -1; + +#if DEBUG + foreach (var otherLayer in sprite.Comp.Layers) + { + DebugTools.AssertEqual(otherLayer, sprite.Comp.Layers[otherLayer.Index]); + } +#endif + RebuildBounds(sprite!); QueueUpdateIsInert(sprite!); return true; @@ -94,12 +106,25 @@ public bool RemoveLayer( 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.Owner!; 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) @@ -108,13 +133,20 @@ public int AddLayer(Entity sprite, Layer layer, int? index = n } else { - index = sprite.Comp.Layers.Count; + 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 + RebuildBounds(sprite!); QueueUpdateIsInert(sprite!); - return index.Value; + return layer.Index; } /// @@ -130,7 +162,7 @@ public int AddRsiLayer(Entity sprite, RSI.StateId stateId, RSI if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) return -1; - var layer = new Layer(sprite.Comp) {State = stateId, RSI = rsi}; + var layer = new Layer {State = stateId, RSI = rsi}; rsi ??= sprite.Comp._baseRsi; if (rsi != null && rsi.TryGetState(stateId, out var state)) @@ -180,7 +212,7 @@ public int AddTextureLayer(Entity sprite, Texture? texture, in if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) return -1; - var layer = new Layer(sprite.Comp) {Texture = texture}; + var layer = new Layer {Texture = texture}; return AddLayer(sprite, layer, index); } @@ -202,21 +234,19 @@ public int AddLayer(Entity sprite, PrototypeLayerData layerDat if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) return -1; - index = AddBlankLayer(sprite, index); - sprite.Comp.LayerSetData(index.Value, layerDatum); - return index.Value; + var layer = AddBlankLayer(sprite!, index); + LayerSetData(layer, layerDatum); + return layer.Index; } /// /// Add a blank sprite layer. /// - public int AddBlankLayer(Entity sprite, int? index = null) + public Layer AddBlankLayer(Entity sprite, int? index = null) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return -1; - - var layer = new Layer(sprite.Comp); - return AddLayer(sprite, layer, index); + var layer = new Layer(); + AddLayer(sprite!, layer, index); + return layer; } #endregion diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerMap.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerMap.cs index 52cca68648b..d5239aa533d 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerMap.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerMap.cs @@ -152,6 +152,32 @@ public bool LayerMapTryGet(Entity sprite, string key, out int return false; } + /// + /// Attempt to resolve an enum mapping. + /// + public bool TryGetLayer(Entity sprite, Enum key, [NotNullWhen(true)] out Layer? layer, bool logMissing) + { + layer = null; + if (!_query.Resolve(sprite.Owner, ref sprite.Comp, logMissing)) + return false; + + return LayerMapTryGet(sprite, key, out var index, logMissing) + && TryGetLayer(sprite, index, out layer, logMissing); + } + + /// + /// Attempt to resolve a string mapping. + /// + public bool TryGetLayer(Entity sprite, string key, [NotNullWhen(true)] out Layer? layer, bool logMissing) + { + layer = null; + if (!_query.Resolve(sprite.Owner, ref sprite.Comp, logMissing)) + return false; + + return LayerMapTryGet(sprite, key, out var index, logMissing) + && TryGetLayer(sprite, index, out layer, logMissing); + } + public int LayerMapGet(Entity sprite, Enum key) { if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) @@ -197,9 +223,9 @@ public int LayerMapReserve(Entity sprite, Enum key) if (LayerExists(sprite, key)) throw new Exception("Layer already exists"); - var index = AddBlankLayer(sprite); - LayerMapSet(sprite, key, index); - return index; + var layer = AddBlankLayer(sprite!); + LayerMapSet(sprite, key, layer.Index); + return layer.Index; } /// @@ -214,9 +240,9 @@ public int LayerMapReserve(Entity sprite, string key) if (LayerExists(sprite, key)) throw new Exception("Layer already exists"); - var index = AddBlankLayer(sprite); - LayerMapSet(sprite, key, index); - return index; + var layer = AddBlankLayer(sprite!); + LayerMapSet(sprite, key, layer.Index); + return layer.Index; } public bool RemoveLayer(Entity sprite, string key, bool logMissing = true) diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs index 4c4bb6a97a5..4afeb8d3b7b 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs @@ -20,38 +20,26 @@ public sealed partial class SpriteSystem public void LayerSetData(Entity sprite, int index, PrototypeLayerData data) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - if (TryGetLayer(sprite, index, out var layer, true)) LayerSetData(layer, data); } - public void LayerSetData(Layer layer, PrototypeLayerData data) - { - // TODO SPRITE store layer index. - var index = layer._parent.Layers.IndexOf(layer); - - // TODO SPRITE ECS - layer._parent.LayerSetData(layer, index, data); - } - public void LayerSetData(Entity sprite, string key, PrototypeLayerData data) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetData(sprite, index, data); + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetData(layer, data); } public void LayerSetData(Entity sprite, Enum key, PrototypeLayerData data) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetData(layer, data); + } - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetData(sprite, index, data); + public void LayerSetData(Layer layer, PrototypeLayerData data) + { + // TODO SPRITE ECS + layer._parent.LayerSetData(layer, data); } #endregion @@ -59,15 +47,33 @@ public void LayerSetData(Entity sprite, Enum key, PrototypeLay #region SpriteSpecifier public void LayerSetSprite(Entity sprite, int index, SpriteSpecifier specifier) + { + if (TryGetLayer(sprite, index, out var layer, true)) + LayerSetSprite(layer, specifier); + } + + public void LayerSetSprite(Entity sprite, string key, SpriteSpecifier specifier) + { + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetSprite(layer, specifier); + } + + public void LayerSetSprite(Entity sprite, Enum key, SpriteSpecifier specifier) + { + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetSprite(layer, specifier); + } + + public void LayerSetSprite(Layer layer, SpriteSpecifier specifier) { switch (specifier) { case SpriteSpecifier.Texture tex: - LayerSetTexture(sprite, index, tex.TexturePath); + LayerSetTexture(layer, tex.TexturePath); break; case SpriteSpecifier.Rsi rsi: - LayerSetRsi(sprite, index, rsi.RsiPath, rsi.RsiState); + LayerSetRsi(layer, rsi.RsiPath, rsi.RsiState); break; default: @@ -75,62 +81,53 @@ public void LayerSetSprite(Entity sprite, int index, SpriteSpe } } - public void LayerSetSprite(Entity sprite, string key, SpriteSpecifier specifier) - { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetSprite(sprite, index, specifier); - } - - public void LayerSetSprite(Entity sprite, Enum key, SpriteSpecifier specifier) - { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetSprite(sprite, index, specifier); - } - #endregion #region Texture public void LayerSetTexture(Entity sprite, int index, Texture? texture) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - if (TryGetLayer(sprite, index, out var layer, true)) LayerSetTexture(layer, texture); } + public void LayerSetTexture(Entity sprite, string key, Texture? texture) + { + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetTexture(layer, texture); + } + + public void LayerSetTexture(Entity sprite, Enum key, Texture? texture) + { + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetTexture(layer, texture); + } + public void LayerSetTexture(Layer layer, Texture? texture) { LayerSetRsiState(layer, StateId.Invalid, refresh: true); layer.Texture = texture; } - public void LayerSetTexture(Entity sprite, string key, Texture? texture) + public void LayerSetTexture(Entity sprite, int index, ResPath path) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetTexture(sprite, index, texture); + if (TryGetLayer(sprite, index, out var layer, true)) + LayerSetTexture(layer, path); } - public void LayerSetTexture(Entity sprite, Enum key, Texture? texture) + public void LayerSetTexture(Entity sprite, string key, ResPath path) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetTexture(layer, path); + } - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetTexture(sprite, index, texture); + public void LayerSetTexture(Entity sprite, Enum key, ResPath path) + { + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetTexture(layer, path); } - public void LayerSetTexture(Entity sprite, int index, ResPath path) + private void LayerSetTexture(Layer layer, ResPath path) { if (!_resourceCache.TryGetResource(TextureRoot / path, out var texture)) { @@ -139,25 +136,7 @@ public void LayerSetTexture(Entity sprite, int index, ResPath Log.Error($"Unable to load texture '{path}'. Trace:\n{Environment.StackTrace}"); } - LayerSetTexture(sprite, index, texture?.Texture); - } - - public void LayerSetTexture(Entity sprite, string key, ResPath texture) - { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetTexture(sprite, index, texture); - } - - public void LayerSetTexture(Entity sprite, Enum key, ResPath texture) - { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetTexture(sprite, index, texture); + LayerSetTexture(layer, texture?.Texture); } #endregion @@ -166,13 +145,22 @@ public void LayerSetTexture(Entity sprite, Enum key, ResPath t public void LayerSetRsiState(Entity sprite, int index, StateId state) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - if (TryGetLayer(sprite, index, out var layer, true)) LayerSetRsiState(layer, state); } + public void LayerSetRsiState(Entity sprite, string key, StateId state) + { + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetRsiState(layer, state); + } + + public void LayerSetRsiState(Entity sprite, Enum key, StateId state) + { + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetRsiState(layer, state); + } + public void LayerSetRsiState(Layer layer, StateId state, bool refresh = false) { if (layer.StateId == state && !refresh) @@ -184,15 +172,17 @@ public void LayerSetRsiState(Layer layer, StateId state, bool refresh = false) { layer._actualState = null; } - else if (layer.ActualRsi is not {} rsi) + else if (layer.ActualRsi is not { } rsi) { - Log.Error($"{ToPrettyString(layer.Owner)} has no RSI to pull new state from! Trace:\n{Environment.StackTrace}"); + Log.Error( + $"{ToPrettyString(layer.Owner)} has no RSI to pull new state from! Trace:\n{Environment.StackTrace}"); layer._actualState = GetFallbackState(); } else if (!rsi.TryGetState(layer.StateId, out layer._actualState)) { layer._actualState = GetFallbackState(); - Log.Error($"{ToPrettyString(layer.Owner)}'s state '{state}' does not exist in RSI {rsi.Path}. Trace:\n{Environment.StackTrace}"); + Log.Error( + $"{ToPrettyString(layer.Owner)}'s state '{state}' does not exist in RSI {rsi.Path}. Trace:\n{Environment.StackTrace}"); } layer.AnimationFrame = 0; @@ -203,88 +193,58 @@ public void LayerSetRsiState(Layer layer, StateId state, bool refresh = false) QueueUpdateIsInert(layer.Owner); } - public void LayerSetRsiState(Entity sprite, string key, StateId state) - { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetRsiState(sprite, index, state); - } - - public void LayerSetRsiState(Entity sprite, Enum key, StateId state) - { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetRsiState(sprite, index, state); - } - #endregion #region Rsi public void LayerSetRsi(Entity sprite, int index, RSI? rsi, StateId? state = null) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - - if (!TryGetLayer(sprite, index, out var layer, true)) - return; - - layer._rsi = rsi; - LayerSetRsiState(layer, state ?? layer.StateId, refresh: true); - } - - public void LayerSetRsi(Entity sprite, Layer layer, RSI? rsi, StateId? state = null) - { - layer._rsi = rsi; - LayerSetRsiState(layer, state ?? layer.StateId, refresh: true); + if (TryGetLayer(sprite, index, out var layer, true)) + LayerSetRsi(layer, rsi, state); } public void LayerSetRsi(Entity sprite, string key, RSI? rsi, StateId? state = null) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetRsi(sprite, index, rsi, state); + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetRsi(layer, rsi, state); } public void LayerSetRsi(Entity sprite, Enum key, RSI? rsi, StateId? state = null) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetRsi(layer, rsi, state); + } - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetRsi(sprite, index, rsi, state); + public void LayerSetRsi(Layer layer, RSI? rsi, StateId? state = null) + { + layer._rsi = rsi; + LayerSetRsiState(layer, state ?? layer.StateId, refresh: true); } public void LayerSetRsi(Entity sprite, int index, ResPath rsi, StateId? state = null) { - if (!_resourceCache.TryGetResource(TextureRoot / rsi, out var res)) - Log.Error($"Unable to load RSI '{rsi}' for entity {ToPrettyString(sprite)}. Trace:\n{Environment.StackTrace}"); - - LayerSetRsi(sprite, index, res?.RSI, state); + if (TryGetLayer(sprite, index, out var layer, true)) + LayerSetRsi(layer, rsi, state); } public void LayerSetRsi(Entity sprite, string key, ResPath rsi, StateId? state = null) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetRsi(sprite, index, rsi, state); + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetRsi(layer, rsi, state); } public void LayerSetRsi(Entity sprite, Enum key, ResPath rsi, StateId? state = null) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetRsi(layer, rsi, state); + } - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetRsi(sprite, index, rsi, state); + public void LayerSetRsi(Layer layer, ResPath rsi, StateId? state = null) + { + if (!_resourceCache.TryGetResource(TextureRoot / rsi, out var res)) + Log.Error($"Unable to load RSI '{rsi}' for entity {ToPrettyString(layer.Owner)}. Trace:\n{Environment.StackTrace}"); + + LayerSetRsi(layer, res?.RSI, state); } #endregion @@ -293,13 +253,22 @@ public void LayerSetRsi(Entity sprite, Enum key, ResPath rsi, public void LayerSetScale(Entity sprite, int index, Vector2 value) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - if (TryGetLayer(sprite, index, out var layer, true)) LayerSetScale(layer, value); } + public void LayerSetScale(Entity sprite, string key, Vector2 value) + { + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetScale(layer, value); + } + + public void LayerSetScale(Entity sprite, Enum key, Vector2 value) + { + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetScale(layer, value); + } + public void LayerSetScale(Layer layer, Vector2 value) { if (layer._scale.EqualsApprox(value)) @@ -313,63 +282,36 @@ public void LayerSetScale(Layer layer, Vector2 value) RebuildBounds(layer.Owner); } - public void LayerSetScale(Entity sprite, string key, Vector2 value) - { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetScale(sprite, index, value); - } - - public void LayerSetScale(Entity sprite, Enum key, Vector2 value) - { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetScale(sprite, index, value); - } - #endregion #region Rotation public void LayerSetRotation(Entity sprite, int index, Angle value) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - if (TryGetLayer(sprite, index, out var layer, true)) LayerSetRotation(layer, value); } - public void LayerSetRotation(Layer layer, Angle value) + public void LayerSetRotation(Entity sprite, string key, Angle value) { - if (layer._rotation.EqualsApprox(value)) - return; - - layer._rotation = value; - layer.UpdateLocalMatrix(); - RebuildBounds(layer.Owner); + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetRotation(layer, value); } - public void LayerSetRotation(Entity sprite, string key, Angle value) + public void LayerSetRotation(Entity sprite, Enum key, Angle value) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetRotation(sprite, index, value); + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetRotation(layer, value); } - public void LayerSetRotation(Entity sprite, Enum key, Angle value) + public void LayerSetRotation(Layer layer, Angle value) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + if (layer._rotation.EqualsApprox(value)) return; - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetRotation(sprite, index, value); + layer._rotation = value; + layer.UpdateLocalMatrix(); + RebuildBounds(layer.Owner); } #endregion @@ -378,39 +320,30 @@ public void LayerSetRotation(Entity sprite, Enum key, Angle va public void LayerSetOffset(Entity sprite, int index, Vector2 value) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - if (TryGetLayer(sprite, index, out var layer, true)) LayerSetOffset(layer, value); } - public void LayerSetOffset(Layer layer, Vector2 value) + public void LayerSetOffset(Entity sprite, string key, Vector2 value) { - if (layer._offset.EqualsApprox(value)) - return; - - layer._offset = value; - layer.UpdateLocalMatrix(); - RebuildBounds(layer.Owner); + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetOffset(layer, value); } - public void LayerSetOffset(Entity sprite, string key, Vector2 value) + public void LayerSetOffset(Entity sprite, Enum key, Vector2 value) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetOffset(sprite, index, value); + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetOffset(layer, value); } - public void LayerSetOffset(Entity sprite, Enum key, Vector2 value) + public void LayerSetOffset(Layer layer, Vector2 value) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + if (layer._offset.EqualsApprox(value)) return; - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetOffset(sprite, index, value); + layer._offset = value; + layer.UpdateLocalMatrix(); + RebuildBounds(layer.Owner); } #endregion @@ -419,39 +352,30 @@ public void LayerSetOffset(Entity sprite, Enum key, Vector2 va public void LayerSetVisible(Entity sprite, int index, bool value) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - if (TryGetLayer(sprite, index, out var layer, true)) LayerSetVisible(layer, value); } - public void LayerSetVisible(Layer layer, bool value) + public void LayerSetVisible(Entity sprite, string key, bool value) { - if (layer._visible == value) - return; - - layer._visible = value; - QueueUpdateIsInert(layer.Owner); - RebuildBounds(layer.Owner); + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetVisible(layer, value); } - public void LayerSetVisible(Entity sprite, string key, bool value) + public void LayerSetVisible(Entity sprite, Enum key, bool value) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetVisible(sprite, index, value); + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetVisible(layer, value); } - public void LayerSetVisible(Entity sprite, Enum key, bool value) + public void LayerSetVisible(Layer layer, bool value) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + if (layer._visible == value) return; - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetVisible(sprite, index, value); + layer._visible = value; + QueueUpdateIsInert(layer.Owner); + RebuildBounds(layer.Owner); } #endregion @@ -460,38 +384,25 @@ public void LayerSetVisible(Entity sprite, Enum key, bool valu public void LayerSetColor(Entity sprite, int index, Color value) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - - if (!TryGetLayer(sprite, index, out var layer, true)) - return; - - LayerSetColor(layer, value); - layer.Color = value; - } - - public void LayerSetColor(Layer layer, Color value) - { - //Yes this is trivial, but this is here mainly for future proofing. - layer.Color = value; + if (TryGetLayer(sprite, index, out var layer, true)) + LayerSetColor(layer, value); } public void LayerSetColor(Entity sprite, string key, Color value) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetColor(sprite, index, value); + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetColor(layer, value); } public void LayerSetColor(Entity sprite, Enum key, Color value) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetColor(layer, value); + } - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetColor(sprite, index, value); + public void LayerSetColor(Layer layer, Color value) + { + layer.Color = value; } #endregion @@ -500,35 +411,25 @@ public void LayerSetColor(Entity sprite, Enum key, Color value public void LayerSetDirOffset(Entity sprite, int index, DirectionOffset value) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - if (TryGetLayer(sprite, index, out var layer, true)) LayerSetDirOffset(layer, value); } - public void LayerSetDirOffset(Layer layer, DirectionOffset value) - { - //Yes this is trivial, but this is here mainly for future proofing. - layer.DirOffset = value; - } - public void LayerSetDirOffset(Entity sprite, string key, DirectionOffset value) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetDirOffset(sprite, index, value); + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetDirOffset(layer, value); } public void LayerSetDirOffset(Entity sprite, Enum key, DirectionOffset value) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetDirOffset(layer, value); + } - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetDirOffset(sprite, index, value); + public void LayerSetDirOffset(Layer layer, DirectionOffset value) + { + layer.DirOffset = value; } #endregion @@ -537,19 +438,28 @@ public void LayerSetDirOffset(Entity sprite, Enum key, Directi public void LayerSetAnimationTime(Entity sprite, int index, float value) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - if (TryGetLayer(sprite, index, out var layer, true)) LayerSetAnimationTime(layer, value); } + public void LayerSetAnimationTime(Entity sprite, string key, float value) + { + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetAnimationTime(layer, value); + } + + public void LayerSetAnimationTime(Entity sprite, Enum key, float value) + { + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetAnimationTime(layer, value); + } + public void LayerSetAnimationTime(Layer layer, float value) { if (!layer.StateId.IsValid) return; - if (layer.ActualRsi is not {} rsi) + if (layer.ActualRsi is not { } rsi) return; var state = rsi[layer.StateId]; @@ -571,62 +481,35 @@ public void LayerSetAnimationTime(Layer layer, float value) layer.SetAnimationTime(value); } - public void LayerSetAnimationTime(Entity sprite, string key, float value) - { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetAnimationTime(sprite, index, value); - } - - public void LayerSetAnimationTime(Entity sprite, Enum key, float value) - { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetAnimationTime(sprite, index, value); - } - #endregion #region AutoAnimated public void LayerSetAutoAnimated(Entity sprite, int index, bool value) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - if (TryGetLayer(sprite, index, out var layer, true)) LayerSetAutoAnimated(layer, value); } - public void LayerSetAutoAnimated(Layer layer, bool value) + public void LayerSetAutoAnimated(Entity sprite, string key, bool value) { - if (layer._autoAnimated == value) - return; - - layer._autoAnimated = value; - QueueUpdateIsInert(layer.Owner); + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetAutoAnimated(layer, value); } - public void LayerSetAutoAnimated(Entity sprite, string key, bool value) + public void LayerSetAutoAnimated(Entity sprite, Enum key, bool value) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetAutoAnimated(sprite, index, value); + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetAutoAnimated(layer, value); } - public void LayerSetAutoAnimated(Entity sprite, Enum key, bool value) + public void LayerSetAutoAnimated(Layer layer, bool value) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) + if (layer._autoAnimated == value) return; - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetAutoAnimated(sprite, index, value); + layer._autoAnimated = value; + QueueUpdateIsInert(layer.Owner); } #endregion @@ -635,34 +518,25 @@ public void LayerSetAutoAnimated(Entity sprite, Enum key, bool public void LayerSetRenderingStrategy(Entity sprite, int index, LayerRenderingStrategy value) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - if (TryGetLayer(sprite, index, out var layer, true)) LayerSetRenderingStrategy(layer, value); } - public void LayerSetRenderingStrategy(Layer layer, LayerRenderingStrategy value) - { - layer.RenderingStrategy = value; - } - public void LayerSetRenderingStrategy(Entity sprite, string key, LayerRenderingStrategy value) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; - - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetRenderingStrategy(sprite, index, value); + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetRenderingStrategy(layer, value); } public void LayerSetRenderingStrategy(Entity sprite, Enum key, LayerRenderingStrategy value) { - if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) - return; + if (TryGetLayer(sprite, key, out var layer, true)) + LayerSetRenderingStrategy(layer, value); + } - if (LayerMapTryGet(sprite, key, out var index, true)) - LayerSetRenderingStrategy(sprite, index, value); + public void LayerSetRenderingStrategy(Layer layer, LayerRenderingStrategy value) + { + layer.RenderingStrategy = value; } #endregion From 5bc336bf4d3b80b20e8e515943fda33700b4b8eb Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Mon, 13 Jan 2025 23:52:02 +1300 Subject: [PATCH 25/36] Access --- .../GameObjects/Components/Renderable/SpriteComponent.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index c49da287772..2c7b9b750fa 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -1147,7 +1147,10 @@ public sealed class Layer : ISpriteLayer, ISerializationHooks { internal SpriteComponent _parent => Owner.Comp; + [Access(typeof(SpriteSystem), typeof(SpriteComponent))] [ViewVariables] public Entity Owner; + + [Access(typeof(SpriteSystem), typeof(SpriteComponent))] [ViewVariables] public int Index; [ViewVariables] public string? ShaderPrototype; From c3ec771b159d986bb3534cce6caa366e5cbb4cf3 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Tue, 14 Jan 2025 00:45:58 +1300 Subject: [PATCH 26/36] Try fix NREs --- .../Components/Renderable/SpriteComponent.cs | 38 +++++++++--- .../EntitySystems/SpriteSystem.Component.cs | 60 +++++++++++-------- .../EntitySystems/SpriteSystem.Layer.cs | 20 ++++--- 3 files changed, 75 insertions(+), 43 deletions(-) diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index 2c7b9b750fa..411099b7871 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -282,10 +282,8 @@ void ISerializationHooks.AfterDeserialization() Layers.Clear(); foreach (var datum in layerDatums) { - var layer = new Layer(); + var layer = new Layer((Owner, this), Layers.Count); Layers.Add(layer); - layer.Owner = (Owner, this); - layer.Index = Layers.Count - 1; LayerSetData(layer, datum); } @@ -310,7 +308,7 @@ void ISerializationHooks.AfterDeserialization() [Obsolete("Use SpriteSystem.CopySprite() instead.")] public void CopyFrom(SpriteComponent other) { - Sys.CopySprite(other, this); + Sys.CopySprite((other.Owner, other), (Owner, this)); } [Obsolete("Use LocalMatrix")] @@ -1147,11 +1145,20 @@ public sealed class Layer : ISpriteLayer, ISerializationHooks { internal SpriteComponent _parent => Owner.Comp; + /// + /// The entity that this layer belongs to. + /// [Access(typeof(SpriteSystem), typeof(SpriteComponent))] - [ViewVariables] public Entity Owner; + [ViewVariables] internal Entity Owner; + // Internal, because I might want to change this in future W/O breaking changes. + // Also, it's possible for SpriteComponent to be null if it is not currently attached to a sprite. + /// + /// The index of the layer within its layer collection (usually a SpriteComponent). + /// [Access(typeof(SpriteSystem), typeof(SpriteComponent))] - [ViewVariables] public int Index; + [ViewVariables] internal int Index; + // Internal, because I might want to change this in future W/O breaking changes. [ViewVariables] public string? ShaderPrototype; [ViewVariables] public ShaderInstance? Shader; @@ -1320,11 +1327,24 @@ public Vector2 Offset [ViewVariables(VVAccess.ReadWrite)] public CopyToShaderParameters? CopyToShaderParameters; - public Layer() + [Obsolete("Use SpriteSystem.AddBlankLayer")] + public Layer(SpriteComponent parent) + { + Owner = (parent.Owner, parent); + } + + internal Layer() + { + } + + internal Layer(Entity owner, int index) { + Owner = owner; + Index = index; } - public Layer(Layer toClone, SpriteComponent _) + [Obsolete] // This should be internal to SpriteSystem + public Layer(Layer toClone, SpriteComponent parent) : this(parent) { if (toClone.Shader != null) { @@ -1350,6 +1370,8 @@ public Layer(Layer toClone, SpriteComponent _) CopyToShaderParameters = new CopyToShaderParameters(copyToShaderParameters); } + // TODO SPRITE + // Is Layer even serializable? void ISerializationHooks.AfterDeserialization() { UpdateLocalMatrix(); diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Component.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Component.cs index 325fb6abf9d..94e2f5b36d1 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Component.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Component.cs @@ -55,34 +55,42 @@ public void CopySprite(Entity source, Entity if (!Resolve(target.Owner, ref target.Comp)) return; - CopySprite(source.Comp, target.Comp); - } - - public void CopySprite(SpriteComponent source, SpriteComponent target) - { - target._baseRsi = source._baseRsi; - target._bounds = source._bounds; - target._visible = source._visible; - target.color = source.color; - target.offset = source.offset; - target.rotation = source.rotation; - target.scale = source.scale; - target.LocalMatrix = Matrix3Helpers.CreateTransform(in target.offset, in target.rotation, in target.scale); - target.drawDepth = source.drawDepth; - target.NoRotation = source.NoRotation; - target.DirectionOverride = source.DirectionOverride; - target.EnableDirectionOverride = source.EnableDirectionOverride; - target.Layers = new List(source.Layers.Count); - foreach (var otherLayer in source.Layers) + target.Comp._baseRsi = source.Comp._baseRsi; + target.Comp._bounds = source.Comp._bounds; + target.Comp._visible = source.Comp._visible; + target.Comp.color = source.Comp.color; + target.Comp.offset = source.Comp.offset; + target.Comp.rotation = source.Comp.rotation; + target.Comp.scale = source.Comp.scale; + target.Comp.LocalMatrix = Matrix3Helpers.CreateTransform( + in target.Comp.offset, + in target.Comp.rotation, + in target + .Comp.scale); + + target.Comp.drawDepth = source.Comp.drawDepth; + target.Comp.NoRotation = source.Comp.NoRotation; + target.Comp.DirectionOverride = source.Comp.DirectionOverride; + target.Comp.EnableDirectionOverride = source.Comp.EnableDirectionOverride; + target.Comp.Layers = new List(source.Comp.Layers.Count); + foreach (var otherLayer in source.Comp.Layers) { - target.Layers.Add(new SpriteComponent.Layer(otherLayer, target)); + var layer = new SpriteComponent.Layer(otherLayer, target.Comp); + layer.Index = target.Comp.Layers.Count; + layer.Owner = target!; + target.Comp.Layers.Add(layer); } - target.IsInert = source.IsInert; - target.LayerMap = source.LayerMap.ShallowClone(); - target.PostShader = source.PostShader is {Mutable: true} ? source.PostShader.Duplicate() : source.PostShader; - target.RenderOrder = source.RenderOrder; - target.GranularLayersRendering = source.GranularLayersRendering; + target.Comp.IsInert = source.Comp.IsInert; + target.Comp.LayerMap = source.Comp.LayerMap.ShallowClone(); + target.Comp.PostShader = source.Comp.PostShader is {Mutable: true} + ? source.Comp.PostShader.Duplicate() + : source.Comp.PostShader; + + target.Comp.RenderOrder = source.Comp.RenderOrder; + target.Comp.GranularLayersRendering = source.Comp.GranularLayersRendering; + + _tree.QueueTreeUpdate(target!); } public void RebuildBounds(Entity sprite) @@ -90,7 +98,7 @@ public void RebuildBounds(Entity sprite) // TODO SPRITE // Maybe the bounds calculation should be deferred? // The tree update is already deferred anyways. - // And layer.CalculateBoundingBox() is relatively expensive. + // And layer.CalculateBoundingBox() is relatively expensive for sprites like players var bounds = new Box2(); foreach (var layer in sprite.Comp.Layers) diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs index 70c2e0edf06..ecdab81df1a 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs @@ -112,7 +112,7 @@ public int AddLayer(Entity sprite, Layer layer, int? index = n return -1; } - layer.Owner = sprite.Owner!; + layer.Owner = sprite!; if (index is { } i && i != sprite.Comp.Layers.Count) { @@ -144,8 +144,11 @@ public int AddLayer(Entity sprite, Layer layer, int? index = n } #endif - RebuildBounds(sprite!); - QueueUpdateIsInert(sprite!); + if (!layer.Blank) + { + RebuildBounds(sprite!); + QueueUpdateIsInert(sprite!); + } return layer.Index; } @@ -162,15 +165,14 @@ public int AddRsiLayer(Entity sprite, RSI.StateId stateId, RSI if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) return -1; - var layer = new Layer {State = stateId, RSI = rsi}; - rsi ??= sprite.Comp._baseRsi; + var layer = AddBlankLayer(sprite!, index); - if (rsi != null && rsi.TryGetState(stateId, out var state)) - layer.AnimationTimeLeft = state.GetDelay(0); + if (rsi != null) + LayerSetRsi(layer, rsi, stateId); else - Log.Error($"State does not exist in RSI: '{stateId}'. Trace:\n{Environment.StackTrace}"); + LayerSetRsiState(layer, stateId); - return AddLayer(sprite, layer, index); + return layer.Index; } /// From 954c3bb14b8193c68ae3922c2f95fcbf72af256b Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Tue, 14 Jan 2025 00:57:06 +1300 Subject: [PATCH 27/36] Asserts --- .../SpriteSystem.LayerSetters.cs | 43 +++++++++++++++++++ Robust.Client/Utility/SpriteSpecifierExt.cs | 4 +- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs index 4afeb8d3b7b..8c532d59458 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs @@ -38,6 +38,9 @@ public void LayerSetData(Entity sprite, Enum key, PrototypeLay public void LayerSetData(Layer layer, PrototypeLayerData data) { + DebugTools.Assert(layer.Owner != default); + DebugTools.AssertNotNull(layer.Owner.Comp); + DebugTools.AssertEqual(layer.Owner.Comp.Layers[layer.Index], layer); // TODO SPRITE ECS layer._parent.LayerSetData(layer, data); } @@ -163,6 +166,10 @@ public void LayerSetRsiState(Entity sprite, Enum key, StateId public void LayerSetRsiState(Layer layer, StateId state, bool refresh = false) { + DebugTools.Assert(layer.Owner != default); + DebugTools.AssertNotNull(layer.Owner.Comp); + DebugTools.AssertEqual(layer.Owner.Comp.Layers[layer.Index], layer); + if (layer.StateId == state && !refresh) return; @@ -271,6 +278,10 @@ public void LayerSetScale(Entity sprite, Enum key, Vector2 val public void LayerSetScale(Layer layer, Vector2 value) { + DebugTools.Assert(layer.Owner != default); + DebugTools.AssertNotNull(layer.Owner.Comp); + DebugTools.AssertEqual(layer.Owner.Comp.Layers[layer.Index], layer); + if (layer._scale.EqualsApprox(value)) return; @@ -306,6 +317,10 @@ public void LayerSetRotation(Entity sprite, Enum key, Angle va public void LayerSetRotation(Layer layer, Angle value) { + DebugTools.Assert(layer.Owner != default); + DebugTools.AssertNotNull(layer.Owner.Comp); + DebugTools.AssertEqual(layer.Owner.Comp.Layers[layer.Index], layer); + if (layer._rotation.EqualsApprox(value)) return; @@ -338,6 +353,10 @@ public void LayerSetOffset(Entity sprite, Enum key, Vector2 va public void LayerSetOffset(Layer layer, Vector2 value) { + DebugTools.Assert(layer.Owner != default); + DebugTools.AssertNotNull(layer.Owner.Comp); + DebugTools.AssertEqual(layer.Owner.Comp.Layers[layer.Index], layer); + if (layer._offset.EqualsApprox(value)) return; @@ -370,6 +389,10 @@ public void LayerSetVisible(Entity sprite, Enum key, bool valu public void LayerSetVisible(Layer layer, bool value) { + DebugTools.Assert(layer.Owner != default); + DebugTools.AssertNotNull(layer.Owner.Comp); + DebugTools.AssertEqual(layer.Owner.Comp.Layers[layer.Index], layer); + if (layer._visible == value) return; @@ -402,6 +425,10 @@ public void LayerSetColor(Entity sprite, Enum key, Color value public void LayerSetColor(Layer layer, Color value) { + DebugTools.Assert(layer.Owner != default); + DebugTools.AssertNotNull(layer.Owner.Comp); + DebugTools.AssertEqual(layer.Owner.Comp.Layers[layer.Index], layer); + layer.Color = value; } @@ -429,6 +456,10 @@ public void LayerSetDirOffset(Entity sprite, Enum key, Directi public void LayerSetDirOffset(Layer layer, DirectionOffset value) { + DebugTools.Assert(layer.Owner != default); + DebugTools.AssertNotNull(layer.Owner.Comp); + DebugTools.AssertEqual(layer.Owner.Comp.Layers[layer.Index], layer); + layer.DirOffset = value; } @@ -456,6 +487,10 @@ public void LayerSetAnimationTime(Entity sprite, Enum key, flo public void LayerSetAnimationTime(Layer layer, float value) { + DebugTools.Assert(layer.Owner != default); + DebugTools.AssertNotNull(layer.Owner.Comp); + DebugTools.AssertEqual(layer.Owner.Comp.Layers[layer.Index], layer); + if (!layer.StateId.IsValid) return; @@ -505,6 +540,10 @@ public void LayerSetAutoAnimated(Entity sprite, Enum key, bool public void LayerSetAutoAnimated(Layer layer, bool value) { + DebugTools.Assert(layer.Owner != default); + DebugTools.AssertNotNull(layer.Owner.Comp); + DebugTools.AssertEqual(layer.Owner.Comp.Layers[layer.Index], layer); + if (layer._autoAnimated == value) return; @@ -536,6 +575,10 @@ public void LayerSetRenderingStrategy(Entity sprite, Enum key, public void LayerSetRenderingStrategy(Layer layer, LayerRenderingStrategy value) { + DebugTools.Assert(layer.Owner != default); + DebugTools.AssertNotNull(layer.Owner.Comp); + DebugTools.AssertEqual(layer.Owner.Comp.Layers[layer.Index], layer); + layer.RenderingStrategy = value; } diff --git a/Robust.Client/Utility/SpriteSpecifierExt.cs b/Robust.Client/Utility/SpriteSpecifierExt.cs index c55ae24c587..1a90011a745 100644 --- a/Robust.Client/Utility/SpriteSpecifierExt.cs +++ b/Robust.Client/Utility/SpriteSpecifierExt.cs @@ -27,9 +27,9 @@ public static Texture GetTexture(this SpriteSpecifier.Texture texSpecifier, IRes [Obsolete("Use SpriteSystem")] public static RSI.State GetState(this SpriteSpecifier.Rsi rsiSpecifier, IResourceCache cache) { - var sys = IoCManager.Resolve().GetEntitySystem(); if (!cache.TryGetResource(SpriteSpecifierSerializer.TextureRoot / rsiSpecifier.RsiPath, out var theRsi)) { + var sys = IoCManager.Resolve().GetEntitySystem(); Logger.Error("SpriteSpecifier failed to load RSI {0}", rsiSpecifier.RsiPath); return sys.GetFallbackState(); } @@ -40,7 +40,7 @@ public static RSI.State GetState(this SpriteSpecifier.Rsi rsiSpecifier, IResourc } Logger.Error($"SpriteSpecifier has invalid RSI state '{rsiSpecifier.RsiState}' for RSI: {rsiSpecifier.RsiPath}"); - return sys.GetFallbackState(); + return IoCManager.Resolve().GetEntitySystem().GetFallbackState(); } [Obsolete("Use SpriteSystem")] From 595593345aac9fddd756c84755457fbb0e554a90 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Tue, 14 Jan 2025 13:38:56 +1300 Subject: [PATCH 28/36] LayerGetState --- .../Components/Renderable/SpriteComponent.cs | 9 +- .../SpriteSystem.LayerGetters.cs | 89 +++++++++++++++++++ 2 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerGetters.cs diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index 411099b7871..1d45ddf8b0a 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -899,10 +899,7 @@ public void LayerSetRenderingStrategy(object layerKey, LayerRenderingStrategy re LayerSetRenderingStrategy(layer, renderingStrategy); } - #endregion - - - /// + [Obsolete("Use SpriteSystem.LayerGetRsiState() instead.")] public RSI.StateId LayerGetState(int layer) { if (!TryGetLayer(layer, out var theLayer, true)) @@ -911,16 +908,20 @@ public RSI.StateId LayerGetState(int layer) return theLayer.State; } + [Obsolete("Use SpriteSystem.LayerGetEffectiveRsi() instead.")] public RSI? LayerGetActualRSI(int layer) { return this[layer].ActualRsi; } + [Obsolete("Use SpriteSystem.LayerGetEffectiveRsi() instead.")] public RSI? LayerGetActualRSI(object layerKey) { return this[layerKey].ActualRsi; } + #endregion + public void LayerSetShader(int layer, ShaderInstance? shader, string? prototype = null) { if (!TryGetLayer(layer, out var theLayer, true)) diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerGetters.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerGetters.cs new file mode 100644 index 00000000000..e38152b64af --- /dev/null +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerGetters.cs @@ -0,0 +1,89 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Robust.Client.Graphics; +using Robust.Client.ResourceManagement; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.TypeSerializers.Implementations; +using Robust.Shared.Utility; +using static Robust.Client.GameObjects.SpriteComponent; +using static Robust.Client.Graphics.RSI; + +namespace Robust.Client.GameObjects; + +// This partial class contains various public methods for reading a layer's properties +public sealed partial class SpriteSystem +{ + #region RsiState + + /// + /// Get the RSI state being used by the current layer. Note that the return value may be an invalid state. E.g., + /// this might be a texture layer that does not use RSIs. + /// + public StateId LayerGetRsiState(Entity sprite, int index) + { + if (TryGetLayer(sprite, index, out var layer, true)) + return layer.StateId; + + return StateId.Invalid; + } + + /// + /// Get the RSI state being used by the current layer. Note that the return value may be an invalid state. E.g., + /// this might be a texture layer that does not use RSIs. + /// + public StateId LayerGetRsiState(Entity sprite, string key, StateId state) + { + if (TryGetLayer(sprite, key, out var layer, true)) + return layer.StateId; + + return StateId.Invalid; + } + + /// + /// Get the RSI state being used by the current layer. Note that the return value may be an invalid state. E.g., + /// this might be a texture layer that does not use RSIs. + /// + public StateId LayerGetRsiState(Entity sprite, Enum key, StateId state) + { + if (TryGetLayer(sprite, key, out var layer, true)) + return layer.StateId; + + return StateId.Invalid; + } + + #endregion + + #region RsiState + + /// + /// Returns the RSI being used by the layer to resolve it's RSI state. If the layer does not specify an RSI, this + /// will just be the base RSI of the owning sprite (). + /// + public RSI? LayerGetEffectiveRsi(Entity sprite, int index) + { + TryGetLayer(sprite, index, out var layer, true); + return layer?.ActualRsi; + } + + /// + /// Returns the RSI being used by the layer to resolve it's RSI state. If the layer does not specify an RSI, this + /// will just be the base RSI of the owning sprite (). + /// + public RSI? LayerGetEffectiveRsi(Entity sprite, string key, StateId state) + { + TryGetLayer(sprite, key, out var layer, true); + return layer?.ActualRsi; + } + + /// + /// Returns the RSI being used by the layer to resolve it's RSI state. If the layer does not specify an RSI, this + /// will just be the base RSI of the owning sprite (). + /// + public RSI? LayerGetEffectiveRsi(Entity sprite, Enum key, StateId state) + { + TryGetLayer(sprite, key, out var layer, true); + return layer?.ActualRsi; + } + + #endregion +} From d717172cfe6cc34157361b38430e5e63e62e4c67 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Tue, 14 Jan 2025 13:51:38 +1300 Subject: [PATCH 29/36] Cleanup --- .../Components/Renderable/SpriteComponent.cs | 4 +++- .../EntitySystems/SpriteSystem.Component.cs | 1 - .../EntitySystems/SpriteSystem.Layer.cs | 5 ++--- .../EntitySystems/SpriteSystem.LayerGetters.cs | 5 ----- .../EntitySystems/SpriteSystem.LayerSetters.cs | 1 - .../EntitySystems/SpriteSystem.Specifier.cs | 16 +++++++++++----- .../GameObjects/EntitySystems/SpriteSystem.cs | 5 +++-- Robust.Client/Utility/SpriteSpecifierExt.cs | 8 +++++--- .../Implementations/SpriteSpecifierSerializer.cs | 2 +- 9 files changed, 25 insertions(+), 22 deletions(-) diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index 1d45ddf8b0a..b6f368d4770 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -1906,11 +1906,13 @@ public IRsiStateLike? Icon } } + [Obsolete("Use SpriteSystem.GetPrototypeTextures() instead")] public static IEnumerable GetPrototypeTextures(EntityPrototype prototype, IResourceCache resourceCache) { return GetPrototypeTextures(prototype, resourceCache, out var _); } + [Obsolete("Use SpriteSystem.GetPrototypeTextures() instead")] public static IEnumerable GetPrototypeTextures(EntityPrototype prototype, IResourceCache resourceCache, out bool noRot) { var results = new List(); @@ -1965,7 +1967,7 @@ public static IEnumerable GetPrototypeTextures(Enti return results; } - [Obsolete("Use SpriteSystem")] + [Obsolete("Use SpriteSystem.GetPrototypeIcon() instead")] public static IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache) { var sys = IoCManager.Resolve().GetEntitySystem(); diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Component.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Component.cs index 94e2f5b36d1..7759a356a7b 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Component.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Component.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Robust.Shared.GameObjects; using Robust.Shared.Maths; using Robust.Shared.Utility; diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs index ecdab81df1a..274afc0fd19 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs @@ -3,7 +3,6 @@ using Robust.Client.Graphics; using Robust.Client.ResourceManagement; using Robust.Shared.GameObjects; -using Robust.Shared.Serialization.TypeSerializers.Implementations; using Robust.Shared.Utility; using static Robust.Client.GameObjects.SpriteComponent; @@ -188,7 +187,7 @@ public int AddRsiLayer(Entity sprite, RSI.StateId state, ResPa if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) return -1; - if (!_resourceCache.TryGetResource(SpriteSpecifierSerializer.TextureRoot / path, out var res)) + if (!_resourceCache.TryGetResource(TextureRoot / path, out var res)) Log.Error($"Unable to load RSI '{path}'. Trace:\n{Environment.StackTrace}"); if (path.Extension != "rsi") @@ -199,7 +198,7 @@ public int AddRsiLayer(Entity sprite, RSI.StateId state, ResPa public int AddTextureLayer(Entity sprite, ResPath path, int? index = null) { - if (_resourceCache.TryGetResource(SpriteSpecifierSerializer.TextureRoot / path, out var texture)) + if (_resourceCache.TryGetResource(TextureRoot / path, out var texture)) return AddTextureLayer(sprite, texture?.Texture, index); if (path.Extension == "rsi") diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerGetters.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerGetters.cs index e38152b64af..73881d1c317 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerGetters.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerGetters.cs @@ -1,11 +1,6 @@ using System; -using System.Diagnostics.CodeAnalysis; using Robust.Client.Graphics; -using Robust.Client.ResourceManagement; using Robust.Shared.GameObjects; -using Robust.Shared.Serialization.TypeSerializers.Implementations; -using Robust.Shared.Utility; -using static Robust.Client.GameObjects.SpriteComponent; using static Robust.Client.Graphics.RSI; namespace Robust.Client.GameObjects; diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs index 8c532d59458..90b8e48a29a 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs @@ -7,7 +7,6 @@ using Robust.Shared.Utility; using static Robust.Client.GameObjects.SpriteComponent; using static Robust.Client.Graphics.RSI; -using static Robust.Shared.Serialization.TypeSerializers.Implementations.SpriteSpecifierSerializer; #pragma warning disable CS0618 // Type or member is obsolete diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Specifier.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Specifier.cs index 1e4f27b9006..558982a1ac4 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Specifier.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Specifier.cs @@ -3,15 +3,14 @@ using JetBrains.Annotations; using Robust.Client.Graphics; using Robust.Client.ResourceManagement; -using Robust.Client.Utility; -using Robust.Shared.Graphics; using Robust.Shared.Map; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations; using Robust.Shared.Utility; namespace Robust.Client.GameObjects; +// This partial class contains various public helper methods for extracting textures/icons from sprite specifiers and +// entity prototypes. public sealed partial class SpriteSystem { private readonly Dictionary _cachedPrototypeIcons = new(); @@ -31,7 +30,7 @@ public IRsiStateLike RsiStateLike(SpriteSpecifier specifier) switch (specifier) { case SpriteSpecifier.Texture tex: - return tex.GetTexture(_resourceCache); + return GetTexture(tex); case SpriteSpecifier.Rsi rsi: return GetState(rsi); @@ -111,7 +110,7 @@ public RSI.State GetFallbackState() public RSI.State GetState(SpriteSpecifier.Rsi rsiSpecifier) { if (_resourceCache.TryGetResource( - SpriteSpecifierSerializer.TextureRoot / rsiSpecifier.RsiPath, + TextureRoot / rsiSpecifier.RsiPath, out var theRsi) && theRsi.RSI.TryGetState(rsiSpecifier.RsiState, out var state)) { @@ -122,6 +121,13 @@ public RSI.State GetState(SpriteSpecifier.Rsi rsiSpecifier) return GetFallbackState(); } + public Texture GetTexture(SpriteSpecifier.Texture texSpecifier) + { + return _resourceCache + .GetResource(TextureRoot / texSpecifier.TexturePath) + .Texture; + } + private void OnPrototypesReloaded(PrototypesReloadedEventArgs args) { if (!args.TryGetModified(out var modified)) diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs index cb3913bd508..ceebde5d18d 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs @@ -13,10 +13,9 @@ using Robust.Shared.Graphics.RSI; using Robust.Shared.IoC; using Robust.Shared.Log; -using Robust.Shared.Map; using Robust.Shared.Maths; -using Robust.Shared.Physics; using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations; using Robust.Shared.Timing; using Robust.Shared.Utility; using static Robust.Client.GameObjects.SpriteComponent; @@ -40,6 +39,8 @@ public sealed partial class SpriteSystem : EntitySystem private readonly Queue _inertUpdateQueue = new(); + public static readonly ResPath TextureRoot = SpriteSpecifierSerializer.TextureRoot; + /// /// Entities that require a sprite frame update. /// diff --git a/Robust.Client/Utility/SpriteSpecifierExt.cs b/Robust.Client/Utility/SpriteSpecifierExt.cs index 1a90011a745..5909e9fc929 100644 --- a/Robust.Client/Utility/SpriteSpecifierExt.cs +++ b/Robust.Client/Utility/SpriteSpecifierExt.cs @@ -17,6 +17,7 @@ namespace Robust.Client.Utility /// public static class SpriteSpecifierExt { + [Obsolete("Use SpriteSystem.GetTexture() instead")] public static Texture GetTexture(this SpriteSpecifier.Texture texSpecifier, IResourceCache cache) { return cache @@ -24,7 +25,7 @@ public static Texture GetTexture(this SpriteSpecifier.Texture texSpecifier, IRes .Texture; } - [Obsolete("Use SpriteSystem")] + [Obsolete("Use SpriteSystem.GetState() instead")] public static RSI.State GetState(this SpriteSpecifier.Rsi rsiSpecifier, IResourceCache cache) { if (!cache.TryGetResource(SpriteSpecifierSerializer.TextureRoot / rsiSpecifier.RsiPath, out var theRsi)) @@ -43,18 +44,19 @@ public static RSI.State GetState(this SpriteSpecifier.Rsi rsiSpecifier, IResourc return IoCManager.Resolve().GetEntitySystem().GetFallbackState(); } - [Obsolete("Use SpriteSystem")] + [Obsolete("Use SpriteSystem.Frame0() instead")] public static Texture Frame0(this SpriteSpecifier specifier) { return specifier.RsiStateLike().Default; } + [Obsolete("Use SpriteSystem.RsiStateLike() instead")] public static IDirectionalTextureProvider DirFrame0(this SpriteSpecifier specifier) { return specifier.RsiStateLike(); } - [Obsolete("Use SpriteSystem")] + [Obsolete("Use SpriteSystem.RsiStateLike() instead")] public static IRsiStateLike RsiStateLike(this SpriteSpecifier specifier) { var resC = IoCManager.Resolve(); diff --git a/Robust.Shared/Serialization/TypeSerializers/Implementations/SpriteSpecifierSerializer.cs b/Robust.Shared/Serialization/TypeSerializers/Implementations/SpriteSpecifierSerializer.cs index a9831acba4b..92c1f42ca1e 100644 --- a/Robust.Shared/Serialization/TypeSerializers/Implementations/SpriteSpecifierSerializer.cs +++ b/Robust.Shared/Serialization/TypeSerializers/Implementations/SpriteSpecifierSerializer.cs @@ -25,7 +25,7 @@ public abstract class SpriteSpecifierSerializer : ITypeCopier, ITypeCopier { - // Should probably be in SpriteComponent, but is needed for server to validate paths. + // Should probably be in SpriteSystem, but is needed for server to validate paths. // So I guess it might as well go here? public static readonly ResPath TextureRoot = new("/Textures"); From 4a2b3eeb192769988bbf72ba276ed6fd5f701cd4 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Tue, 14 Jan 2025 13:53:19 +1300 Subject: [PATCH 30/36] Merge helper partial classes --- .../EntitySystems/SpriteSystem.Helpers.cs | 137 +++++++++++++++++ .../EntitySystems/SpriteSystem.Specifier.cs | 143 ------------------ 2 files changed, 137 insertions(+), 143 deletions(-) delete mode 100644 Robust.Client/GameObjects/EntitySystems/SpriteSystem.Specifier.cs diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Helpers.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Helpers.cs index 2cbf9fcbb59..3b05804a56a 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Helpers.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Helpers.cs @@ -1,11 +1,148 @@ +using System; +using System.Collections.Generic; using System.Numerics; +using JetBrains.Annotations; +using Robust.Client.Graphics; +using Robust.Client.ResourceManagement; using Robust.Shared.GameObjects; using Robust.Shared.Map; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; namespace Robust.Client.GameObjects; +// This partial class contains various public helper methods, including methods for extracting textures/icons from +// sprite specifiers and entity prototypes. public sealed partial class SpriteSystem { + private readonly Dictionary _cachedPrototypeIcons = new(); + + public Texture Frame0(EntityPrototype prototype) + { + return GetPrototypeIcon(prototype).Default; + } + + public Texture Frame0(SpriteSpecifier specifier) + { + return RsiStateLike(specifier).Default; + } + + public IRsiStateLike RsiStateLike(SpriteSpecifier specifier) + { + switch (specifier) + { + case SpriteSpecifier.Texture tex: + return GetTexture(tex); + + case SpriteSpecifier.Rsi rsi: + return GetState(rsi); + + case SpriteSpecifier.EntityPrototype prototypeIcon: + return GetPrototypeIcon(prototypeIcon.EntityPrototypeId); + + default: + throw new NotSupportedException(); + } + } + + public Texture GetIcon(IconComponent icon) + { + return GetState(icon.Icon).Frame0; + } + + /// + /// Returns an icon for a given ID, or a fallback in case of an error. + /// This method caches the result based on the prototype identifier. + /// + public IRsiStateLike GetPrototypeIcon(string prototype) + { + // Check if this prototype has been cached before, and if so return the result. + if (_cachedPrototypeIcons.TryGetValue(prototype, out var cachedResult)) + return cachedResult; + + if (!_proto.TryIndex(prototype, out var entityPrototype)) + { + // The specified prototype doesn't exist, return the fallback "error" sprite. + _sawmill.Error("Failed to load PrototypeIcon {0}", prototype); + return GetFallbackState(); + } + + // Generate the icon and cache it in case it's ever needed again. + var result = GetPrototypeIcon(entityPrototype); + _cachedPrototypeIcons[prototype] = result; + + return result; + } + + /// + /// Returns an icon for a given ID, or a fallback in case of an error. + /// This method does NOT cache the result. + /// + public IRsiStateLike GetPrototypeIcon(EntityPrototype prototype) + { + // IconComponent takes precedence. If it has a valid icon, return that. Otherwise, continue as normal. + if (prototype.Components.TryGetValue("Icon", out var compData) + && compData.Component is IconComponent icon) + { + return GetIcon(icon); + } + + // If the prototype doesn't have a SpriteComponent, then there's nothing we can do but return the fallback. + if (!prototype.Components.ContainsKey("Sprite")) + { + return GetFallbackState(); + } + + // Finally, we use spawn a dummy entity to get its icon. + var dummy = Spawn(prototype.ID, MapCoordinates.Nullspace); + var spriteComponent = EnsureComp(dummy); + var result = spriteComponent.Icon ?? GetFallbackState(); + Del(dummy); + + return result; + } + + [Pure] + public RSI.State GetFallbackState() + { + return _resourceCache.GetFallback().RSI["error"]; + } + + [Pure] + public RSI.State GetState(SpriteSpecifier.Rsi rsiSpecifier) + { + if (_resourceCache.TryGetResource( + TextureRoot / rsiSpecifier.RsiPath, + out var theRsi) && + theRsi.RSI.TryGetState(rsiSpecifier.RsiState, out var state)) + { + return state; + } + + _sawmill.Error("Failed to load RSI {0}", rsiSpecifier.RsiPath); + return GetFallbackState(); + } + + public Texture GetTexture(SpriteSpecifier.Texture texSpecifier) + { + return _resourceCache + .GetResource(TextureRoot / texSpecifier.TexturePath) + .Texture; + } + + private void OnPrototypesReloaded(PrototypesReloadedEventArgs args) + { + if (!args.TryGetModified(out var modified)) + return; + + // Remove all changed prototypes from the cache, if they're there. + foreach (var prototype in modified) + { + // Let's be lazy and not regenerate them until something needs them again. + _cachedPrototypeIcons.Remove(prototype); + } + } + /// /// Gets an entity's sprite position in world terms. /// diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Specifier.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Specifier.cs deleted file mode 100644 index 558982a1ac4..00000000000 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Specifier.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System; -using System.Collections.Generic; -using JetBrains.Annotations; -using Robust.Client.Graphics; -using Robust.Client.ResourceManagement; -using Robust.Shared.Map; -using Robust.Shared.Prototypes; -using Robust.Shared.Utility; - -namespace Robust.Client.GameObjects; - -// This partial class contains various public helper methods for extracting textures/icons from sprite specifiers and -// entity prototypes. -public sealed partial class SpriteSystem -{ - private readonly Dictionary _cachedPrototypeIcons = new(); - - public Texture Frame0(EntityPrototype prototype) - { - return GetPrototypeIcon(prototype).Default; - } - - public Texture Frame0(SpriteSpecifier specifier) - { - return RsiStateLike(specifier).Default; - } - - public IRsiStateLike RsiStateLike(SpriteSpecifier specifier) - { - switch (specifier) - { - case SpriteSpecifier.Texture tex: - return GetTexture(tex); - - case SpriteSpecifier.Rsi rsi: - return GetState(rsi); - - case SpriteSpecifier.EntityPrototype prototypeIcon: - return GetPrototypeIcon(prototypeIcon.EntityPrototypeId); - - default: - throw new NotSupportedException(); - } - } - - public Texture GetIcon(IconComponent icon) - { - return GetState(icon.Icon).Frame0; - } - - /// - /// Returns an icon for a given ID, or a fallback in case of an error. - /// This method caches the result based on the prototype identifier. - /// - public IRsiStateLike GetPrototypeIcon(string prototype) - { - // Check if this prototype has been cached before, and if so return the result. - if (_cachedPrototypeIcons.TryGetValue(prototype, out var cachedResult)) - return cachedResult; - - if (!_proto.TryIndex(prototype, out var entityPrototype)) - { - // The specified prototype doesn't exist, return the fallback "error" sprite. - _sawmill.Error("Failed to load PrototypeIcon {0}", prototype); - return GetFallbackState(); - } - - // Generate the icon and cache it in case it's ever needed again. - var result = GetPrototypeIcon(entityPrototype); - _cachedPrototypeIcons[prototype] = result; - - return result; - } - - /// - /// Returns an icon for a given ID, or a fallback in case of an error. - /// This method does NOT cache the result. - /// - public IRsiStateLike GetPrototypeIcon(EntityPrototype prototype) - { - // IconComponent takes precedence. If it has a valid icon, return that. Otherwise, continue as normal. - if (prototype.Components.TryGetValue("Icon", out var compData) - && compData.Component is IconComponent icon) - { - return GetIcon(icon); - } - - // If the prototype doesn't have a SpriteComponent, then there's nothing we can do but return the fallback. - if (!prototype.Components.ContainsKey("Sprite")) - { - return GetFallbackState(); - } - - // Finally, we use spawn a dummy entity to get its icon. - var dummy = Spawn(prototype.ID, MapCoordinates.Nullspace); - var spriteComponent = EnsureComp(dummy); - var result = spriteComponent.Icon ?? GetFallbackState(); - Del(dummy); - - return result; - } - - [Pure] - public RSI.State GetFallbackState() - { - return _resourceCache.GetFallback().RSI["error"]; - } - - [Pure] - public RSI.State GetState(SpriteSpecifier.Rsi rsiSpecifier) - { - if (_resourceCache.TryGetResource( - TextureRoot / rsiSpecifier.RsiPath, - out var theRsi) && - theRsi.RSI.TryGetState(rsiSpecifier.RsiState, out var state)) - { - return state; - } - - _sawmill.Error("Failed to load RSI {0}", rsiSpecifier.RsiPath); - return GetFallbackState(); - } - - public Texture GetTexture(SpriteSpecifier.Texture texSpecifier) - { - return _resourceCache - .GetResource(TextureRoot / texSpecifier.TexturePath) - .Texture; - } - - private void OnPrototypesReloaded(PrototypesReloadedEventArgs args) - { - if (!args.TryGetModified(out var modified)) - return; - - // Remove all changed prototypes from the cache, if they're there. - foreach (var prototype in modified) - { - // Let's be lazy and not regenerate them until something needs them again. - _cachedPrototypeIcons.Remove(prototype); - } - } -} From 517b262bd1da047f8a3b9f9d1d67e12f7e2db50b Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Tue, 14 Jan 2025 14:11:47 +1300 Subject: [PATCH 31/36] partial rendering --- .../Components/Renderable/SpriteComponent.cs | 67 +------------ .../EntitySystems/SpriteSystem.Render.cs | 97 +++++++++++++++++++ .../GameObjects/EntitySystems/SpriteSystem.cs | 8 -- Robust.Client/Graphics/Clyde/Clyde.HLR.cs | 2 +- .../Components/Renderable/SpriteLayerData.cs | 4 + 5 files changed, 105 insertions(+), 73 deletions(-) create mode 100644 Robust.Client/GameObjects/EntitySystems/SpriteSystem.Render.cs diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index b6f368d4770..ed0b1317e8a 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -920,8 +920,6 @@ public RSI.StateId LayerGetState(int layer) return this[layerKey].ActualRsi; } - #endregion - public void LayerSetShader(int layer, ShaderInstance? shader, string? prototype = null) { if (!TryGetLayer(layer, out var theLayer, true)) @@ -964,72 +962,13 @@ public void LayerSetShader(object layerKey, string shaderName) } // Lobby SpriteView rendering path + [Obsolete("Use SpriteSystem.Render() instead.")] public void Render(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Direction? overrideDirection = null, Vector2 position = default) { - RenderInternal(drawingHandle, eyeRotation, worldRotation, position, overrideDirection); + Sys.Render((Owner, this), drawingHandle, eyeRotation, worldRotation, position, overrideDirection); } - internal void RenderInternal(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Vector2 worldPosition, Direction? overrideDirection) - { - var angle = worldRotation + eyeRotation; // angle on-screen. Used to decide the direction of 4/8 directional RSIs - angle = angle.Reduced().FlipPositive(); // Reduce the angles to fix math shenanigans - - var cardinal = Angle.Zero; - - // If we have a 1-directional sprite then snap it to try and always face it south if applicable. - if (!NoRotation && SnapCardinals) - { - cardinal = angle.GetCardinalDir().ToAngle(); - } - - // worldRotation + eyeRotation should be the angle of the entity on-screen. If no-rot is enabled this is just set to zero. - // However, at some point later the eye-matrix is applied separately, so we subtract -eye rotation for now: - var entityMatrix = Matrix3Helpers.CreateTransform(worldPosition, NoRotation ? -eyeRotation : worldRotation - cardinal); - - var transformSprite = Matrix3x2.Multiply(LocalMatrix, entityMatrix); - - if (GranularLayersRendering) - { - //Default rendering - entityMatrix = Matrix3Helpers.CreateTransform(worldPosition, worldRotation); - var transformDefault = Matrix3x2.Multiply(LocalMatrix, entityMatrix); - //Snap to cardinals - entityMatrix = Matrix3Helpers.CreateTransform(worldPosition, worldRotation - angle.GetCardinalDir().ToAngle()); - var transformSnap = Matrix3x2.Multiply(LocalMatrix, entityMatrix); - //No rotation - entityMatrix = Matrix3Helpers.CreateTransform(worldPosition, -eyeRotation); - var transformNoRot = Matrix3x2.Multiply(LocalMatrix, entityMatrix); - - foreach (var layer in Layers) { - switch (layer.RenderingStrategy) - { - case LayerRenderingStrategy.NoRotation: - layer.Render(drawingHandle, ref transformNoRot, angle, overrideDirection); - break; - case LayerRenderingStrategy.SnapToCardinals: - layer.Render(drawingHandle, ref transformSnap, angle, overrideDirection); - break; - case LayerRenderingStrategy.Default: - layer.Render(drawingHandle, ref transformDefault, angle, overrideDirection); - break; - case LayerRenderingStrategy.UseSpriteStrategy: - layer.Render(drawingHandle, ref transformSprite, angle, overrideDirection); - break; - default: - Logger.Error($"Tried to render a layer with unknown rendering stragegy: {layer.RenderingStrategy}"); - break; - } - } - } - - else - { - foreach (var layer in Layers) - { - layer.Render(drawingHandle, ref transformSprite, angle, overrideDirection); - } - } - } + #endregion public int GetLayerDirectionCount(ISpriteLayer layer) { diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Render.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Render.cs new file mode 100644 index 00000000000..9ab36b8e8c4 --- /dev/null +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Render.cs @@ -0,0 +1,97 @@ +using System.Numerics; +using Robust.Client.Graphics; +using Robust.Shared.GameObjects; +using Robust.Shared.Maths; + +namespace Robust.Client.GameObjects; + +// This partial class contains code related to actually rendering sprites. +public sealed partial class SpriteSystem +{ + public void Render( + Entity sprite, + DrawingHandleWorld drawingHandle, + Angle eyeRotation, + Angle worldRotation, + Vector2 worldPosition) + { + Render(sprite, + drawingHandle, + eyeRotation, + worldRotation, + worldPosition, + sprite.Comp.EnableDirectionOverride ? sprite.Comp.DirectionOverride : null); + } + + public void Render( + Entity sprite, + DrawingHandleWorld drawingHandle, + Angle eyeRotation, + Angle worldRotation, + Vector2 worldPosition, + Direction? overrideDirection) + { + if (!sprite.Comp.IsInert) + _queuedFrameUpdate.Add(sprite); + + var angle = worldRotation + eyeRotation; // angle on-screen. Used to decide the direction of 4/8 directional RSIs + angle = angle.Reduced().FlipPositive(); // Reduce the angles to fix math shenanigans + + var cardinal = Angle.Zero; + + // If we have a 1-directional sprite then snap it to try and always face it south if applicable. + if (sprite.Comp is {NoRotation: false, SnapCardinals: true}) + cardinal = angle.GetCardinalDir().ToAngle(); + + // worldRotation + eyeRotation should be the angle of the entity on-screen. If no-rot is enabled this is just set to zero. + // However, at some point later the eye-matrix is applied separately, so we subtract -eye rotation for now: + var entityMatrix = Matrix3Helpers.CreateTransform(worldPosition, sprite.Comp.NoRotation ? -eyeRotation : worldRotation - cardinal); + var spriteMatrix = Matrix3x2.Multiply(sprite.Comp.LocalMatrix, entityMatrix); + + // Fast path for when all sprites use the same transform matrix + if (!sprite.Comp.GranularLayersRendering) + { + foreach (var layer in sprite.Comp.Layers) + { + layer.Render(drawingHandle, ref spriteMatrix, angle, overrideDirection); + } + return; + } + + // TODO sprite optimize angle.GetCardinalDir().ToAngle() + + //Default rendering (NoRotation = false) + entityMatrix = Matrix3Helpers.CreateTransform(worldPosition, worldRotation); + var transformDefault = Matrix3x2.Multiply(sprite.Comp.LocalMatrix, entityMatrix); + + //Snap to cardinals + entityMatrix = Matrix3Helpers.CreateTransform(worldPosition, worldRotation - angle.GetCardinalDir().ToAngle()); + var transformSnap = Matrix3x2.Multiply(sprite.Comp.LocalMatrix, entityMatrix); + + //No rotation + entityMatrix = Matrix3Helpers.CreateTransform(worldPosition, -eyeRotation); + var transformNoRot = Matrix3x2.Multiply(sprite.Comp.LocalMatrix, entityMatrix); + + foreach (var layer in sprite.Comp.Layers) + { + switch (layer.RenderingStrategy) + { + case LayerRenderingStrategy.UseSpriteStrategy: + layer.Render(drawingHandle, ref spriteMatrix, angle, overrideDirection); + break; + case LayerRenderingStrategy.Default: + layer.Render(drawingHandle, ref transformDefault, angle, overrideDirection); + break; + case LayerRenderingStrategy.NoRotation: + layer.Render(drawingHandle, ref transformNoRot, angle, overrideDirection); + break; + case LayerRenderingStrategy.SnapToCardinals: + layer.Render(drawingHandle, ref transformSnap, angle, overrideDirection); + break; + default: + Log.Error($"Tried to render a layer with unknown rendering stragegy: {layer.RenderingStrategy}"); + break; + } + } + } +} diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs index ceebde5d18d..d3e0d9ae9fb 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs @@ -49,14 +49,6 @@ public sealed partial class SpriteSystem : EntitySystem private ISawmill _sawmill = default!; private EntityQuery _query; - internal void Render(EntityUid uid, SpriteComponent sprite, DrawingHandleWorld drawingHandle, Angle eyeRotation, in Angle worldRotation, in Vector2 worldPosition) - { - if (!sprite.IsInert) - _queuedFrameUpdate.Add(uid); - - sprite.RenderInternal(drawingHandle, eyeRotation, worldRotation, worldPosition, sprite.EnableDirectionOverride ? sprite.DirectionOverride : null); - } - public override void Initialize() { base.Initialize(); diff --git a/Robust.Client/Graphics/Clyde/Clyde.HLR.cs b/Robust.Client/Graphics/Clyde/Clyde.HLR.cs index 933327331a9..42d1612d968 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.HLR.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.HLR.cs @@ -355,7 +355,7 @@ private void DrawEntities(Viewport viewport, Box2Rotated worldBounds, Box2 world } } - spriteSystem.Render(entry.Uid, entry.Sprite, _renderHandle.DrawingHandleWorld, eye.Rotation, in entry.WorldRot, in entry.WorldPos); + spriteSystem.Render(new(entry.Uid, entry.Sprite), _renderHandle.DrawingHandleWorld, eye.Rotation, entry.WorldRot, entry.WorldPos); if (entry.Sprite.PostShader != null && entityPostRenderTarget != null) { diff --git a/Robust.Shared/GameObjects/Components/Renderable/SpriteLayerData.cs b/Robust.Shared/GameObjects/Components/Renderable/SpriteLayerData.cs index 531a28a19c3..00e3ff38b33 100644 --- a/Robust.Shared/GameObjects/Components/Renderable/SpriteLayerData.cs +++ b/Robust.Shared/GameObjects/Components/Renderable/SpriteLayerData.cs @@ -80,4 +80,8 @@ public enum LayerRenderingStrategy SnapToCardinals, NoRotation, UseSpriteStrategy + // TODO SPRITE + // Refactor this make the sprites strategy the actual default. + // That way layers have to opt in to having a custom strategy, instead of opt out. + // Also rename default to make it clear that its not actually the default, instead I guess its "WithRotation"? } From 9681e08e3bb4202a3315c819e3cc1d2cac338a0c Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Tue, 14 Jan 2025 14:18:54 +1300 Subject: [PATCH 32/36] GetLayerDirectionCount --- .../Components/Renderable/SpriteComponent.cs | 5 +- .../SpriteSystem.LayerGetters.cs | 66 +++++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index ed0b1317e8a..d252c4be266 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -968,8 +968,7 @@ public void Render(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle wo Sys.Render((Owner, this), drawingHandle, eyeRotation, worldRotation, position, overrideDirection); } - #endregion - + [Obsolete("Use SpriteSystem.LayerGetDirectionCount() instead.")] public int GetLayerDirectionCount(ISpriteLayer layer) { if (!layer.RsiState.IsValid) @@ -991,6 +990,8 @@ public int GetLayerDirectionCount(ISpriteLayer layer) }; } + #endregion + public string GetDebugString() { var builder = new StringBuilder(); diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerGetters.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerGetters.cs index 73881d1c317..bdc0eb5e8a5 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerGetters.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerGetters.cs @@ -1,6 +1,8 @@ using System; using Robust.Client.Graphics; using Robust.Shared.GameObjects; +using Robust.Shared.Graphics.RSI; +using static Robust.Client.GameObjects.SpriteComponent; using static Robust.Client.Graphics.RSI; namespace Robust.Client.GameObjects; @@ -81,4 +83,68 @@ public StateId LayerGetRsiState(Entity sprite, Enum key, State } #endregion + + #region Directions + + public RsiDirectionType LayerGetDirections(Entity sprite, int index) + { + return TryGetLayer(sprite, index, out var layer, true) + ? LayerGetDirections(layer) + : RsiDirectionType.Dir1; + } + + + public RsiDirectionType LayerGetDirections(Entity sprite, Enum key) + { + return TryGetLayer(sprite, key, out var layer, true) + ? LayerGetDirections(layer) + : RsiDirectionType.Dir1; + } + + public RsiDirectionType LayerGetDirections(Entity sprite, string key) + { + return TryGetLayer(sprite, key, out var layer, true) + ? LayerGetDirections(layer) + : RsiDirectionType.Dir1; + } + + public RsiDirectionType LayerGetDirections(Layer layer) + { + if (!layer.StateId.IsValid) + return RsiDirectionType.Dir1; + + // Pull texture from RSI state instead. + if (layer.ActualRsi is not {} rsi || !rsi.TryGetState(layer.StateId, out var state)) + return RsiDirectionType.Dir1; + + return state.RsiDirections; + } + + public int LayerGetDirectionCount(Entity sprite, int index) + { + return TryGetLayer(sprite, index, out var layer, true) ? LayerGetDirectionCount(layer) : 1; + } + + public int LayerGetDirectionCount(Entity sprite, Enum key) + { + return TryGetLayer(sprite, key, out var layer, true) ? LayerGetDirectionCount(layer) : 1; + } + + public int LayerGetDirectionCount(Entity sprite, string key) + { + return TryGetLayer(sprite, key, out var layer, true) ? LayerGetDirectionCount(layer) : 1; + } + + public int LayerGetDirectionCount(Layer layer) + { + return LayerGetDirections(layer) switch + { + RsiDirectionType.Dir1 => 1, + RsiDirectionType.Dir4 => 4, + RsiDirectionType.Dir8 => 8, + _ => throw new ArgumentOutOfRangeException() + }; + } + + #endregion } From 09aa9850a2754c5deb8f73896400e92cede5363a Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Tue, 14 Jan 2025 15:38:56 +1300 Subject: [PATCH 33/36] Cache local bounds --- .../ComponentTrees/SpriteTreeSystem.cs | 10 +- .../Components/Renderable/SpriteComponent.cs | 159 ++++++------------ .../EntitySystems/SpriteSystem.Bounds.cs | 149 ++++++++++++++++ .../EntitySystems/SpriteSystem.Component.cs | 19 +-- .../EntitySystems/SpriteSystem.Layer.cs | 7 +- .../SpriteSystem.LayerSetters.cs | 22 ++- .../EntitySystems/SpriteSystem.Setters.cs | 21 ++- 7 files changed, 249 insertions(+), 138 deletions(-) create mode 100644 Robust.Client/GameObjects/EntitySystems/SpriteSystem.Bounds.cs diff --git a/Robust.Client/ComponentTrees/SpriteTreeSystem.cs b/Robust.Client/ComponentTrees/SpriteTreeSystem.cs index c859ebc501c..eafbb23f545 100644 --- a/Robust.Client/ComponentTrees/SpriteTreeSystem.cs +++ b/Robust.Client/ComponentTrees/SpriteTreeSystem.cs @@ -2,6 +2,7 @@ using Robust.Client.GameObjects; using Robust.Shared.ComponentTrees; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; using Robust.Shared.Maths; using Robust.Shared.Physics; @@ -9,6 +10,8 @@ namespace Robust.Client.ComponentTrees; public sealed class SpriteTreeSystem : ComponentTreeSystem { + [Dependency] private readonly SpriteSystem _sprite = default!; + #region Component Tree Overrides protected override bool DoFrameUpdate => true; protected override bool DoTickUpdate => false; @@ -16,6 +19,11 @@ public sealed class SpriteTreeSystem : ComponentTreeSystem 1024; protected override Box2 ExtractAabb(in ComponentTreeEntry entry, Vector2 pos, Angle rot) - => entry.Component.CalculateRotatedBoundingBox(pos, rot, default).CalcBoundingBox(); + { + // TODO SPRITE optimize this + // Because the just take the BB of the rotated BB, I'mt pretty sure we do a lot of unnecessary maths. + return _sprite.CalculateBounds((entry.Uid, entry.Component), pos, rot, default).CalcBoundingBox(); + } + #endregion } diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index d252c4be266..5a7fc5d13ef 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -55,7 +55,7 @@ public sealed partial class SpriteComponent : Component, IComponentDebug, ISeria /// Whether the layers have independant drawing strategies, e.g some may snap to cardinals while others won't. /// The sprite should still set its global rendering method (e.g NoRot or SnapCardinals), this only gives finer control over how layers are rendered internally. /// - [DataField] + [DataField] // TODO Sprite access restrict. public bool GranularLayersRendering = false; [DataField] @@ -190,13 +190,15 @@ public bool ContainerOccluded internal bool _containerOccluded; - internal Box2 _bounds; - /// - /// The bounds of the sprite. This does factor in the sprite's but not the - /// and + /// Whether or not the sprite's local bounding box is dirty and need to be rebuilt. /// - public Box2 Bounds => _bounds; + internal bool BoundsDirty = true; + + internal Box2 _bounds; + + [Obsolete("Use SpriteSystem.GetLocalBounds() instead.")] + public Box2 Bounds => Sys.GetLocalBounds((Owner, this)); [ViewVariables(VVAccess.ReadWrite)] internal bool _inertUpdateQueued; @@ -289,14 +291,7 @@ void ISerializationHooks.AfterDeserialization() } - _bounds = new Box2(); - foreach (var layer in Layers) - { - if (layer is {Visible: true, Blank: false}) - _bounds = _bounds.Union(layer.CalculateBoundingBox()); - } - - _bounds = _bounds.Scale(Scale); + BoundsDirty = true; LocalMatrix = Matrix3Helpers.CreateTransform(in offset, in rotation, in scale); } @@ -620,9 +615,11 @@ internal void LayerSetData(Layer layer, PrototypeLayerData layerDatum) layer.CopyToShaderParameters = null; } + BoundsDirty = true; + layer.BoundsDirty = true; // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract if (Owner != EntityUid.Invalid) - Sys?.RebuildBounds((Owner, this)); + TreeSys?.QueueTreeUpdate((Owner, this)); object ParseKey(string keyString) { @@ -990,16 +987,11 @@ public int GetLayerDirectionCount(ISpriteLayer layer) }; } - #endregion - public string GetDebugString() { var builder = new StringBuilder(); - builder.AppendFormat( - "vis/depth/scl/rot/ofs/col/norot/override/dir: {0}/{1}/{2}/{3}/{4}/{5}/{6}/{8}/{7}\n", - Visible, DrawDepth, Scale, Rotation, Offset, - Color, NoRotation, entities.GetComponent(Owner).WorldRotation.ToRsiDirection(RsiDirectionType.Dir8), - DirectionOverride + builder.Append( + $"vis/depth/scl/rot/ofs/col/norot/override: {Visible}/{DrawDepth}/{Scale}/{Rotation}/{Offset}/{Color}/{NoRotation}/{DirectionOverride}/\n" ); foreach (var layer in Layers) @@ -1008,54 +1000,32 @@ public string GetDebugString() "shad/tex/rsi/state/ant/anf/scl/rot/vis/col/dofs/renderstrat: {0}/{1}/{2}/{3}/{4}/{5}/{6}/{7}/{8}/{9}/{10}/{11}\n", // These are references and don't include useful data for knowing where they came from, sadly. // "is one set" is better than nothing at least. - layer.Shader != null, layer.Texture != null, layer.RSI != null, + layer.Shader != null, + layer.Texture != null, + layer.RSI != null, layer.State, - layer.AnimationTimeLeft, layer.AnimationFrame, layer.Scale, layer.Rotation, layer.Visible, - layer.Color, layer.DirOffset, layer.RenderingStrategy + layer.AnimationTimeLeft, + layer.AnimationFrame, + layer.Scale, + layer.Rotation, + layer.Visible, + layer.Color, + layer.DirOffset, + layer.RenderingStrategy ); } return builder.ToString(); } - /// + [Obsolete("Use SpriteSystem.GetBoundingBox() instead.")] public Box2Rotated CalculateRotatedBoundingBox(Vector2 worldPosition, Angle worldRotation, Angle eyeRot) { - // fast check for empty sprites - if (!Visible || Layers.Count == 0) - { - return new Box2Rotated(new Box2(worldPosition, worldPosition), Angle.Zero, worldPosition); - } - - // We need to modify world rotation so that it lies between 0 and 2pi. - // This matters for 4 or 8 directional sprites deciding which quadrant (octant?) they lie in. - // the 0->2pi convention is set by the sprite-rendering code that selects the layers. - // See RenderInternal(). - - worldRotation = worldRotation.Reduced(); - if (worldRotation.Theta < 0) - worldRotation = new Angle(worldRotation.Theta + Math.Tau); - - // Next, what we do is take the box2 and apply the sprite's transform, and then the entity's transform. We - // could do this via Matrix3.TransformBox, but that only yields bounding boxes. So instead we manually - // transform our box by the combination of these matrices: - - Angle finalRotation = NoRotation - ? Rotation - eyeRot - : Rotation + worldRotation; - - // slightly faster path if offset == 0 (true for 99.9% of sprites) - if (Offset == Vector2.Zero) - return new Box2Rotated(Bounds.Translated(worldPosition), finalRotation, worldPosition); - - var adjustedOffset = NoRotation - ? (-eyeRot).RotateVec(Offset) - : worldRotation.RotateVec(Offset); - - Vector2 position = adjustedOffset + worldPosition; - return new Box2Rotated(Bounds.Translated(position), finalRotation, position); + return Sys.CalculateBounds((Owner, this), worldPosition, worldRotation, eyeRot); } + #endregion + /// /// Enum to "offset" a cardinal direction. /// @@ -1172,7 +1142,9 @@ public Vector2 Scale _scale = value; UpdateLocalMatrix(); - _parent.Sys.RebuildBounds((_parent.Owner, _parent)); + BoundsDirty = true; + Owner.Comp.BoundsDirty = true; + Owner.Comp.TreeSys.QueueTreeUpdate(Owner); } } internal Vector2 _scale = Vector2.One; @@ -1187,7 +1159,9 @@ public Angle Rotation _rotation = value; UpdateLocalMatrix(); - _parent.Sys.RebuildBounds((_parent.Owner, _parent)); + BoundsDirty = true; + Owner.Comp.BoundsDirty = true; + Owner.Comp.TreeSys.QueueTreeUpdate(Owner); } } internal Angle _rotation = Angle.Zero; @@ -1205,10 +1179,10 @@ public bool Visible // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract if (_parent.Owner != EntityUid.Invalid) - _parent.Sys?.QueueUpdateIsInert((_parent.Owner, _parent)); + Owner.Comp.Sys?.QueueUpdateIsInert(Owner); if (_parent.Owner != EntityUid.Invalid) - _parent.Sys?.RebuildBounds((_parent.Owner, _parent)); + Owner.Comp.TreeSys.QueueTreeUpdate(Owner); } } @@ -1242,11 +1216,14 @@ public Vector2 Offset { if (_offset.EqualsApprox(value)) return; + BoundsDirty = true; + Owner.Comp.BoundsDirty = true; + _offset = value; UpdateLocalMatrix(); // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract if (_parent.Owner != EntityUid.Invalid) - _parent.Sys?.RebuildBounds((_parent.Owner, _parent)); + Owner.Comp.TreeSys.QueueTreeUpdate(Owner); } } @@ -1262,7 +1239,7 @@ public Vector2 Offset /// Whether the current layer have a specific rendering method (e.g no rotation or snap to cardinal) /// The sprite GranularLayersRendering var must be set to true for this to have any effect. /// - [ViewVariables] + [ViewVariables] // TODO access restrict public LayerRenderingStrategy RenderingStrategy = LayerRenderingStrategy.UseSpriteStrategy; [ViewVariables(VVAccess.ReadWrite)] @@ -1463,6 +1440,9 @@ public void SetRsi(RSI? rsi) } } + BoundsDirty = true; + Owner.Comp.BoundsDirty = true; + _parent.TreeSys.QueueTreeUpdate((_parent.Owner, _parent)); _parent.Sys.QueueUpdateIsInert((_parent.Owner, _parent)); } @@ -1504,6 +1484,9 @@ public void SetTexture(Texture? texture) State = default; Texture = texture; + BoundsDirty = true; + Owner.Comp.BoundsDirty = true; + _parent.TreeSys.QueueTreeUpdate((_parent.Owner, _parent)); _parent.Sys.QueueUpdateIsInert((_parent.Owner, _parent)); } @@ -1527,46 +1510,14 @@ public Vector2i PixelSize } } - /// - public Box2 CalculateBoundingBox() - { - var textureSize = (Vector2) PixelSize / EyeManager.PixelsPerMeter; - var longestSide = MathF.Max(textureSize.X, textureSize.Y); - var longestRotatedSide = Math.Max(longestSide, (textureSize.X + textureSize.Y) / MathF.Sqrt(2)); - - Vector2 size; - - // If this layer has any form of arbitrary rotation, return a bounding box big enough to cover - // any possible rotation. - if (_rotation != 0) - { - size = new Vector2(longestRotatedSide, longestRotatedSide); - } - else if (_parent.SnapCardinals && (!_parent.GranularLayersRendering || RenderingStrategy == LayerRenderingStrategy.UseSpriteStrategy) - || _parent.GranularLayersRendering && RenderingStrategy == LayerRenderingStrategy.SnapToCardinals) - { - DebugTools.Assert(_actualState == null || _actualState.RsiDirections == RsiDirectionType.Dir1); - size = new Vector2(longestSide, longestSide); - } - else - { - // Build the bounding box based on how many directions the sprite has - size = (_actualState?.RsiDirections) switch - { - // If we have four cardinal directions, take the longest side of our texture and square it, then turn that into our bounding box. - // This accounts for all possible rotations. - RsiDirectionType.Dir4 => new Vector2(longestSide, longestSide), - - // If we have eight directions, find the maximum length of the texture (accounting for rotation), then square it to make - RsiDirectionType.Dir8 => new Vector2(longestRotatedSide, longestRotatedSide), - - // If we have only one direction or an invalid RSI state, create a simple bounding box with the size of the texture. - _ => textureSize - }; - } + /// + /// Whether or not the layers's local bounding box is dirty and need to be rebuilt. + /// + internal bool BoundsDirty = true; + internal Box2 Bounds; - return Box2.CenteredAround(Offset, size * _scale); - } + [Obsolete("Use SpriteSystem.GetLocalBounds()")] + public Box2 CalculateBoundingBox() => Owner.Comp.Sys.GetLocalBounds(this); /// /// Update Cached RSI state. State is cached to avoid calling this every time an entity gets drawn. diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Bounds.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Bounds.cs new file mode 100644 index 00000000000..4ddd26dbac8 --- /dev/null +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Bounds.cs @@ -0,0 +1,149 @@ +using System; +using System.Linq; +using System.Numerics; +using Robust.Client.Graphics; +using Robust.Shared.GameObjects; +using Robust.Shared.Graphics.RSI; +using Robust.Shared.Maths; +using Robust.Shared.Utility; +using static Robust.Client.GameObjects.SpriteComponent; + +namespace Robust.Client.GameObjects; + +// This partial class contains code related to updating a sprites bounding boxes and its position in the sprite tree. +public sealed partial class SpriteSystem +{ + /// + /// Get a sprite's local bounding box. The returned bounds do factor in the sprite's scale but not the rotation or + /// offset. + /// + public Box2 GetLocalBounds(Entity sprite) + { + if (!sprite.Comp.BoundsDirty) + { + DebugTools.Assert(sprite.Comp.Layers.All(x => !x.BoundsDirty || x.Blank || !x.Visible)); + return sprite.Comp._bounds; + } + + var bounds = new Box2(); + foreach (var layer in sprite.Comp.Layers) + { + if (layer is {Visible: true, Blank: false}) + bounds = bounds.Union(GetLocalBounds(layer)); + } + + sprite.Comp._bounds = bounds; + sprite.Comp.BoundsDirty = false; + return sprite.Comp._bounds; + } + + /// + /// Get a layer's local bounding box relative to its owning sprite. Unlike the sprite variant of this method, this + /// does account for the layer's rotation and offset. + /// + public Box2 GetLocalBounds(Layer layer) + { + if (!layer.BoundsDirty) + return layer.Bounds; + + layer.Bounds = CalculateLocalBounds(layer); + layer.BoundsDirty = false; + return layer.Bounds; + } + + internal Box2 CalculateLocalBounds(Layer layer) + { + if (layer.Blank) + return Box2.Empty; + + var textureSize = (Vector2) layer.PixelSize / EyeManager.PixelsPerMeter; + var longestSide = MathF.Max(textureSize.X, textureSize.Y); + var longestRotatedSide = Math.Max(longestSide, (textureSize.X + textureSize.Y) / MathF.Sqrt(2)); + + Vector2 size; + var sprite = layer.Owner.Comp; + + // If this layer has any form of arbitrary rotation, return a bounding box big enough to cover + // any possible rotation. + if (layer._rotation != 0) + { + size = new Vector2(longestRotatedSide, longestRotatedSide); + return Box2.CenteredAround(layer.Offset, size * layer._scale); + } + + var snapToCardinals = sprite.SnapCardinals; + if (sprite.GranularLayersRendering && layer.RenderingStrategy != LayerRenderingStrategy.UseSpriteStrategy) + { + snapToCardinals = layer.RenderingStrategy == LayerRenderingStrategy.SnapToCardinals; + } + + if (snapToCardinals) + { + // Snapping to cardinals only makes sense for 1-directional layers/sprites + DebugTools.Assert(layer._actualState == null || layer._actualState.RsiDirections == RsiDirectionType.Dir1); + + // We won't know the actual direction it snaps to, so we ahve to assume the box is given by the longest side. + size = new Vector2(longestSide, longestSide); + return Box2.CenteredAround(layer.Offset, size * layer._scale); + } + + // Build the bounding box based on how many directions the sprite has + size = (layer._actualState?.RsiDirections) switch + { + RsiDirectionType.Dir4 => new Vector2(longestSide, longestSide), + RsiDirectionType.Dir8 => new Vector2(longestRotatedSide, longestRotatedSide), + _ => textureSize + }; + + return Box2.CenteredAround(layer.Offset, size * layer._scale); + } + + /// + /// Gets a sprite's bounding box in world coordinates. + /// + public Box2Rotated CalculateBounds(Entity sprite, Vector2 worldPos, Angle worldRot, Angle eyeRot) + { + // fast check for invisible sprites + if (!sprite.Comp.Visible || sprite.Comp.Layers.Count == 0) + return new Box2Rotated(new Box2(worldPos, worldPos), Angle.Zero, worldPos); + + // We need to modify world rotation so that it lies between 0 and 2pi. + // This matters for 4 or 8 directional sprites deciding which quadrant (octant?) they lie in. + // the 0->2pi convention is set by the sprite-rendering code that selects the layers. + // See RenderInternal(). + + worldRot = worldRot.Reduced(); + if (worldRot.Theta < 0) + worldRot = new Angle(worldRot.Theta + Math.Tau); + + // Next, what we do is take the box2 and apply the sprite's transform, and then the entity's transform. We + // could do this via Matrix3.TransformBox, but that only yields bounding boxes. So instead we manually + // transform our box by the combination of these matrices: + + var finalRotation = sprite.Comp.NoRotation + ? sprite.Comp.Rotation - eyeRot + : sprite.Comp.Rotation + worldRot; + + var bounds = GetLocalBounds(sprite); + + // slightly faster path if offset == 0 (true for 99.9% of sprites) + if (sprite.Comp.Offset == Vector2.Zero) + return new Box2Rotated(bounds.Translated(worldPos), finalRotation, worldPos); + + var adjustedOffset = sprite.Comp.NoRotation + ? (-eyeRot).RotateVec(sprite.Comp.Offset) + : worldRot.RotateVec(sprite.Comp.Offset); + + var position = adjustedOffset + worldPos; + return new Box2Rotated(bounds.Translated(position), finalRotation, position); + } + + private void DirtyBounds(Entity sprite) + { + sprite.Comp.BoundsDirty = true; + foreach (var layer in sprite.Comp.Layers) + { + layer.BoundsDirty = true; + } + } +} diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Component.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Component.cs index 7759a356a7b..8d8f43b42d6 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Component.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Component.cs @@ -89,27 +89,10 @@ in target target.Comp.RenderOrder = source.Comp.RenderOrder; target.Comp.GranularLayersRendering = source.Comp.GranularLayersRendering; + DirtyBounds(target!); _tree.QueueTreeUpdate(target!); } - public void RebuildBounds(Entity sprite) - { - // TODO SPRITE - // Maybe the bounds calculation should be deferred? - // The tree update is already deferred anyways. - // And layer.CalculateBoundingBox() is relatively expensive for sprites like players - - var bounds = new Box2(); - foreach (var layer in sprite.Comp.Layers) - { - if (layer is {Visible: true, Blank: false}) - bounds = bounds.Union(layer.CalculateBoundingBox()); - } - - sprite.Comp._bounds = bounds.Scale(sprite.Comp.Scale); - _tree.QueueTreeUpdate(sprite); - } - /// /// Adds a sprite to a queue that will update next frame. /// diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs index 274afc0fd19..449151fc406 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Layer.cs @@ -91,7 +91,8 @@ public bool RemoveLayer( } #endif - RebuildBounds(sprite!); + sprite.Comp.BoundsDirty = true; + _tree.QueueTreeUpdate(sprite!); QueueUpdateIsInert(sprite!); return true; } @@ -145,7 +146,9 @@ public int AddLayer(Entity sprite, Layer layer, int? index = n if (!layer.Blank) { - RebuildBounds(sprite!); + layer.BoundsDirty = true; + sprite.Comp.BoundsDirty = true; + _tree.QueueTreeUpdate(sprite!); QueueUpdateIsInert(sprite!); } return layer.Index; diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs index 90b8e48a29a..c07cddf4320 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs @@ -195,8 +195,10 @@ public void LayerSetRsiState(Layer layer, StateId state, bool refresh = false) layer.AnimationTime = 0; layer.AnimationTimeLeft = layer._actualState?.GetDelay(0) ?? 0f; - RebuildBounds(layer.Owner); + _tree.QueueTreeUpdate(layer.Owner); QueueUpdateIsInert(layer.Owner); + layer.BoundsDirty = true; + layer.Owner.Comp.BoundsDirty = true; } #endregion @@ -289,7 +291,9 @@ public void LayerSetScale(Layer layer, Vector2 value) layer._scale = value; layer.UpdateLocalMatrix(); - RebuildBounds(layer.Owner); + _tree.QueueTreeUpdate(layer.Owner); + layer.BoundsDirty = true; + layer.Owner.Comp.BoundsDirty = true; } #endregion @@ -325,7 +329,9 @@ public void LayerSetRotation(Layer layer, Angle value) layer._rotation = value; layer.UpdateLocalMatrix(); - RebuildBounds(layer.Owner); + _tree.QueueTreeUpdate(layer.Owner); + layer.BoundsDirty = true; + layer.Owner.Comp.BoundsDirty = true; } #endregion @@ -361,7 +367,9 @@ public void LayerSetOffset(Layer layer, Vector2 value) layer._offset = value; layer.UpdateLocalMatrix(); - RebuildBounds(layer.Owner); + _tree.QueueTreeUpdate(layer.Owner); + layer.BoundsDirty = true; + layer.Owner.Comp.BoundsDirty = true; } #endregion @@ -397,7 +405,8 @@ public void LayerSetVisible(Layer layer, bool value) layer._visible = value; QueueUpdateIsInert(layer.Owner); - RebuildBounds(layer.Owner); + _tree.QueueTreeUpdate(layer.Owner); + layer.Owner.Comp.BoundsDirty = true; } #endregion @@ -579,6 +588,9 @@ public void LayerSetRenderingStrategy(Layer layer, LayerRenderingStrategy value) DebugTools.AssertEqual(layer.Owner.Comp.Layers[layer.Index], layer); layer.RenderingStrategy = value; + layer.BoundsDirty = true; + layer.Owner.Comp.BoundsDirty = true; + _tree.QueueTreeUpdate(layer.Owner); } #endregion diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Setters.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Setters.cs index 6e3884a23e7..8546939aca4 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Setters.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Setters.cs @@ -72,8 +72,7 @@ public void SetVisible(Entity sprite, bool value) return; sprite.Comp._visible = value; - if (!sprite.Comp.TreeUpdateQueued) - _tree.QueueTreeUpdate(sprite!); + _tree.QueueTreeUpdate(sprite!); } public void SetDrawDepth(Entity sprite, int value) @@ -133,23 +132,29 @@ public void SetContainerOccluded(Entity sprite, bool value) _tree.QueueTreeUpdate(sprite!); } - public void SetShader(Entity sprite, ShaderInstance? shader) + public void SetSnapCardinals(Entity sprite, bool value) { if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) return; - sprite.Comp.PostShader = shader; + if (value == sprite.Comp._snapCardinals) + return; + + sprite.Comp._snapCardinals = value; + _tree.QueueTreeUpdate(sprite!); + DirtyBounds(sprite!); } - public void SetSnapCardinals(Entity sprite, bool value) + public void SetGranularLayersRendering(Entity sprite, bool value) { if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) return; - if (value == sprite.Comp._snapCardinals) + if (value == sprite.Comp.GranularLayersRendering) return; - sprite.Comp._snapCardinals = value; - RebuildBounds(sprite!); + sprite.Comp.GranularLayersRendering = value; + _tree.QueueTreeUpdate(sprite!); + DirtyBounds(sprite!); } } From 0a2753fdb3dff7cbc7816173e1db1efde5e468db Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Tue, 14 Jan 2025 16:49:46 +1300 Subject: [PATCH 34/36] RenderLayer --- .../Components/Renderable/SpriteComponent.cs | 110 +++--------------- .../EntitySystems/SpriteSystem.Helpers.cs | 5 + .../EntitySystems/SpriteSystem.Render.cs | 104 +++++++++++++++-- Robust.Client/Graphics/Clyde/Clyde.HLR.cs | 2 +- .../Graphics/Clyde/Clyde.Rendering.cs | 2 + Robust.Client/Placement/PlacementMode.cs | 2 +- 6 files changed, 124 insertions(+), 101 deletions(-) diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index 5a7fc5d13ef..64345b04755 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -962,7 +962,7 @@ public void LayerSetShader(object layerKey, string shaderName) [Obsolete("Use SpriteSystem.Render() instead.")] public void Render(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Direction? overrideDirection = null, Vector2 position = default) { - Sys.Render((Owner, this), drawingHandle, eyeRotation, worldRotation, position, overrideDirection); + Sys.RenderSprite((Owner, this), drawingHandle, eyeRotation, worldRotation, position, overrideDirection); } [Obsolete("Use SpriteSystem.LayerGetDirectionCount() instead.")] @@ -1540,24 +1540,32 @@ internal void UpdateActualState() } } + public void GetLayerDrawMatrix(RsiDirection dir, out Matrix3x2 layerDrawMatrix) + { + GetLayerDrawMatrix(dir, out layerDrawMatrix, Owner.Comp.NoRotation); + } + /// - /// Given the apparent rotation of an entity on screen (world + eye rotation), get layer's matrix for drawing & - /// relevant RSI direction. + /// Given the apparent rotation of an entity on screen (world + eye rotation), get layer's matrix for drawing & + /// relevant RSI direction. /// - public void GetLayerDrawMatrix(RsiDirection dir, out Matrix3x2 layerDrawMatrix) + internal void GetLayerDrawMatrix(RsiDirection dir, out Matrix3x2 layerDrawMatrix, bool noRot) { - if (_parent.NoRotation || dir == RsiDirection.South) + // TODO RENDERING + // Consider changing the RSI format (or at least modify the loaded textures) to remove this + // unnecessary matrix transformation. This transform is completely unnecessary for 4- and + // 1-directional sprites. Its only really required for 8-directional sprites. + + if (dir == RsiDirection.South || noRot) layerDrawMatrix = LocalMatrix; else - { - layerDrawMatrix = Matrix3x2.Multiply(_rsiDirectionMatrices[(int)dir], LocalMatrix); - } + layerDrawMatrix = Matrix3x2.Multiply(_rsiDirectionMatrices[(int) dir], LocalMatrix); } private static Matrix3x2[] _rsiDirectionMatrices = new Matrix3x2[] { // array order chosen such that this array can be indexed by casing an RSI direction to an int - Matrix3x2.Identity, // should probably just avoid matrix multiplication altogether if the direction is south. + Matrix3x2.Identity, Matrix3Helpers.CreateRotation(-Direction.North.ToAngle()), Matrix3Helpers.CreateRotation(-Direction.East.ToAngle()), Matrix3Helpers.CreateRotation(-Direction.West.ToAngle()), @@ -1575,7 +1583,8 @@ public static RsiDirection GetDirection(RsiDirectionType dirType, Angle angle) { if (dirType == RsiDirectionType.Dir1) return RsiDirection.South; - else if (dirType == RsiDirectionType.Dir8) + + if (dirType == RsiDirectionType.Dir8) return angle.GetDir().Convert(dirType); // For 4-directional sprites, as entities are often moving & facing diagonally, we will slightly bias the @@ -1595,87 +1604,6 @@ public static RsiDirection GetDirection(RsiDirectionType dirType, Angle angle) }; } - /// - /// Render a layer. This assumes that the input angle is between 0 and 2pi. - /// - internal void Render(DrawingHandleWorld drawingHandle, ref Matrix3x2 spriteMatrix, Angle angle, Direction? overrideDirection) - { - if (!Visible || Blank) - return; - - var dir = _actualState == null ? RsiDirection.South : GetDirection(_actualState.RsiDirections, angle); - - // Set the drawing transform for this layer - GetLayerDrawMatrix(dir, out var layerMatrix); - - // The direction used to draw the sprite can differ from the one that the angle would naively suggest, - // due to direction overrides or offsets. - if (overrideDirection != null && _actualState != null) - dir = overrideDirection.Value.Convert(_actualState.RsiDirections); - dir = dir.OffsetRsiDir(DirOffset); - - // Get the correct directional texture from the state, and draw it! - var texture = GetRenderTexture(_actualState, dir); - - if (CopyToShaderParameters == null) - { - // Set the drawing transform for this layer - var transformMatrix = Matrix3x2.Multiply(layerMatrix, spriteMatrix); - drawingHandle.SetTransform(in transformMatrix); - - RenderTexture(drawingHandle, texture); - } - else - { - // Multiple atrocities to god being committed right here. - var otherLayerIdx = _parent.LayerMap[CopyToShaderParameters.LayerKey!]; - var otherLayer = _parent.Layers[otherLayerIdx]; - if (otherLayer.Shader is not { } shader) - { - // No shader set apparently..? - return; - } - - if (!shader.Mutable) - otherLayer.Shader = shader = shader.Duplicate(); - - var clydeTexture = Clyde.RenderHandle.ExtractTexture(texture, null, out var csr); - var sr = Clyde.RenderHandle.WorldTextureBoundsToUV(clydeTexture, csr); - - if (CopyToShaderParameters.ParameterTexture is { } paramTexture) - shader.SetParameter(paramTexture, clydeTexture); - - if (CopyToShaderParameters.ParameterUV is { } paramUV) - { - var uv = new Vector4(sr.Left, sr.Bottom, sr.Right, sr.Top); - shader.SetParameter(paramUV, uv); - } - } - } - - private void RenderTexture(DrawingHandleWorld drawingHandle, Texture texture) - { - if (Shader != null) - drawingHandle.UseShader(Shader); - - var layerColor = _parent.color * Color; - var textureSize = texture.Size / (float)EyeManager.PixelsPerMeter; - var quad = Box2.FromDimensions(textureSize/-2, textureSize); - - drawingHandle.DrawTextureRectRegion(texture, quad, layerColor); - - if (Shader != null) - drawingHandle.UseShader(null); - } - - private Texture GetRenderTexture(RSI.State? state, RsiDirection dir) - { - if (state == null) - return Texture ?? _parent.resourceCache.GetFallback().Texture; - - return state.GetFrame(dir, AnimationFrame); - } - internal void AdvanceFrameAnimation(RSI.State state) { // Can't advance frames without more than 1 delay which is already checked above. diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Helpers.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Helpers.cs index 3b05804a56a..fcf3b58f7fc 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Helpers.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Helpers.cs @@ -108,6 +108,11 @@ public RSI.State GetFallbackState() return _resourceCache.GetFallback().RSI["error"]; } + public Texture GetFallbackTexture() + { + return _resourceCache.GetFallback().Texture; + } + [Pure] public RSI.State GetState(SpriteSpecifier.Rsi rsiSpecifier) { diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Render.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Render.cs index 9ab36b8e8c4..3597edae7ec 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Render.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Render.cs @@ -1,21 +1,26 @@ using System.Numerics; using Robust.Client.Graphics; +using Robust.Client.Graphics.Clyde; +using Robust.Client.Utility; using Robust.Shared.GameObjects; +using Robust.Shared.Graphics.RSI; using Robust.Shared.Maths; +using static Robust.Client.GameObjects.SpriteComponent; +using Vector4 = Robust.Shared.Maths.Vector4; namespace Robust.Client.GameObjects; // This partial class contains code related to actually rendering sprites. public sealed partial class SpriteSystem { - public void Render( + public void RenderSprite( Entity sprite, DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Vector2 worldPosition) { - Render(sprite, + RenderSprite(sprite, drawingHandle, eyeRotation, worldRotation, @@ -23,7 +28,7 @@ public void Render( sprite.Comp.EnableDirectionOverride ? sprite.Comp.DirectionOverride : null); } - public void Render( + public void RenderSprite( Entity sprite, DrawingHandleWorld drawingHandle, Angle eyeRotation, @@ -31,6 +36,13 @@ public void Render( Vector2 worldPosition, Direction? overrideDirection) { + // TODO SPRITE RENDERING + // Add fast path for simple sprites. + // I.e., when a sprite is modified, check if it is "simple". If it is. cache texture information in a struct + // and use a fast path here. + // E.g., simple 1-directional, 1-layer sprites can basically become a direct texture draw call. (most in game items). + // Similarly, 1-directional multi-layer sprites can become a sequence of direct draw calls (most in game walls). + if (!sprite.Comp.IsInert) _queuedFrameUpdate.Add(sprite); @@ -53,7 +65,7 @@ public void Render( { foreach (var layer in sprite.Comp.Layers) { - layer.Render(drawingHandle, ref spriteMatrix, angle, overrideDirection); + RenderLayer(layer, drawingHandle, ref spriteMatrix, angle, overrideDirection); } return; } @@ -77,16 +89,16 @@ public void Render( switch (layer.RenderingStrategy) { case LayerRenderingStrategy.UseSpriteStrategy: - layer.Render(drawingHandle, ref spriteMatrix, angle, overrideDirection); + RenderLayer(layer, drawingHandle, ref spriteMatrix, angle, overrideDirection); break; case LayerRenderingStrategy.Default: - layer.Render(drawingHandle, ref transformDefault, angle, overrideDirection); + RenderLayer(layer, drawingHandle, ref transformDefault, angle, overrideDirection); break; case LayerRenderingStrategy.NoRotation: - layer.Render(drawingHandle, ref transformNoRot, angle, overrideDirection); + RenderLayer(layer, drawingHandle, ref transformNoRot, angle, overrideDirection); break; case LayerRenderingStrategy.SnapToCardinals: - layer.Render(drawingHandle, ref transformSnap, angle, overrideDirection); + RenderLayer(layer, drawingHandle, ref transformSnap, angle, overrideDirection); break; default: Log.Error($"Tried to render a layer with unknown rendering stragegy: {layer.RenderingStrategy}"); @@ -94,4 +106,80 @@ public void Render( } } } + + /// + /// Render a layer. This assumes that the input angle is between 0 and 2pi. + /// + private void RenderLayer(Layer layer, DrawingHandleWorld drawingHandle, ref Matrix3x2 spriteMatrix, Angle angle, Direction? overrideDirection) + { + if (!layer.Visible || layer.Blank) + return; + + var state = layer._actualState; + var dir = state == null ? RsiDirection.South : Layer.GetDirection(state.RsiDirections, angle); + + // Set the drawing transform for this layer + layer.GetLayerDrawMatrix(dir, out var layerMatrix, layer.Owner.Comp.NoRotation); + + // The direction used to draw the sprite can differ from the one that the angle would naively suggest, + // due to direction overrides or offsets. + if (overrideDirection != null && state != null) + dir = overrideDirection.Value.Convert(state.RsiDirections); + dir = dir.OffsetRsiDir(layer.DirOffset); + + var texture = state?.GetFrame(dir, layer.AnimationFrame) ?? layer.Texture ?? GetFallbackTexture(); + + // TODO SPRITE + // Refactor shader-param-layers to a separate layer type after layers are split into types & collections. + // I.e., separate Layer -> RsiLayer, TextureLayer, LayerCollection, SpriteLayer, and ShaderLayer + if (layer.CopyToShaderParameters != null) + { + HandleShaderLayer(layer, texture, layer.CopyToShaderParameters); + return; + } + + // Set the drawing transform for this layer + var transformMatrix = Matrix3x2.Multiply(layerMatrix, spriteMatrix); + drawingHandle.SetTransform(in transformMatrix); + + if (layer.Shader != null) + drawingHandle.UseShader(layer.Shader); + + var layerColor = layer.Owner.Comp.color * layer.Color; + var textureSize = texture.Size / (float) EyeManager.PixelsPerMeter; + var quad = Box2.FromDimensions(textureSize / -2, textureSize); + + drawingHandle.DrawTextureRectRegion(texture, quad, layerColor); + + if (layer.Shader != null) + drawingHandle.UseShader(null); + } + + /// + /// Handle a a "fake layer" that just exists to modify the parameters of a shader being used by some other + /// layer. + /// + private void HandleShaderLayer(Layer layer, Texture texture, CopyToShaderParameters @params) + { + // Multiple atrocities to god being committed right here. + var otherLayerIdx = layer._parent.LayerMap[@params.LayerKey!]; + var otherLayer = layer._parent.Layers[otherLayerIdx]; + if (otherLayer.Shader is not { } shader) + return; + + if (!shader.Mutable) + otherLayer.Shader = shader = shader.Duplicate(); + + var clydeTexture = Clyde.RenderHandle.ExtractTexture(texture, null, out var csr); + + if (@params.ParameterTexture is { } paramTexture) + shader.SetParameter(paramTexture, clydeTexture); + + if (@params.ParameterUV is not { } paramUV) + return; + + var sr = Clyde.RenderHandle.WorldTextureBoundsToUV(clydeTexture, csr); + var uv = new Vector4(sr.Left, sr.Bottom, sr.Right, sr.Top); + shader.SetParameter(paramUV, uv); + } } diff --git a/Robust.Client/Graphics/Clyde/Clyde.HLR.cs b/Robust.Client/Graphics/Clyde/Clyde.HLR.cs index 42d1612d968..92ce6e6975f 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.HLR.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.HLR.cs @@ -355,7 +355,7 @@ private void DrawEntities(Viewport viewport, Box2Rotated worldBounds, Box2 world } } - spriteSystem.Render(new(entry.Uid, entry.Sprite), _renderHandle.DrawingHandleWorld, eye.Rotation, entry.WorldRot, entry.WorldPos); + spriteSystem.RenderSprite(new(entry.Uid, entry.Sprite), _renderHandle.DrawingHandleWorld, eye.Rotation, entry.WorldRot, entry.WorldPos); if (entry.Sprite.PostShader != null && entityPostRenderTarget != null) { diff --git a/Robust.Client/Graphics/Clyde/Clyde.Rendering.cs b/Robust.Client/Graphics/Clyde/Clyde.Rendering.cs index af19a2c1a2e..bed45811eb0 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.Rendering.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.Rendering.cs @@ -574,6 +574,8 @@ private void DrawTexture(ClydeHandle texture, Vector2 bl, Vector2 br, Vector2 tl EnsureBatchSpaceAvailable(4, GetQuadBatchIndexCount()); EnsureBatchState(texture, true, GetQuadBatchPrimitiveType(), _queuedShader); + // TODO RENDERING + // It's probably better to do this on the GPU. bl = Vector2.Transform(bl, _currentMatrixModel); br = Vector2.Transform(br, _currentMatrixModel); tr = Vector2.Transform(tr, _currentMatrixModel); diff --git a/Robust.Client/Placement/PlacementMode.cs b/Robust.Client/Placement/PlacementMode.cs index f3e6ea67c76..62b48d1b7bb 100644 --- a/Robust.Client/Placement/PlacementMode.cs +++ b/Robust.Client/Placement/PlacementMode.cs @@ -126,7 +126,7 @@ public virtual void Render(in OverlayDrawArgs args) sprite.Color = IsValidPosition(coordinate) ? ValidPlaceColor : InvalidPlaceColor; var rot = args.Viewport.Eye?.Rotation ?? default; - spriteSys.Render(uid.Value, sprite, args.WorldHandle, rot, worldRot, worldPos); + spriteSys.RenderSprite(uid.Value, sprite, args.WorldHandle, rot, worldRot, worldPos); } } From 6a2b36902a774fd351d8444fa1703173feaeb03a Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Tue, 14 Jan 2025 17:15:51 +1300 Subject: [PATCH 35/36] RefreshCachedState --- .../Components/Renderable/SpriteComponent.cs | 20 ++++++- .../EntitySystems/SpriteSystem.Bounds.cs | 4 +- .../SpriteSystem.LayerSetters.cs | 52 +++++++++++-------- .../EntitySystems/SpriteSystem.Setters.cs | 8 ++- Robust.Client/Placement/PlacementMode.cs | 2 +- 5 files changed, 59 insertions(+), 27 deletions(-) diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index 64345b04755..d3fab92634f 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -1079,11 +1079,14 @@ public sealed class Layer : ISpriteLayer, ISerializationHooks [ViewVariables] public RSI? RSI { get => _rsi; + [Obsolete("Use SpriteSystem.LayerSetRsi() instead.")] set { if (_rsi == value) return; + BoundsDirty = true; + Owner.Comp.BoundsDirty = true; _rsi = value; UpdateActualState(); } @@ -1093,6 +1096,7 @@ [ViewVariables] public RSI? RSI [ViewVariables] public RSI.StateId State { get => StateId; + [Obsolete("Use SpriteSystem.LayerSetRsiState() instead.")] set { if (StateId == value) @@ -1120,15 +1124,18 @@ [ViewVariables] public RSI.StateId State /// [ViewVariables] public bool Cycle; + // TODO SPRITE ACCESS internal RSI.State? _actualState; [ViewVariables] public RSI.State? ActualState => _actualState; + // TODO SPRITE ACCESS public Matrix3x2 LocalMatrix = Matrix3x2.Identity; [ViewVariables(VVAccess.ReadWrite)] public Vector2 Scale { get => _scale; + [Obsolete("Use SpriteSystem.LayerSetScale() instead.")] set { if (_scale.EqualsApprox(value)) return; @@ -1153,6 +1160,7 @@ public Vector2 Scale public Angle Rotation { get => _rotation; + [Obsolete("Use SpriteSystem.LayerSetRotation() instead.")] set { if (_rotation.EqualsApprox(value)) return; @@ -1171,6 +1179,7 @@ public Angle Rotation public bool Visible { get => _visible; + [Obsolete("Use SpriteSystem.LayerSetVisible() instead.")] set { if (_visible == value) @@ -1197,6 +1206,7 @@ public bool Visible public bool AutoAnimated { get => _autoAnimated; + [Obsolete("Use SpriteSystem.LayerSetAutoAnimated() instead.")] set { if (_autoAnimated == value) @@ -1212,6 +1222,7 @@ public bool AutoAnimated public Vector2 Offset { get => _offset; + [Obsolete("Use SpriteSystem.LayerSetOffset() instead.")] set { if (_offset.EqualsApprox(value)) return; @@ -1239,9 +1250,11 @@ public Vector2 Offset /// Whether the current layer have a specific rendering method (e.g no rotation or snap to cardinal) /// The sprite GranularLayersRendering var must be set to true for this to have any effect. /// - [ViewVariables] // TODO access restrict + [ViewVariables] // TODO SPRITE ACCESS public LayerRenderingStrategy RenderingStrategy = LayerRenderingStrategy.UseSpriteStrategy; + // TODO SPRITE ACCESS + // If someone sets this, it stops the actual layer from being drawn, which should chage the sprites bounds. [ViewVariables(VVAccess.ReadWrite)] public CopyToShaderParameters? CopyToShaderParameters; @@ -1373,6 +1386,7 @@ public RsiDirection EffectiveDirection(RSI.State state, Angle worldRotation, } } + [Obsolete("Use SpriteSystem.LayerSetAnimationTime")] public void SetAnimationTime(float animationTime) { if (!State.IsValid) @@ -1404,6 +1418,7 @@ public void SetAnimationTime(float animationTime) AdvanceFrameAnimation(state); } + [Obsolete("Use SpriteSystem.LayerSetAutoAnimated")] public void SetAutoAnimated(bool value) { AutoAnimated = value; @@ -1411,6 +1426,7 @@ public void SetAutoAnimated(bool value) _parent.Sys.QueueUpdateIsInert((_parent.Owner, _parent)); } + [Obsolete("Use SpriteSystem.LayerSetRsi")] public void SetRsi(RSI? rsi) { RSI = rsi; @@ -1447,6 +1463,7 @@ public void SetRsi(RSI? rsi) _parent.Sys.QueueUpdateIsInert((_parent.Owner, _parent)); } + [Obsolete("Use SpriteSystem.LayerSetRsiState")] public void SetState(RSI.StateId stateId) { if (State == stateId) @@ -1479,6 +1496,7 @@ public void SetState(RSI.StateId stateId) _parent.Sys.QueueUpdateIsInert((_parent.Owner, _parent)); } + [Obsolete("Use SpriteSystem.LayerSetTexture")] public void SetTexture(Texture? texture) { State = default; diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Bounds.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Bounds.cs index 4ddd26dbac8..3d8e0c45b6f 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Bounds.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Bounds.cs @@ -28,7 +28,7 @@ public Box2 GetLocalBounds(Entity sprite) var bounds = new Box2(); foreach (var layer in sprite.Comp.Layers) { - if (layer is {Visible: true, Blank: false}) + if (layer is {Visible: true, Blank: false, CopyToShaderParameters: null}) bounds = bounds.Union(GetLocalBounds(layer)); } @@ -53,7 +53,7 @@ public Box2 GetLocalBounds(Layer layer) internal Box2 CalculateLocalBounds(Layer layer) { - if (layer.Blank) + if (layer.Blank || layer.CopyToShaderParameters == null) return Box2.Empty; var textureSize = (Vector2) layer.PixelSize / EyeManager.PixelsPerMeter; diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs index c07cddf4320..dd1c7e1520c 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.LayerSetters.cs @@ -173,28 +173,7 @@ public void LayerSetRsiState(Layer layer, StateId state, bool refresh = false) return; layer.StateId = state; - - if (!layer.StateId.IsValid) - { - layer._actualState = null; - } - else if (layer.ActualRsi is not { } rsi) - { - Log.Error( - $"{ToPrettyString(layer.Owner)} has no RSI to pull new state from! Trace:\n{Environment.StackTrace}"); - layer._actualState = GetFallbackState(); - } - else if (!rsi.TryGetState(layer.StateId, out layer._actualState)) - { - layer._actualState = GetFallbackState(); - Log.Error( - $"{ToPrettyString(layer.Owner)}'s state '{state}' does not exist in RSI {rsi.Path}. Trace:\n{Environment.StackTrace}"); - } - - layer.AnimationFrame = 0; - layer.AnimationTime = 0; - layer.AnimationTimeLeft = layer._actualState?.GetDelay(0) ?? 0f; - + RefreshCachedState(layer, true, null); _tree.QueueTreeUpdate(layer.Owner); QueueUpdateIsInert(layer.Owner); layer.BoundsDirty = true; @@ -594,4 +573,33 @@ public void LayerSetRenderingStrategy(Layer layer, LayerRenderingStrategy value) } #endregion + + /// + /// Refreshes an RSI layer's cached RSI state. + /// + private void RefreshCachedState(Layer layer, bool logErrors, RSI.State? fallback) + { + if (!layer.StateId.IsValid) + { + layer._actualState = null; + } + else if (layer.ActualRsi is not { } rsi) + { + layer._actualState = fallback ?? GetFallbackState(); + if (logErrors) + Log.Error( + $"{ToPrettyString(layer.Owner)} has no RSI to pull new state from! Trace:\n{Environment.StackTrace}"); + } + else if (!rsi.TryGetState(layer.StateId, out layer._actualState)) + { + layer._actualState = fallback ?? GetFallbackState(); + if (logErrors) + Log.Error( + $"{ToPrettyString(layer.Owner)}'s state '{layer.StateId}' does not exist in RSI {rsi.Path}. Trace:\n{Environment.StackTrace}"); + } + + layer.AnimationFrame = 0; + layer.AnimationTime = 0; + layer.AnimationTimeLeft = layer._actualState?.GetDelay(0) ?? 0f; + } } diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Setters.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Setters.cs index 8546939aca4..75b2571cfea 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Setters.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Setters.cs @@ -91,6 +91,11 @@ public void SetColor(Entity sprite, Color value) sprite.Comp.color = value; } + /// + /// Modify a sprites base RSI. This is the RSI that is used by any RSI layers that do not specify their own. + /// Note that changing the base RSI may result in existing layers having an invalid state. This will not log errors + /// under the assumption that the states of each layers will be updated after the base RSI has changed. + /// public void SetBaseRsi(Entity sprite, RSI? value) { if (!_query.Resolve(sprite.Owner, ref sprite.Comp)) @@ -103,13 +108,14 @@ public void SetBaseRsi(Entity sprite, RSI? value) if (value == null) return; + var fallback = GetFallbackState(); for (var i = 0; i < sprite.Comp.Layers.Count; i++) { var layer = sprite.Comp.Layers[i]; if (!layer.State.IsValid || layer.RSI != null) continue; - layer.UpdateActualState(); + RefreshCachedState(layer, logErrors: false, fallback); if (value.TryGetState(layer.State, out var state)) { diff --git a/Robust.Client/Placement/PlacementMode.cs b/Robust.Client/Placement/PlacementMode.cs index 62b48d1b7bb..e9371d13279 100644 --- a/Robust.Client/Placement/PlacementMode.cs +++ b/Robust.Client/Placement/PlacementMode.cs @@ -126,7 +126,7 @@ public virtual void Render(in OverlayDrawArgs args) sprite.Color = IsValidPosition(coordinate) ? ValidPlaceColor : InvalidPlaceColor; var rot = args.Viewport.Eye?.Rotation ?? default; - spriteSys.RenderSprite(uid.Value, sprite, args.WorldHandle, rot, worldRot, worldPos); + spriteSys.RenderSprite((uid.Value, sprite), args.WorldHandle, rot, worldRot, worldPos); } } From d268a1c4fd9310784f3f3515928c25333b06a9f7 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Tue, 14 Jan 2025 17:51:38 +1300 Subject: [PATCH 36/36] RoundToCardinalAngle --- .../Components/Renderable/SpriteComponent.cs | 2 ++ .../GameObjects/EntitySystems/SpriteSystem.Render.cs | 6 ++---- Robust.Shared.Maths/Angle.cs | 10 ++++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index d3fab92634f..ddbe6904258 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -480,6 +480,8 @@ public bool SnapCardinals [DataField("noRot")] public bool NoRotation; + // TODO SPRITE + // When refactoring, make this nullable and remove EnableDirectionOverride [DataField("overrideDir")] public Direction DirectionOverride = Direction.East; diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Render.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Render.cs index 3597edae7ec..d7772cf6626 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Render.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Render.cs @@ -53,7 +53,7 @@ public void RenderSprite( // If we have a 1-directional sprite then snap it to try and always face it south if applicable. if (sprite.Comp is {NoRotation: false, SnapCardinals: true}) - cardinal = angle.GetCardinalDir().ToAngle(); + cardinal = angle.RoundToCardinalAngle(); // worldRotation + eyeRotation should be the angle of the entity on-screen. If no-rot is enabled this is just set to zero. // However, at some point later the eye-matrix is applied separately, so we subtract -eye rotation for now: @@ -70,14 +70,12 @@ public void RenderSprite( return; } - // TODO sprite optimize angle.GetCardinalDir().ToAngle() - //Default rendering (NoRotation = false) entityMatrix = Matrix3Helpers.CreateTransform(worldPosition, worldRotation); var transformDefault = Matrix3x2.Multiply(sprite.Comp.LocalMatrix, entityMatrix); //Snap to cardinals - entityMatrix = Matrix3Helpers.CreateTransform(worldPosition, worldRotation - angle.GetCardinalDir().ToAngle()); + entityMatrix = Matrix3Helpers.CreateTransform(worldPosition, worldRotation - angle.RoundToCardinalAngle()); var transformSnap = Matrix3x2.Multiply(sprite.Comp.LocalMatrix, entityMatrix); //No rotation diff --git a/Robust.Shared.Maths/Angle.cs b/Robust.Shared.Maths/Angle.cs index eb99fc5cea6..49cf7bb0b4e 100644 --- a/Robust.Shared.Maths/Angle.cs +++ b/Robust.Shared.Maths/Angle.cs @@ -104,6 +104,16 @@ public readonly Direction GetCardinalDir() return (Direction) (Math.Floor((ang + CardinalOffset) / CardinalSegment) * 2 % 8); } + /// + /// Rounds the angle to the nearest cardinal direction. This behaves similarly to a combination of + /// and Direction.ToAngle(), however this may return an angle outside of the range + /// returned by those methods (-pi to pi). + /// + public Angle RoundToCardinalAngle() + { + return new Angle(CardinalSegment * Math.Floor((Theta + CardinalOffset) / CardinalSegment)); + } + /// /// Rotates the vector counter-clockwise around its origin by the value of Theta. ///