161 lines
5.8 KiB
C#
161 lines
5.8 KiB
C#
|
|
using System.Collections.Generic;
|
|
using Content.Shared.Rounding;
|
|
using Content.Shared.Stacks;
|
|
using JetBrains.Annotations;
|
|
using Robust.Client.GameObjects;
|
|
using Robust.Shared.GameObjects;
|
|
using Robust.Shared.IoC;
|
|
using Robust.Shared.Serialization.Manager.Attributes;
|
|
using Robust.Shared.Utility;
|
|
|
|
namespace Content.Client.Stack
|
|
{
|
|
/// <summary>
|
|
/// Visualizer for items that come in stacks and have different appearance
|
|
/// depending on the size of the stack. Visualizer can work by switching between different
|
|
/// icons in <c>_spriteLayers</c> or if the sprite layers are supposed to be composed as transparent layers.
|
|
/// The former behavior is default and the latter behavior can be defined in prototypes.
|
|
///
|
|
/// <example>
|
|
/// <para>To define a Stack Visualizer prototype insert the following
|
|
/// snippet (you can skip Appearance if already defined)
|
|
/// </para>
|
|
/// <code>
|
|
/// - type: Appearance
|
|
/// visuals:
|
|
/// - type: StackVisualizer
|
|
/// stackLayers:
|
|
/// - goldbar_10
|
|
/// - goldbar_20
|
|
/// - goldbar_30
|
|
/// </code>
|
|
/// </example>
|
|
/// <example>
|
|
/// <para>Defining a stack visualizer with composable transparent layers</para>
|
|
/// <code>
|
|
/// - type: StackVisualizer
|
|
/// composite: true
|
|
/// stackLayers:
|
|
/// - cigarette_1
|
|
/// - cigarette_2
|
|
/// - cigarette_3
|
|
/// - cigarette_4
|
|
/// - cigarette_5
|
|
/// - cigarette_6
|
|
/// </code>
|
|
/// </example>
|
|
/// <seealso cref="_spriteLayers"/>
|
|
/// </summary>
|
|
[UsedImplicitly]
|
|
public sealed class StackVisualizer : AppearanceVisualizer
|
|
{
|
|
/// <summary>
|
|
/// Default IconLayer stack.
|
|
/// </summary>
|
|
private const int IconLayer = 0;
|
|
|
|
/// <summary>
|
|
/// Sprite layers used in stack visualizer. Sprites first in layer correspond to lower stack states
|
|
/// e.g. <code>_spriteLayers[0]</code> is lower stack level than <code>_spriteLayers[1]</code>.
|
|
/// </summary>
|
|
[DataField("stackLayers")] private readonly List<string> _spriteLayers = new();
|
|
|
|
/// <summary>
|
|
/// Determines if the visualizer uses composite or non-composite layers for icons. Defaults to false.
|
|
///
|
|
/// <list type="bullet">
|
|
/// <item>
|
|
/// <description>false: they are opaque and mutually exclusive (e.g. sprites in a cable coil). <b>Default value</b></description>
|
|
/// </item>
|
|
/// <item>
|
|
/// <description>true: they are transparent and thus layered one over another in ascending order first</description>
|
|
/// </item>
|
|
/// </list>
|
|
///
|
|
/// </summary>
|
|
[DataField("composite")] private bool _isComposite;
|
|
|
|
[DataField("sprite")] private ResourcePath? _spritePath;
|
|
|
|
public override void InitializeEntity(EntityUid entity)
|
|
{
|
|
base.InitializeEntity(entity);
|
|
|
|
if (_isComposite
|
|
&& _spriteLayers.Count > 0
|
|
&& IoCManager.Resolve<IEntityManager>().TryGetComponent<ISpriteComponent?>(entity, out var spriteComponent))
|
|
{
|
|
var spritePath = _spritePath ?? spriteComponent.BaseRSI!.Path!;
|
|
|
|
foreach (var sprite in _spriteLayers)
|
|
{
|
|
spriteComponent.LayerMapReserveBlank(sprite);
|
|
spriteComponent.LayerSetSprite(sprite, new SpriteSpecifier.Rsi(spritePath, sprite));
|
|
spriteComponent.LayerSetVisible(sprite, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void OnChangeData(AppearanceComponent component)
|
|
{
|
|
base.OnChangeData(component);
|
|
|
|
var entities = IoCManager.Resolve<IEntityManager>();
|
|
if (entities.TryGetComponent(component.Owner, out ISpriteComponent? spriteComponent))
|
|
{
|
|
if (_isComposite)
|
|
{
|
|
ProcessCompositeSprites(component, spriteComponent);
|
|
}
|
|
else
|
|
{
|
|
ProcessOpaqueSprites(component, spriteComponent);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ProcessOpaqueSprites(AppearanceComponent component, ISpriteComponent spriteComponent)
|
|
{
|
|
// Skip processing if no actual
|
|
if (!component.TryGetData<int>(StackVisuals.Actual, out var actual)) return;
|
|
if (!component.TryGetData<int>(StackVisuals.MaxCount, out var maxCount))
|
|
{
|
|
maxCount = _spriteLayers.Count;
|
|
}
|
|
|
|
var activeLayer = ContentHelpers.RoundToEqualLevels(actual, maxCount, _spriteLayers.Count);
|
|
spriteComponent.LayerSetState(IconLayer, _spriteLayers[activeLayer]);
|
|
}
|
|
|
|
private void ProcessCompositeSprites(AppearanceComponent component, ISpriteComponent spriteComponent)
|
|
{
|
|
// If hidden, don't render any sprites
|
|
if (component.TryGetData<bool>(StackVisuals.Hide, out var hide)
|
|
&& hide)
|
|
{
|
|
foreach (var transparentSprite in _spriteLayers)
|
|
{
|
|
spriteComponent.LayerSetVisible(transparentSprite, false);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// Skip processing if no actual/maxCount
|
|
if (!component.TryGetData<int>(StackVisuals.Actual, out var actual)) return;
|
|
if (!component.TryGetData<int>(StackVisuals.MaxCount, out var maxCount))
|
|
{
|
|
maxCount = _spriteLayers.Count;
|
|
}
|
|
|
|
|
|
var activeTill = ContentHelpers.RoundToNearestLevels(actual, maxCount, _spriteLayers.Count);
|
|
for (var i = 0; i < _spriteLayers.Count; i++)
|
|
{
|
|
spriteComponent.LayerSetVisible(_spriteLayers[i], i < activeTill);
|
|
}
|
|
}
|
|
}
|
|
}
|