mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-09-02 10:07:15 +02:00
2001 lines
74 KiB
C#
2001 lines
74 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Globalization;
|
|
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;
|
|
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.Reflection;
|
|
using Robust.Shared.Serialization;
|
|
using Robust.Shared.Serialization.Manager.Attributes;
|
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
|
using Robust.Shared.Utility;
|
|
using Robust.Shared.ViewVariables;
|
|
using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
|
|
using static Robust.Shared.Serialization.TypeSerializers.Implementations.SpriteSpecifierSerializer;
|
|
using Direction = Robust.Shared.Maths.Direction;
|
|
#pragma warning disable CS0618 // Type or member is obsolete
|
|
|
|
namespace Robust.Client.GameObjects
|
|
{
|
|
[RegisterComponent]
|
|
public sealed partial class SpriteComponent : Component, IComponentDebug, ISerializationHooks, IComponentTreeEntry<SpriteComponent>, IAnimationProperties
|
|
{
|
|
public const string LogCategory = "go.comp.sprite";
|
|
|
|
[Dependency] private IResourceCacheInternal resourceCache = default!;
|
|
[Dependency] private IPrototypeManager prototypes = default!;
|
|
[Dependency] private EntityManager entities = default!;
|
|
[Dependency] private IReflectionManager reflection = default!;
|
|
|
|
/// <summary>
|
|
/// See <see cref="CVars.RenderSpriteDirectionBias"/>.
|
|
/// </summary>
|
|
public static double DirectionBias = -0.05;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
[DataField] // TODO Sprite access restrict.
|
|
public bool GranularLayersRendering = false;
|
|
|
|
[DataField("visible")]
|
|
internal bool _visible = true;
|
|
|
|
// VV convenience variable to examine layer objects using layer keys
|
|
// ReSharper disable once UnusedMember.Local
|
|
[ViewVariables]
|
|
private Dictionary<object, Layer> MappedLayers => LayerMap.ToDictionary(x => x.Key, x => Layers[x.Value]);
|
|
|
|
[ViewVariables(VVAccess.ReadWrite)]
|
|
public bool Visible
|
|
{
|
|
get => _visible;
|
|
[Obsolete("Use SpriteSystem.SetVisible() instead.")]
|
|
set => Sys.SetVisible((Owner, this), value);
|
|
}
|
|
|
|
private SpriteSystem? _sys;
|
|
private SpriteTreeSystem? _treeSys;
|
|
private SpriteSystem Sys => _sys ??= (entities.Started ? entities.System<SpriteSystem>() : null)!;
|
|
private SpriteTreeSystem TreeSys => _treeSys ??= (entities.Started ? entities.System<SpriteTreeSystem>() : null)!;
|
|
|
|
[DataField("drawdepth", customTypeSerializer: typeof(ConstantSerializer<DrawDepthTag>))]
|
|
internal int drawDepth = DrawDepthTag.Default;
|
|
|
|
/// <summary>
|
|
/// Z-index for drawing.
|
|
/// </summary>
|
|
[ViewVariables(VVAccess.ReadWrite)]
|
|
public int DrawDepth
|
|
{
|
|
get => drawDepth;
|
|
[Obsolete("Use SpriteSystem.SetDrawDepth() instead.")]
|
|
set => Sys.SetDrawDepth((Owner, this), value);
|
|
}
|
|
|
|
[DataField("scale")] // Explicit name, in case this field ever gets renamed
|
|
internal Vector2 scale = Vector2.One;
|
|
|
|
/// <summary>
|
|
/// A scale applied to all layers.
|
|
/// </summary>
|
|
[Animatable]
|
|
[ViewVariables(VVAccess.ReadWrite)]
|
|
public Vector2 Scale
|
|
{
|
|
get => scale;
|
|
[Obsolete("Use SpriteSystem.SetScale() instead.")]
|
|
set => Sys.SetScale((Owner, this), value);
|
|
}
|
|
|
|
[DataField("rotation")] // Explicit name, in case this field ever gets renamed
|
|
internal Angle rotation = Angle.Zero;
|
|
|
|
[Animatable]
|
|
[ViewVariables(VVAccess.ReadWrite)]
|
|
public Angle Rotation
|
|
{
|
|
get => rotation;
|
|
[Obsolete("Use SpriteSystem.SetRotation() instead.")]
|
|
set => Sys.SetRotation((Owner, this), value);
|
|
}
|
|
|
|
[DataField("offset")] // Explicit name, in case this field ever gets renamed
|
|
internal Vector2 offset = Vector2.Zero;
|
|
|
|
/// <summary>
|
|
/// Offset applied to all layers.
|
|
/// </summary>
|
|
[Animatable]
|
|
[ViewVariables(VVAccess.ReadWrite)]
|
|
public Vector2 Offset
|
|
{
|
|
get => offset;
|
|
[Obsolete("Use SpriteSystem.SetOffset() instead.")]
|
|
set => Sys.SetOffset((Owner, this), value);
|
|
}
|
|
|
|
[DataField("color")] // Explicit name, in case this field ever gets renamed
|
|
internal Color color = Color.White;
|
|
|
|
[Animatable]
|
|
[ViewVariables(VVAccess.ReadWrite)]
|
|
public Color Color
|
|
{
|
|
get => color;
|
|
[Obsolete("Use SpriteSystem.SetColor() instead.")]
|
|
set => Sys.SetColor((Owner, this), value);
|
|
}
|
|
|
|
public Matrix3x2 LocalMatrix = Matrix3x2.Identity;
|
|
|
|
[ViewVariables]
|
|
public DynamicTree<ComponentTreeEntry<SpriteComponent>>? Tree { get; set; }
|
|
|
|
public EntityUid? TreeUid { get; set; }
|
|
|
|
public bool AddToTree => Visible && !ContainerOccluded && Layers.Count > 0;
|
|
|
|
public bool TreeUpdateQueued { get; set; }
|
|
|
|
internal RSI? _baseRsi;
|
|
|
|
[ViewVariables(VVAccess.ReadWrite)]
|
|
public RSI? BaseRSI
|
|
{
|
|
get => _baseRsi;
|
|
[Obsolete("Use SpriteSystem.SetBaseRSI() instead.")]
|
|
set => Sys.SetBaseRsi((Owner, this), value);
|
|
}
|
|
|
|
[DataField("sprite", readOnly: true)] internal string? rsi;
|
|
[DataField("layers", readOnly: true)] internal List<PrototypeLayerData> layerDatums = new();
|
|
|
|
[DataField(readOnly: true)] private string? state;
|
|
[DataField(readOnly: true)] private string? texture;
|
|
|
|
/// <summary>
|
|
/// Should this entity show up in containers regardless of whether the container can show contents?
|
|
/// </summary>
|
|
[DataField("overrideContainerOcclusion")]
|
|
[ViewVariables(VVAccess.ReadWrite)]
|
|
public bool OverrideContainerOcclusion;
|
|
|
|
[ViewVariables(VVAccess.ReadWrite)]
|
|
public bool ContainerOccluded
|
|
{
|
|
get => _containerOccluded && !OverrideContainerOcclusion;
|
|
[Obsolete("Use SpriteSystem.SetContainerOccluded() instead.")]
|
|
set => Sys.SetContainerOccluded((Owner, this), value);
|
|
}
|
|
|
|
internal bool _containerOccluded;
|
|
|
|
/// <summary>
|
|
/// Whether or not the sprite's local bounding box is dirty and need to be rebuilt.
|
|
/// </summary>
|
|
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;
|
|
|
|
private const string LegacyPostShaderId = "__legacy";
|
|
|
|
/// <summary>
|
|
/// Ordered post-shaders to apply. These get run on the final entity sprite.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Read-only as these should be constructed from base prototype + runtime.
|
|
/// </remarks>
|
|
[ViewVariables]
|
|
[DataField(readOnly: true)]
|
|
internal List<PostShaderEntry> PostShaders = new();
|
|
|
|
[ViewVariables]
|
|
internal bool PostShaderOrderDirty;
|
|
|
|
/// <summary>
|
|
/// Legacy for code that still expects 1 shader.
|
|
/// </summary>
|
|
[ViewVariables]
|
|
[Obsolete("Use SpriteSystem.SetPostShader(), SpriteSystem.RemovePostShader(), or SpriteSystem.ClearPostShaders() instead.")]
|
|
public ShaderInstance? PostShader
|
|
{
|
|
get => PostShaders.Count == 1 ? PostShaders[0].Shader : null;
|
|
set
|
|
{
|
|
PostShaders.Clear();
|
|
|
|
if (value != null)
|
|
PostShaders.Add(new PostShaderEntry(LegacyPostShaderId, value));
|
|
|
|
PostShaderOrderDirty = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Legacy single-post-shader flag.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// New post-shader code should set this per entry through SpriteSystem.SetPostShader().
|
|
/// </remarks>
|
|
[DataField]
|
|
[Obsolete("Use SpriteSystem.SetPostShader(..., getScreenTexture: true) instead.")]
|
|
public bool GetScreenTexture;
|
|
|
|
/// <summary>
|
|
/// Legacy single-post-shader flag.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// New post-shader code should set this per entry through SpriteSystem.SetPostShader().
|
|
/// </remarks>
|
|
[DataField]
|
|
[Obsolete("Use SpriteSystem.SetPostShader(..., raiseShaderEvent: true) instead.")]
|
|
public bool RaiseShaderEvent;
|
|
|
|
[ViewVariables]
|
|
internal bool HasPostShaders => PostShaders.Count != 0;
|
|
|
|
[ViewVariables] internal Dictionary<object, int> LayerMap { get; set; } = new();
|
|
[ViewVariables] internal List<Layer> Layers = new();
|
|
|
|
[ViewVariables(VVAccess.ReadWrite)] public uint RenderOrder { get; set; }
|
|
|
|
[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<ISpriteLayer> AllLayers => Layers;
|
|
|
|
void ISerializationHooks.AfterDeserialization()
|
|
{
|
|
// Please somebody burn this to the ground. There is so much spaghetti.
|
|
// Why has no one answered my prayers.
|
|
// I answered half of your prayer someone please answer the rest
|
|
|
|
IoCManager.InjectDependencies(this);
|
|
|
|
resourceCache.AddToDeserialize(this);
|
|
|
|
if (layerDatums.Count == 0)
|
|
{
|
|
if (state != null || texture != null)
|
|
{
|
|
layerDatums.Insert(0, new PrototypeLayerData
|
|
{
|
|
TexturePath = string.IsNullOrWhiteSpace(texture) ? null : texture,
|
|
State = string.IsNullOrWhiteSpace(state) ? null : state,
|
|
Color = Color.White,
|
|
Scale = Vector2.One,
|
|
Visible = true,
|
|
RenderingStrategy = LayerRenderingStrategy.UseSpriteStrategy,
|
|
Cycle = false,
|
|
});
|
|
state = null;
|
|
texture = null;
|
|
}
|
|
}
|
|
|
|
BoundsDirty = true;
|
|
LocalMatrix = Matrix3Helpers.CreateTransform(in offset, in rotation, in scale);
|
|
}
|
|
|
|
/// <summary>
|
|
/// If false, this will prevent any of this sprite's animated layers from looping their animation.
|
|
/// This will set <see cref="Layer.AutoAnimated"/> whenever any layer's animation finishes.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// If this is false, this effectively overrides each layer's own <see cref="Layer.Loop"/>.
|
|
/// </remarks>
|
|
[DataField]
|
|
public bool Loop = true;
|
|
|
|
/// <summary>
|
|
/// Update this sprite component to visibly match the current state of other at the time
|
|
/// 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.
|
|
/// </summary>
|
|
[Obsolete("Use SpriteSystem.CopySprite() instead.")]
|
|
public void CopyFrom(SpriteComponent other)
|
|
{
|
|
Sys.CopySprite((other.Owner, other), (Owner, 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)
|
|
{
|
|
throw new ArgumentOutOfRangeException();
|
|
}
|
|
|
|
LayerMap.Add(key, layer);
|
|
}
|
|
|
|
[Obsolete("Use SpriteSystem.LayerMapRemove() instead.")]
|
|
public void LayerMapRemove(object key)
|
|
{
|
|
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);
|
|
|
|
if (!result && logError)
|
|
{
|
|
Logger.ErrorS(LogCategory, "{0} - Layer with key '{1}' does not exist! Trace:\n{2}",
|
|
entities.ToPrettyString(Owner), key, Environment.StackTrace);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
[Obsolete("Use SpriteSystem.TryGetLayer() instead.")]
|
|
public bool TryGetLayer(int index, [NotNullWhen(true)] out Layer? layer, bool logError = false)
|
|
=> Sys.TryGetLayer((Owner, this), index, out layer, logError);
|
|
|
|
[Obsolete("Use SpriteSystem.LayerExists() instead.")]
|
|
public bool LayerExists(int layer, bool logError = true)
|
|
=> Sys.LayerExists((Owner, this), layer);
|
|
|
|
[Obsolete("Use SpriteSystem.LayerExists() instead.")]
|
|
public bool LayerExists(object key, bool logError = false) => LayerMapTryGet(key, out _, logError);
|
|
|
|
[Obsolete("Use SpriteSystem.LayerMapReserve() instead.")]
|
|
public int LayerMapReserveBlank(object key)
|
|
{
|
|
if (LayerMapTryGet(key, out var index))
|
|
{
|
|
return index;
|
|
}
|
|
|
|
index = AddBlankLayer();
|
|
LayerMapSet(key, index);
|
|
|
|
return index;
|
|
}
|
|
|
|
[Obsolete("Use SpriteSystem.AddBlankLayer() instead.")]
|
|
public int AddBlankLayer(int? newIndex = null)
|
|
=> Sys.AddBlankLayer((Owner, this), newIndex).Index;
|
|
|
|
[Obsolete("Use SpriteSystem.AddLayer() instead.")]
|
|
public int AddLayer(PrototypeLayerData layerDatum, int? newIndex = null)
|
|
=> 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)
|
|
=> Sys.AddTextureLayer((Owner, this), texturePath, newIndex);
|
|
|
|
[Obsolete("Use SpriteSystem.AddTextureLayer() instead.")]
|
|
public int AddLayer(Texture? texture, int? newIndex = null)
|
|
=> Sys.AddTextureLayer((Owner, this), texture, newIndex);
|
|
|
|
[Obsolete("Use SpriteSystem.AddRsiLayer() instead.")]
|
|
public int AddLayer(RSI.StateId stateId, int? newIndex = null)
|
|
=> 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)
|
|
=> 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)
|
|
=> 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)
|
|
=> Sys.AddLayer((Owner, this), specifier, newIndex);
|
|
|
|
[Obsolete("Use SpriteSystem.RemoveLayer() instead.")]
|
|
public void RemoveLayer(int layer)
|
|
=> Sys.RemoveLayer((Owner, this), layer);
|
|
|
|
[Obsolete("Use SpriteSystem.RemoveLayer() instead.")]
|
|
public void RemoveLayer(object layerKey)
|
|
{
|
|
if (!LayerMapTryGet(layerKey, out var layer, true))
|
|
return;
|
|
|
|
RemoveLayer(layer);
|
|
}
|
|
|
|
[DataField("snapCardinals")]
|
|
internal bool _snapCardinals = false;
|
|
|
|
/// <summary>
|
|
/// If the sprite only has 1 direction should it snap at cardinals if rotated.
|
|
/// </summary>
|
|
[ViewVariables(VVAccess.ReadWrite)]
|
|
public bool SnapCardinals
|
|
{
|
|
get => _snapCardinals;
|
|
[Obsolete("Use SpriteSystem.SnapCardinals() instead.")]
|
|
set => Sys.SetSnapCardinals((Owner, this), value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
[DataField("noRot")]
|
|
public bool NoRotation;
|
|
|
|
// TODO SPRITE
|
|
// When refactoring, make this nullable and remove EnableDirectionOverride
|
|
[DataField("overrideDir")]
|
|
public Direction DirectionOverride = Direction.East;
|
|
|
|
[DataField("enableOverrideDir")]
|
|
public bool EnableDirectionOverride;
|
|
|
|
[Obsolete("Use SpriteSystem.LayerSetData() instead.")]
|
|
public void LayerSetData(int index, PrototypeLayerData layerDatum)
|
|
=> Sys.LayerSetData((Owner, this), index, layerDatum);
|
|
|
|
[Obsolete("Use SpriteSystem.LayerSetData() instead.")]
|
|
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;
|
|
|
|
if (resourceCache.TryGetResource(path, out RSIResource? resource))
|
|
{
|
|
layer.RSI = resource.RSI;
|
|
}
|
|
else
|
|
{
|
|
Logger.ErrorS(LogCategory, "Unable to load layer RSI '{0}'.", path);
|
|
}
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(layerDatum.State))
|
|
{
|
|
var theRsi = layer.RSI ?? BaseRSI;
|
|
if (theRsi == null)
|
|
{
|
|
Logger.ErrorS(LogCategory,
|
|
"Layer has no RSI to load states from. Cannot use 'state' property. ({0})",
|
|
layerDatum.State);
|
|
}
|
|
else
|
|
{
|
|
var stateid = new RSI.StateId(layerDatum.State);
|
|
layer.State = stateid;
|
|
if (theRsi.TryGetState(stateid, out var state))
|
|
{
|
|
// Always use south because this layer will be cached in the serializer.
|
|
layer.AnimationTimeLeft = state.GetDelay(0);
|
|
layer.AnimationTime = 0;
|
|
layer.AnimationFrame = 0;
|
|
}
|
|
else
|
|
{
|
|
Logger.ErrorS(LogCategory,
|
|
$"State '{stateid}' not found in RSI: '{theRsi.Path}'.",
|
|
stateid);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(layerDatum.TexturePath))
|
|
{
|
|
if (layer.State.IsValid)
|
|
{
|
|
Logger.ErrorS(LogCategory,
|
|
"Cannot specify 'texture' on a layer if it has an RSI state specified."
|
|
);
|
|
}
|
|
else
|
|
{
|
|
layer.Texture =
|
|
resourceCache.GetResource<TextureResource>(TextureRoot / layerDatum.TexturePath);
|
|
}
|
|
}
|
|
|
|
if (layerDatum.Shader != null)
|
|
{
|
|
if (layerDatum.Shader == string.Empty)
|
|
{
|
|
layer.ShaderPrototype = null;
|
|
layer.UnShaded = false;
|
|
layer.Shader = null;
|
|
}
|
|
else if (layerDatum.Shader == SpriteSystem.UnshadedId.Id)
|
|
{
|
|
layer.ShaderPrototype = SpriteSystem.UnshadedId;
|
|
layer.UnShaded = true;
|
|
layer.Shader = null;
|
|
}
|
|
else if (prototypes.TryIndex<ShaderPrototype>(layerDatum.Shader, out var prototype))
|
|
{
|
|
layer.ShaderPrototype = layerDatum.Shader;
|
|
layer.Shader = prototype.Instance();
|
|
layer.UnShaded = false;
|
|
}
|
|
else
|
|
{
|
|
Logger.ErrorS(LogCategory,
|
|
"Shader prototype '{0}' does not exist.",
|
|
layerDatum.Shader);
|
|
}
|
|
}
|
|
|
|
if (layerDatum.MapKeys != null)
|
|
{
|
|
foreach (var keyString in layerDatum.MapKeys)
|
|
{
|
|
var key = ParseKey(keyString);
|
|
|
|
if (LayerMap.TryGetValue(key, out var mappedIndex))
|
|
{
|
|
if (mappedIndex != layer.Index)
|
|
Logger.ErrorS(LogCategory, "Duplicate layer map key definition: {0}", key);
|
|
continue;
|
|
}
|
|
|
|
LayerMap[key] = layer.Index;
|
|
}
|
|
}
|
|
|
|
layer.RenderingStrategy = layerDatum.RenderingStrategy ?? layer.RenderingStrategy;
|
|
layer.Cycle = layerDatum.Cycle;
|
|
layer.Loop = layerDatum.Loop;
|
|
|
|
layer.Color = layerDatum.Color ?? layer.Color;
|
|
layer._rotation = layerDatum.Rotation ?? layer._rotation;
|
|
layer._offset = layerDatum.Offset ?? layer._offset;
|
|
layer._scale = layerDatum.Scale ?? layer._scale;
|
|
layer.UpdateLocalMatrix();
|
|
|
|
// If neither state: nor texture: were provided we assume that they want a blank invisible layer.
|
|
layer.Visible = layerDatum.Visible ?? layer.Visible;
|
|
|
|
if (layerDatum.CopyToShaderParameters is { } copyParameters)
|
|
{
|
|
layer.CopyToShaderParameters = new CopyToShaderParameters(ParseKey(copyParameters.LayerKey))
|
|
{
|
|
ParameterTexture = copyParameters.ParameterTexture,
|
|
ParameterUV = copyParameters.ParameterUV
|
|
};
|
|
}
|
|
else
|
|
{
|
|
layer.CopyToShaderParameters = null;
|
|
}
|
|
|
|
BoundsDirty = true;
|
|
layer.BoundsDirty = true;
|
|
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
|
|
if (Owner != EntityUid.Invalid)
|
|
TreeSys?.QueueTreeUpdate((Owner, this));
|
|
}
|
|
|
|
private object ParseKey(string keyString)
|
|
{
|
|
if (reflection.TryParseEnumReference(keyString, out var @enum))
|
|
return @enum;
|
|
|
|
return keyString;
|
|
}
|
|
|
|
[Obsolete("Use SpriteSystem.LayerSetData() instead.")]
|
|
public void LayerSetData(object layerKey, PrototypeLayerData data)
|
|
{
|
|
if (!LayerMapTryGet(layerKey, out var layer, true))
|
|
return;
|
|
|
|
LayerSetData(layer, data);
|
|
}
|
|
|
|
[Obsolete("Use SpriteSystem.LayerSetSprite() instead.")]
|
|
public void LayerSetSprite(int layer, SpriteSpecifier specifier)
|
|
=> Sys.LayerSetSprite((Owner, this), layer, specifier);
|
|
|
|
[Obsolete("Use SpriteSystem.LayerSetSprite() instead.")]
|
|
public void LayerSetSprite(object layerKey, SpriteSpecifier specifier)
|
|
{
|
|
if (!LayerMapTryGet(layerKey, out var layer, true))
|
|
return;
|
|
|
|
LayerSetSprite(layer, specifier);
|
|
}
|
|
|
|
[Obsolete("Use SpriteSystem.LayerSetTexture() instead.")]
|
|
public void LayerSetTexture(int layer, Texture? texture)
|
|
=> Sys.LayerSetTexture((Owner, this), layer, texture);
|
|
|
|
[Obsolete("Use SpriteSystem.LayerSetTexture() instead.")]
|
|
public void LayerSetTexture(object layerKey, Texture texture)
|
|
{
|
|
if (!LayerMapTryGet(layerKey, out var layer, true))
|
|
return;
|
|
|
|
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)
|
|
=> Sys.LayerSetTexture((Owner, this), layer, texturePath);
|
|
|
|
[Obsolete("Use SpriteSystem.LayerSetTexture() instead.")]
|
|
public void LayerSetTexture(object layerKey, ResPath texturePath)
|
|
{
|
|
if (!LayerMapTryGet(layerKey, out var layer, true))
|
|
return;
|
|
|
|
LayerSetTexture(layer, texturePath);
|
|
}
|
|
|
|
[Obsolete("Use SpriteSystem.LayerSetRsiState() instead.")]
|
|
public void LayerSetState(int layer, RSI.StateId stateId)
|
|
=> 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))
|
|
return;
|
|
|
|
LayerSetState(layer, stateId);
|
|
}
|
|
|
|
[Obsolete("Use SpriteSystem.LayerSetRsi() instead.")]
|
|
public void LayerSetState(int layer, RSI.StateId stateId, RSI? rsi)
|
|
=> 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))
|
|
return;
|
|
|
|
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)
|
|
=> 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))
|
|
return;
|
|
|
|
LayerSetState(layer, stateId, rsiPath);
|
|
}
|
|
|
|
[Obsolete("Use SpriteSystem.LayerSetRsi() instead.")]
|
|
public void LayerSetRSI(int layer, RSI? rsi)
|
|
=> 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))
|
|
return;
|
|
|
|
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)
|
|
=> 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))
|
|
return;
|
|
|
|
LayerSetRSI(layer, rsiPath);
|
|
}
|
|
|
|
[Obsolete("Use SpriteSystem.LayerSetScale() instead.")]
|
|
public void LayerSetScale(int layer, Vector2 scale)
|
|
=> 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))
|
|
return;
|
|
|
|
LayerSetScale(layer, scale);
|
|
}
|
|
|
|
[Obsolete("Use SpriteSystem.LayerSetRotation() instead.")]
|
|
public void LayerSetRotation(int layer, Angle rotation)
|
|
=> 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))
|
|
return;
|
|
|
|
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);
|
|
|
|
[Obsolete("Use SpriteSystem.LayerSetVisible() instead.")]
|
|
public void LayerSetVisible(object layerKey, bool visible)
|
|
{
|
|
if (!LayerMapTryGet(layerKey, out var layer, true))
|
|
return;
|
|
|
|
LayerSetVisible(layer, visible);
|
|
}
|
|
|
|
[Obsolete("Use SpriteSystem.LayerSetColor() instead.")]
|
|
public void LayerSetColor(int layer, Color color)
|
|
=> 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))
|
|
return;
|
|
|
|
LayerSetColor(layer, color);
|
|
}
|
|
|
|
[Obsolete("Use SpriteSystem.LayerSetDirOffset() instead.")]
|
|
public void LayerSetDirOffset(int layer, DirectionOffset offset)
|
|
=> 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))
|
|
return;
|
|
|
|
LayerSetDirOffset(layer, offset);
|
|
}
|
|
|
|
[Obsolete("Use SpriteSystem.LayerSetAnimationTime() instead.")]
|
|
public void LayerSetAnimationTime(int layer, float 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))
|
|
return;
|
|
|
|
LayerSetAnimationTime(layer, animationTime);
|
|
}
|
|
|
|
[Obsolete("Use SpriteSystem.LayerSetAutoAnimated() instead.")]
|
|
public void LayerSetAutoAnimated(int layer, bool 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))
|
|
return;
|
|
|
|
LayerSetAutoAnimated(layer, autoAnimated);
|
|
}
|
|
|
|
[Obsolete("Use SpriteSystem.LayerSetRenderingStrategy() instead.")]
|
|
public void LayerSetRenderingStrategy(int layer, LayerRenderingStrategy 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))
|
|
return;
|
|
|
|
LayerSetRenderingStrategy(layer, renderingStrategy);
|
|
}
|
|
|
|
[Obsolete("Use SpriteSystem.LayerGetRsiState() instead.")]
|
|
public RSI.StateId LayerGetState(int layer)
|
|
{
|
|
if (!TryGetLayer(layer, out var theLayer, true))
|
|
return default;
|
|
|
|
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;
|
|
}
|
|
|
|
public void LayerSetShader(int layer, ShaderInstance? shader, string? prototype = null)
|
|
{
|
|
if (!TryGetLayer(layer, out var theLayer, true))
|
|
return;
|
|
|
|
if (shader == null)
|
|
{
|
|
theLayer.UnShaded = false;
|
|
theLayer.Shader = null;
|
|
theLayer.ShaderPrototype = null;
|
|
return;
|
|
}
|
|
|
|
if (prototype == SpriteSystem.UnshadedId.Id)
|
|
{
|
|
theLayer.UnShaded = true;
|
|
theLayer.ShaderPrototype = SpriteSystem.UnshadedId;
|
|
theLayer.Shader = null;
|
|
return;
|
|
}
|
|
|
|
theLayer.UnShaded = false;
|
|
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<ShaderPrototype>(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
|
|
[Obsolete("Use SpriteSystem.Render() instead.")]
|
|
public void Render(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Direction? overrideDirection = null, Vector2 position = default)
|
|
{
|
|
Sys.RenderSprite((Owner, this), drawingHandle, eyeRotation, worldRotation, position, overrideDirection);
|
|
}
|
|
|
|
[Obsolete("Use SpriteSystem.LayerGetDirectionCount() instead.")]
|
|
public int GetLayerDirectionCount(ISpriteLayer layer)
|
|
{
|
|
if (!layer.RsiState.IsValid)
|
|
return 1;
|
|
|
|
// Pull texture from RSI state instead.
|
|
var rsi = layer.Rsi ?? BaseRSI;
|
|
if (rsi == null || !rsi.TryGetState(layer.RsiState, out var state))
|
|
{
|
|
state = Sys.GetFallbackState();
|
|
}
|
|
|
|
return state.RsiDirections switch
|
|
{
|
|
RsiDirectionType.Dir1 => 1,
|
|
RsiDirectionType.Dir4 => 4,
|
|
RsiDirectionType.Dir8 => 8,
|
|
_ => throw new ArgumentOutOfRangeException()
|
|
};
|
|
}
|
|
|
|
public string GetDebugString()
|
|
{
|
|
var builder = new StringBuilder();
|
|
builder.Append(
|
|
$"vis/depth/scl/rot/ofs/col/norot/override: {Visible}/{DrawDepth}/{Scale}/{Rotation}/{Offset}/{Color}/{NoRotation}/{DirectionOverride}/\n"
|
|
);
|
|
|
|
foreach (var layer in Layers)
|
|
{
|
|
builder.AppendFormat(
|
|
"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.State,
|
|
layer.AnimationTimeLeft,
|
|
layer.AnimationFrame,
|
|
layer.Scale,
|
|
layer.Rotation,
|
|
layer.Visible,
|
|
layer.Color,
|
|
layer.DirOffset,
|
|
layer.RenderingStrategy
|
|
);
|
|
}
|
|
|
|
return builder.ToString();
|
|
}
|
|
|
|
[Obsolete("Use SpriteSystem.CalculateBounds() instead.")]
|
|
public Box2Rotated CalculateRotatedBoundingBox(Vector2 worldPosition, Angle worldRotation, Angle eyeRot)
|
|
{
|
|
return Sys.CalculateBounds((Owner, this), worldPosition, worldRotation, eyeRot);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enum to "offset" a cardinal direction.
|
|
/// </summary>
|
|
public enum DirectionOffset : byte
|
|
{
|
|
/// <summary>
|
|
/// No offset.
|
|
/// </summary>
|
|
None = 0,
|
|
|
|
/// <summary>
|
|
/// Rotate direction clockwise. (North -> East, etc...)
|
|
/// </summary>
|
|
Clockwise = 1,
|
|
|
|
/// <summary>
|
|
/// Rotate direction counter-clockwise. (North -> West, etc...)
|
|
/// </summary>
|
|
CounterClockwise = 2,
|
|
|
|
/// <summary>
|
|
/// Rotate direction 180 degrees, so flip. (North -> South, etc...)
|
|
/// </summary>
|
|
Flip = 3,
|
|
}
|
|
|
|
public sealed class Layer : ISpriteLayer, ISerializationHooks
|
|
{
|
|
internal SpriteComponent _parent => Owner.Comp;
|
|
|
|
/// <summary>
|
|
/// The entity that this layer belongs to.
|
|
/// </summary>
|
|
[Access(typeof(SpriteSystem), typeof(SpriteComponent))]
|
|
[ViewVariables] internal Entity<SpriteComponent> 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.
|
|
|
|
/// <summary>
|
|
/// The index of the layer within its layer collection (usually a SpriteComponent).
|
|
/// </summary>
|
|
[Access(typeof(SpriteSystem), typeof(SpriteComponent))]
|
|
[ViewVariables] internal int Index;
|
|
// Internal, because I might want to change this in future W/O breaking changes.
|
|
|
|
[ViewVariables] public ProtoId<ShaderPrototype>? ShaderPrototype;
|
|
[ViewVariables] public ShaderInstance? Shader;
|
|
[ViewVariables] public Texture? Texture;
|
|
|
|
internal RSI? _rsi;
|
|
|
|
/// <summary>
|
|
/// If true, then this layer is drawn without lighting applied.
|
|
/// Unshaded layers are given special treatment and don't just use the unshaded-shader to avoid having to
|
|
/// unnecessarily swap out the light texture. This helps the number of batches that need to be sent to the
|
|
/// GPU while drawing sprites.
|
|
/// </summary>
|
|
[ViewVariables] internal bool UnShaded;
|
|
|
|
[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();
|
|
}
|
|
}
|
|
|
|
internal RSI.StateId StateId;
|
|
[ViewVariables] public RSI.StateId State
|
|
{
|
|
get => StateId;
|
|
[Obsolete("Use SpriteSystem.LayerSetRsiState() instead.")]
|
|
set
|
|
{
|
|
if (StateId == value)
|
|
return;
|
|
|
|
StateId = value;
|
|
UpdateActualState();
|
|
}
|
|
}
|
|
|
|
[ViewVariables] public float AnimationTimeLeft;
|
|
[ViewVariables] public float AnimationTime;
|
|
[ViewVariables] public int AnimationFrame;
|
|
|
|
/// <summary>
|
|
/// Is the animation currently playing in reverse.
|
|
/// </summary>
|
|
[ViewVariables] public bool Reversed { get; internal set; }
|
|
|
|
/// <summary>
|
|
/// If every animation delay finishes do we reverse it.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Only applies if the state is auto-animated.
|
|
/// </remarks>
|
|
[ViewVariables] public bool Cycle;
|
|
|
|
/// <summary>
|
|
/// If false, this will prevent the layer's animation from looping.
|
|
/// This will set <see cref="AutoAnimated"/> to false once the animation finishes.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This may be overriden by the parent's loop property.
|
|
/// </remarks>
|
|
[ViewVariables] public bool Loop = true;
|
|
|
|
// 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;
|
|
|
|
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: {_parent.entities.ToPrettyString(_parent.Owner)}. Scale: {value}");
|
|
return;
|
|
}
|
|
|
|
_scale = value;
|
|
UpdateLocalMatrix();
|
|
BoundsDirty = true;
|
|
Owner.Comp.BoundsDirty = true;
|
|
Owner.Comp.TreeSys.QueueTreeUpdate(Owner);
|
|
}
|
|
}
|
|
internal Vector2 _scale = Vector2.One;
|
|
|
|
[ViewVariables(VVAccess.ReadWrite)]
|
|
public Angle Rotation
|
|
{
|
|
get => _rotation;
|
|
[Obsolete("Use SpriteSystem.LayerSetRotation() instead.")]
|
|
set
|
|
{
|
|
if (_rotation.EqualsApprox(value)) return;
|
|
|
|
_rotation = value;
|
|
UpdateLocalMatrix();
|
|
BoundsDirty = true;
|
|
Owner.Comp.BoundsDirty = true;
|
|
Owner.Comp.TreeSys.QueueTreeUpdate(Owner);
|
|
}
|
|
}
|
|
internal Angle _rotation = Angle.Zero;
|
|
|
|
// Is the layer actually drawn / does it contribute to the sprites bounding box?
|
|
internal bool Drawn => _visible && !Blank && CopyToShaderParameters == null;
|
|
|
|
internal bool _visible = true;
|
|
[ViewVariables(VVAccess.ReadWrite)]
|
|
public bool Visible
|
|
{
|
|
get => _visible;
|
|
[Obsolete("Use SpriteSystem.LayerSetVisible() instead.")]
|
|
set
|
|
{
|
|
if (_visible == value)
|
|
return;
|
|
_visible = value;
|
|
|
|
Owner.Comp.BoundsDirty = true;
|
|
|
|
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
|
|
if (_parent.Owner != EntityUid.Invalid)
|
|
Owner.Comp.Sys?.QueueUpdateIsInert(Owner);
|
|
|
|
if (_parent.Owner != EntityUid.Invalid)
|
|
Owner.Comp.TreeSys.QueueTreeUpdate(Owner);
|
|
}
|
|
}
|
|
|
|
[ViewVariables]
|
|
public bool Blank => !State.IsValid && Texture == null;
|
|
|
|
[ViewVariables(VVAccess.ReadWrite)]
|
|
public Color Color { get; set; } = Color.White;
|
|
|
|
internal bool _autoAnimated = true;
|
|
[ViewVariables(VVAccess.ReadWrite)]
|
|
public bool AutoAnimated
|
|
{
|
|
get => _autoAnimated;
|
|
[Obsolete("Use SpriteSystem.LayerSetAutoAnimated() instead.")]
|
|
set
|
|
{
|
|
if (_autoAnimated == value)
|
|
return;
|
|
_autoAnimated = value;
|
|
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
|
|
if (_parent.Owner != EntityUid.Invalid)
|
|
_parent.Sys?.QueueUpdateIsInert((_parent.Owner, _parent));
|
|
}
|
|
}
|
|
|
|
[ViewVariables(VVAccess.ReadWrite)]
|
|
public Vector2 Offset
|
|
{
|
|
get => _offset;
|
|
[Obsolete("Use SpriteSystem.LayerSetOffset() instead.")]
|
|
set
|
|
{
|
|
if (_offset.EqualsApprox(value)) return;
|
|
|
|
BoundsDirty = true;
|
|
Owner.Comp.BoundsDirty = true;
|
|
|
|
_offset = value;
|
|
UpdateLocalMatrix();
|
|
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
|
|
if (_parent.Owner != EntityUid.Invalid)
|
|
Owner.Comp.TreeSys.QueueTreeUpdate(Owner);
|
|
}
|
|
}
|
|
|
|
internal Vector2 _offset;
|
|
|
|
[ViewVariables]
|
|
public DirectionOffset DirOffset { get; set; }
|
|
|
|
[ViewVariables]
|
|
public RSI? ActualRsi => RSI ?? _parent.BaseRSI;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
[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 change the sprites bounds.
|
|
[ViewVariables(VVAccess.ReadWrite)]
|
|
public CopyToShaderParameters? CopyToShaderParameters;
|
|
|
|
[Obsolete("Use SpriteSystem.AddBlankLayer")]
|
|
public Layer(SpriteComponent parent)
|
|
{
|
|
Owner = (parent.Owner, parent);
|
|
}
|
|
|
|
internal Layer()
|
|
{
|
|
}
|
|
|
|
internal Layer(Entity<SpriteComponent> owner, int index)
|
|
{
|
|
Owner = owner;
|
|
Index = index;
|
|
}
|
|
|
|
[Obsolete] // This should be internal to SpriteSystem
|
|
public Layer(Layer toClone, SpriteComponent parent) : this(parent)
|
|
{
|
|
if (toClone.Shader != null)
|
|
Shader = toClone.Shader.Mutable ? toClone.Shader.Duplicate() : toClone.Shader;
|
|
|
|
UnShaded = toClone.UnShaded;
|
|
ShaderPrototype = toClone.ShaderPrototype;
|
|
Texture = toClone.Texture;
|
|
RSI = toClone.RSI;
|
|
State = toClone.State;
|
|
AnimationTimeLeft = toClone.AnimationTimeLeft;
|
|
AnimationTime = toClone.AnimationTime;
|
|
AnimationFrame = toClone.AnimationFrame;
|
|
_scale = toClone.Scale;
|
|
_rotation = toClone.Rotation;
|
|
_offset = toClone.Offset;
|
|
UpdateLocalMatrix();
|
|
_visible = toClone._visible;
|
|
Color = toClone.Color;
|
|
DirOffset = toClone.DirOffset;
|
|
_autoAnimated = toClone._autoAnimated;
|
|
RenderingStrategy = toClone.RenderingStrategy;
|
|
Cycle = toClone.Cycle;
|
|
Loop = toClone.Loop;
|
|
if (toClone.CopyToShaderParameters is { } copyToShaderParameters)
|
|
CopyToShaderParameters = new CopyToShaderParameters(copyToShaderParameters);
|
|
}
|
|
|
|
// TODO SPRITE
|
|
// Is Layer even serializable?
|
|
void ISerializationHooks.AfterDeserialization()
|
|
{
|
|
UpdateLocalMatrix();
|
|
}
|
|
|
|
internal void UpdateLocalMatrix()
|
|
{
|
|
LocalMatrix = Matrix3Helpers.CreateTransform(in _offset, in _rotation, in _scale);
|
|
}
|
|
|
|
RSI? ISpriteLayer.Rsi { get => RSI; set => SetRsi(value); }
|
|
RSI.StateId ISpriteLayer.RsiState { get => State; set => SetState(value); }
|
|
Texture? ISpriteLayer.Texture { get => Texture; set => SetTexture(value); }
|
|
|
|
public PrototypeLayerData ToPrototypeData()
|
|
{
|
|
return new PrototypeLayerData
|
|
{
|
|
Color = Color,
|
|
Rotation = Rotation,
|
|
Scale = Scale,
|
|
Shader = ShaderPrototype,
|
|
State = State.Name,
|
|
Visible = Visible,
|
|
RsiPath = RSI?.Path.CanonPath,
|
|
RenderingStrategy = RenderingStrategy,
|
|
//todo TexturePath = Textur
|
|
//todo MapKeys
|
|
};
|
|
}
|
|
|
|
float ISpriteLayer.AnimationTime
|
|
{
|
|
get => AnimationTime;
|
|
set => SetAnimationTime(value);
|
|
}
|
|
|
|
int ISpriteLayer.AnimationFrame => AnimationFrame;
|
|
|
|
public RsiDirection EffectiveDirection(Angle worldRotation)
|
|
{
|
|
if (State == default)
|
|
{
|
|
return default;
|
|
}
|
|
|
|
var rsi = ActualRsi;
|
|
if (rsi == null)
|
|
{
|
|
return default;
|
|
}
|
|
|
|
if (rsi.TryGetState(State, out var state))
|
|
{
|
|
return EffectiveDirection(state, worldRotation, null);
|
|
}
|
|
|
|
return default;
|
|
}
|
|
|
|
public RsiDirection EffectiveDirection(RSI.State state, Angle worldRotation,
|
|
Direction? overrideDirection)
|
|
{
|
|
if (state.RsiDirections == RsiDirectionType.Dir1)
|
|
{
|
|
return RsiDirection.South;
|
|
}
|
|
else
|
|
{
|
|
RsiDirection dir;
|
|
if (overrideDirection != null)
|
|
{
|
|
dir = overrideDirection.Value.Convert(state.RsiDirections);
|
|
}
|
|
else
|
|
{
|
|
dir = worldRotation.ToRsiDirection(state.RsiDirections);
|
|
}
|
|
|
|
return dir.OffsetRsiDir(DirOffset);
|
|
}
|
|
}
|
|
|
|
[Obsolete("Use SpriteSystem.LayerSetAnimationTime")]
|
|
public void SetAnimationTime(float animationTime)
|
|
{
|
|
if (!State.IsValid)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var theLayerRSI = ActualRsi;
|
|
if (theLayerRSI == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var state = theLayerRSI[State];
|
|
if (animationTime > AnimationTime)
|
|
{
|
|
// Handle advancing differently from going backwards.
|
|
AnimationTimeLeft -= (animationTime - 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.
|
|
AnimationTimeLeft = -animationTime + state.GetDelay(0);
|
|
AnimationFrame = 0;
|
|
}
|
|
|
|
AnimationTime = animationTime;
|
|
AdvanceFrameAnimation(state);
|
|
}
|
|
|
|
[Obsolete("Use SpriteSystem.LayerSetAutoAnimated")]
|
|
public void SetAutoAnimated(bool value)
|
|
{
|
|
AutoAnimated = value;
|
|
|
|
_parent.Sys.QueueUpdateIsInert((_parent.Owner, _parent));
|
|
}
|
|
|
|
[Obsolete("Use SpriteSystem.LayerSetRsi")]
|
|
public void SetRsi(RSI? rsi)
|
|
{
|
|
RSI = rsi;
|
|
if (!State.IsValid)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Gotta do this because somebody might use null as argument (totally valid).
|
|
var actualRsi = ActualRsi;
|
|
if (actualRsi == null)
|
|
{
|
|
Logger.ErrorS(LogCategory, "No RSI to pull new state from! Trace:\n{0}", Environment.StackTrace);
|
|
Texture = null;
|
|
}
|
|
else
|
|
{
|
|
if (actualRsi.TryGetState(State, out var state))
|
|
{
|
|
AnimationTimeLeft = state.GetDelay(0);
|
|
}
|
|
else
|
|
{
|
|
Logger.ErrorS(LogCategory, "State '{0}' does not exist in set RSI ({1}). Trace:\n{2}", State, rsi?.Path.CanonPath ?? "null",
|
|
Environment.StackTrace);
|
|
Texture = null;
|
|
}
|
|
}
|
|
|
|
BoundsDirty = true;
|
|
Owner.Comp.BoundsDirty = true;
|
|
|
|
_parent.TreeSys.QueueTreeUpdate((_parent.Owner, _parent));
|
|
_parent.Sys.QueueUpdateIsInert((_parent.Owner, _parent));
|
|
}
|
|
|
|
[Obsolete("Use SpriteSystem.LayerSetRsiState")]
|
|
public void SetState(RSI.StateId stateId)
|
|
{
|
|
if (State == stateId)
|
|
{
|
|
return;
|
|
}
|
|
|
|
State = stateId;
|
|
RSI.State? state;
|
|
var rsi = ActualRsi;
|
|
if (rsi == null)
|
|
{
|
|
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 = _parent.Sys.GetFallbackState();
|
|
Logger.ErrorS(LogCategory, "State '{0}' does not exist in RSI. Trace:\n{1}", stateId,
|
|
Environment.StackTrace);
|
|
}
|
|
}
|
|
|
|
AnimationFrame = 0;
|
|
AnimationTime = 0;
|
|
AnimationTimeLeft = state.GetDelay(0);
|
|
|
|
_parent.Sys.QueueUpdateIsInert((_parent.Owner, _parent));
|
|
}
|
|
|
|
[Obsolete("Use SpriteSystem.LayerSetTexture")]
|
|
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));
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public Vector2i PixelSize
|
|
{
|
|
get
|
|
{
|
|
var pixelSize = Vector2i.Zero;
|
|
if (Texture != null)
|
|
{
|
|
pixelSize = Texture.Size;
|
|
}
|
|
else if (ActualRsi != null)
|
|
{
|
|
pixelSize = ActualRsi.Size;
|
|
}
|
|
|
|
return pixelSize;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether or not the layers's local bounding box is dirty and need to be rebuilt.
|
|
/// </summary>
|
|
internal bool BoundsDirty = true;
|
|
internal Box2 Bounds;
|
|
|
|
[Obsolete("Use SpriteSystem.GetLocalBounds()")]
|
|
public Box2 CalculateBoundingBox() => Owner.Comp.Sys.GetLocalBounds(this);
|
|
|
|
/// <summary>
|
|
/// Update Cached RSI state. State is cached to avoid calling this every time an entity gets drawn.
|
|
/// </summary>
|
|
internal void UpdateActualState()
|
|
{
|
|
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
|
|
_parent.Sys?.QueueUpdateIsInert((_parent.Owner, _parent));
|
|
if (!State.IsValid)
|
|
{
|
|
_actualState = null;
|
|
return;
|
|
}
|
|
|
|
// Pull texture from RSI state
|
|
var rsi = RSI ?? _parent.BaseRSI;
|
|
if (rsi == null || !rsi.TryGetState(State, out _actualState))
|
|
{
|
|
_actualState = _parent.Sys?.GetFallbackState();
|
|
}
|
|
}
|
|
|
|
public void GetLayerDrawMatrix(RsiDirection dir, out Matrix3x2 layerDrawMatrix)
|
|
{
|
|
GetLayerDrawMatrix(dir, out layerDrawMatrix, Owner.Comp.NoRotation);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Given the apparent rotation of an entity on screen (world + eye rotation), get layer's matrix for drawing &
|
|
/// relevant RSI direction.
|
|
/// </summary>
|
|
internal void GetLayerDrawMatrix(RsiDirection dir, out Matrix3x2 layerDrawMatrix, bool noRot)
|
|
{
|
|
// 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);
|
|
}
|
|
|
|
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,
|
|
Matrix3Helpers.CreateRotation(-Direction.North.ToAngle()),
|
|
Matrix3Helpers.CreateRotation(-Direction.East.ToAngle()),
|
|
Matrix3Helpers.CreateRotation(-Direction.West.ToAngle()),
|
|
Matrix3Helpers.CreateRotation(-Direction.SouthEast.ToAngle()),
|
|
Matrix3Helpers.CreateRotation(-Direction.SouthWest.ToAngle()),
|
|
Matrix3Helpers.CreateRotation(-Direction.NorthEast.ToAngle()),
|
|
Matrix3Helpers.CreateRotation(-Direction.NorthWest.ToAngle())
|
|
};
|
|
|
|
/// <summary>
|
|
/// Converts an angle (between 0 and 2pi) to an RSI direction. This will slightly bias the angle to avoid flickering for
|
|
/// 4-directional sprites.
|
|
/// </summary>
|
|
public static RsiDirection GetDirection(RsiDirectionType dirType, Angle angle)
|
|
{
|
|
if (dirType == RsiDirectionType.Dir1)
|
|
return RsiDirection.South;
|
|
|
|
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
|
|
// angle to avoid the sprite flickering.
|
|
|
|
// mod is -0.5 for angles between 0-90 and 180-270, and +0.5 for 90-180 and 270-360
|
|
var mod = (Math.Floor(angle.Theta / MathHelper.PiOver2) % 2) - 0.5;
|
|
|
|
var modTheta = angle.Theta + mod * DirectionBias;
|
|
|
|
return ((int)Math.Round(modTheta / MathHelper.PiOver2) % 4) switch
|
|
{
|
|
0 => RsiDirection.South,
|
|
1 => RsiDirection.East,
|
|
2 => RsiDirection.North,
|
|
_ => RsiDirection.West,
|
|
};
|
|
}
|
|
|
|
internal void AdvanceFrameAnimation(RSI.State state)
|
|
{
|
|
var delayCount = state.DelayCount;
|
|
|
|
while (AnimationTimeLeft < 0)
|
|
{
|
|
if (Reversed)
|
|
{
|
|
AnimationFrame -= 1;
|
|
|
|
if (AnimationFrame < 0)
|
|
{
|
|
if (!Loop || !_parent.Loop)
|
|
{
|
|
// stop at first frame
|
|
AnimationFrame = 0;
|
|
AnimationTimeLeft = 0;
|
|
AutoAnimated = false;
|
|
return;
|
|
}
|
|
|
|
if (Cycle)
|
|
{
|
|
AnimationFrame = 1;
|
|
Reversed = false;
|
|
}
|
|
else
|
|
{
|
|
AnimationFrame = delayCount - 1;
|
|
}
|
|
|
|
AnimationTime = -AnimationTimeLeft;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AnimationFrame += 1;
|
|
|
|
if (AnimationFrame >= delayCount)
|
|
{
|
|
if (!Loop || !_parent.Loop)
|
|
{
|
|
// stop at last frame
|
|
AnimationFrame = delayCount - 1;
|
|
AnimationTimeLeft = 0;
|
|
AutoAnimated = false;
|
|
return;
|
|
}
|
|
|
|
if (Cycle)
|
|
{
|
|
AnimationFrame = delayCount - 2;
|
|
Reversed = true;
|
|
}
|
|
else
|
|
{
|
|
AnimationFrame = 0;
|
|
}
|
|
|
|
AnimationTime = -AnimationTimeLeft;
|
|
}
|
|
}
|
|
|
|
AnimationTimeLeft += state.GetDelay(AnimationFrame);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Instantiated version of <see cref="PrototypeCopyToShaderParameters"/>.
|
|
/// Has <see cref="LayerKey"/> actually resolved to a a real key.
|
|
/// </summary>
|
|
public sealed class CopyToShaderParameters(object layerKey)
|
|
{
|
|
public object LayerKey = layerKey;
|
|
public string? ParameterTexture;
|
|
public string? ParameterUV;
|
|
|
|
public CopyToShaderParameters(CopyToShaderParameters toClone) : this(toClone.LayerKey)
|
|
{
|
|
ParameterTexture = toClone.ParameterTexture;
|
|
ParameterUV = toClone.ParameterUV;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A post-shader applied to this sprite.
|
|
/// </summary>
|
|
[DataDefinition]
|
|
public sealed partial class PostShaderEntry
|
|
{
|
|
/// <summary>
|
|
/// Stable id used by systems to replace or remove their own post-shader.
|
|
/// </summary>
|
|
[DataField(required: true)]
|
|
public string Id = default!;
|
|
|
|
/// <summary>
|
|
/// Shader prototype to run for this post-shader pass.
|
|
/// </summary>
|
|
[DataField(required: true)]
|
|
public ProtoId<ShaderPrototype> Prototype;
|
|
|
|
/// <summary>
|
|
/// Whether to create a per-sprite mutable shader instance.
|
|
/// </summary>
|
|
[DataField]
|
|
public bool Mutable = true;
|
|
|
|
/// <summary>
|
|
/// Whether this post-shader needs the current viewport texture assigned as SCREEN_TEXTURE.
|
|
/// </summary>
|
|
[DataField]
|
|
public bool GetScreenTexture;
|
|
|
|
/// <summary>
|
|
/// Whether to raise BeforePostShaderRenderEvent before rendering this sprite.
|
|
/// </summary>
|
|
[DataField]
|
|
public bool RaiseShaderEvent;
|
|
|
|
/// <summary>
|
|
/// Post-shader ids that this entry should run before.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Missing ids are ignored.
|
|
/// </remarks>
|
|
[DataField]
|
|
public string[] Before = Array.Empty<string>();
|
|
|
|
/// <summary>
|
|
/// Post-shader ids that this entry should run after.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Missing ids are ignored.
|
|
/// </remarks>
|
|
[DataField]
|
|
public string[] After = Array.Empty<string>();
|
|
|
|
/// <summary>
|
|
/// Shader instance to run for this post-shader pass.
|
|
/// </summary>
|
|
public ShaderInstance Shader = default!;
|
|
|
|
/// <summary>
|
|
/// Original insertion position, used as the tie-breaker for unrelated post-shaders.
|
|
/// </summary>
|
|
internal int InsertionIndex;
|
|
|
|
/// <summary>
|
|
/// Creates a post-shader entry.
|
|
/// </summary>
|
|
private PostShaderEntry()
|
|
{
|
|
}
|
|
|
|
public PostShaderEntry(
|
|
string id,
|
|
ShaderInstance shader,
|
|
bool getScreenTexture = false,
|
|
bool raiseShaderEvent = false,
|
|
string[]? before = null,
|
|
string[]? after = null)
|
|
{
|
|
Id = id;
|
|
Shader = shader;
|
|
Mutable = shader.Mutable;
|
|
GetScreenTexture = getScreenTexture;
|
|
RaiseShaderEvent = raiseShaderEvent;
|
|
Before = before ?? Array.Empty<string>();
|
|
After = after ?? Array.Empty<string>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies a post-shader entry for sprite cloning.
|
|
/// </summary>
|
|
public PostShaderEntry(PostShaderEntry toClone)
|
|
{
|
|
Id = toClone.Id;
|
|
Prototype = toClone.Prototype;
|
|
Mutable = toClone.Mutable;
|
|
Shader = toClone.Shader.Mutable ? toClone.Shader.Duplicate() : toClone.Shader;
|
|
GetScreenTexture = toClone.GetScreenTexture;
|
|
RaiseShaderEvent = toClone.RaiseShaderEvent;
|
|
Before = toClone.Before;
|
|
After = toClone.After;
|
|
InsertionIndex = toClone.InsertionIndex;
|
|
}
|
|
}
|
|
|
|
// Use args over big list just to make changes easier and not breaking for callers.
|
|
public struct PostShaderArgs
|
|
{
|
|
/// <summary>
|
|
/// Stable id used to replace or remove this post-shader later.
|
|
/// </summary>
|
|
public string Id;
|
|
|
|
/// <summary>
|
|
/// Shader instance to run for this post-shader pass.
|
|
/// </summary>
|
|
public ShaderInstance Shader;
|
|
|
|
/// <summary>
|
|
/// Whether this post-shader needs the current viewport texture assigned as SCREEN_TEXTURE.
|
|
/// </summary>
|
|
public bool GetScreenTexture;
|
|
|
|
/// <summary>
|
|
/// Whether to raise BeforePostShaderRenderEvent before rendering this sprite.
|
|
/// </summary>
|
|
public bool RaiseShaderEvent;
|
|
|
|
/// <summary>
|
|
/// Post-shader ids that this entry should run before.
|
|
/// </summary>
|
|
public IEnumerable<string>? Before;
|
|
|
|
/// <summary>
|
|
/// Post-shader ids that this entry should run after.
|
|
/// </summary>
|
|
public IEnumerable<string>? After;
|
|
|
|
/// <summary>
|
|
/// Creates post-shader arguments with the required id and shader.
|
|
/// </summary>
|
|
public PostShaderArgs(string id, ShaderInstance shader)
|
|
{
|
|
Id = id;
|
|
Shader = shader;
|
|
}
|
|
}
|
|
|
|
void IAnimationProperties.SetAnimatableProperty(string name, object value)
|
|
{
|
|
if (!name.StartsWith("layer/"))
|
|
{
|
|
AnimationHelper.SetAnimatableProperty(this, name, value);
|
|
return;
|
|
}
|
|
|
|
var delimiter = name.IndexOf("/", 6, StringComparison.Ordinal);
|
|
var indexString = name.Substring(6, delimiter - 6);
|
|
var index = int.Parse(indexString, CultureInfo.InvariantCulture);
|
|
var layerProp = name.Substring(delimiter + 1);
|
|
|
|
switch (layerProp)
|
|
{
|
|
case "texture":
|
|
LayerSetTexture(index, (string)value);
|
|
return;
|
|
case "state":
|
|
LayerSetState(index, (string)value);
|
|
return;
|
|
case "color":
|
|
LayerSetColor(index, (Color)value);
|
|
return;
|
|
default:
|
|
throw new ArgumentException($"Unknown layer property '{layerProp}'");
|
|
}
|
|
}
|
|
|
|
public IRsiStateLike? Icon
|
|
{
|
|
get
|
|
{
|
|
if (Layers.Count == 0) return null;
|
|
|
|
var layer = Layers[0];
|
|
|
|
var texture = layer.Texture;
|
|
|
|
if (!layer.State.IsValid) return texture;
|
|
|
|
// Pull texture from RSI state instead.
|
|
var rsi = layer.RSI ?? BaseRSI;
|
|
if (rsi == null || !rsi.TryGetState(layer.State, out var state))
|
|
{
|
|
state = Sys?.GetFallbackState();
|
|
}
|
|
|
|
return state;
|
|
}
|
|
}
|
|
|
|
[Obsolete("Use SpriteSystem.GetPrototypeTextures() instead")]
|
|
public static IEnumerable<IDirectionalTextureProvider> GetPrototypeTextures(EntityPrototype prototype, IResourceCache resourceCache)
|
|
{
|
|
return GetPrototypeTextures(prototype, resourceCache, out var _);
|
|
}
|
|
|
|
[Obsolete("Use SpriteSystem.GetPrototypeTextures() instead")]
|
|
public static IEnumerable<IDirectionalTextureProvider> GetPrototypeTextures(EntityPrototype prototype, IResourceCache resourceCache, out bool noRot)
|
|
{
|
|
var sys = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SpriteSystem>();
|
|
return sys.GetPrototypeTextures(prototype, out noRot);
|
|
}
|
|
|
|
[Obsolete("Use SpriteSystem.GetPrototypeIcon() instead")]
|
|
public static IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
|
|
{
|
|
var sys = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SpriteSystem>();
|
|
return sys.GetPrototypeIcon(prototype);
|
|
}
|
|
}
|
|
}
|