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
{
///
/// 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 _spriteLayers 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.
///
///
/// To define a Stack Visualizer prototype insert the following
/// snippet (you can skip Appearance if already defined)
///
///
/// - type: Appearance
/// visuals:
/// - type: StackVisualizer
/// stackLayers:
/// - goldbar_10
/// - goldbar_20
/// - goldbar_30
///
///
///
/// Defining a stack visualizer with composable transparent layers
///
/// - type: StackVisualizer
/// composite: true
/// stackLayers:
/// - cigarette_1
/// - cigarette_2
/// - cigarette_3
/// - cigarette_4
/// - cigarette_5
/// - cigarette_6
///
///
///
///
[UsedImplicitly]
public sealed class StackVisualizer : AppearanceVisualizer
{
///
/// Default IconLayer stack.
///
private const int IconLayer = 0;
///
/// Sprite layers used in stack visualizer. Sprites first in layer correspond to lower stack states
/// e.g. _spriteLayers[0] is lower stack level than _spriteLayers[1].
///
[DataField("stackLayers")] private readonly List _spriteLayers = new();
///
/// Determines if the visualizer uses composite or non-composite layers for icons. Defaults to false.
///
///
/// -
/// false: they are opaque and mutually exclusive (e.g. sprites in a cable coil). Default value
///
/// -
/// true: they are transparent and thus layered one over another in ascending order first
///
///
///
///
[DataField("composite")] private bool _isComposite;
[DataField("sprite")] private ResPath? _spritePath;
[Obsolete("Subscribe to your component being initialised instead.")]
public override void InitializeEntity(EntityUid entity)
{
base.InitializeEntity(entity);
if (_isComposite
&& _spriteLayers.Count > 0
&& IoCManager.Resolve().TryGetComponent(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);
}
}
}
[Obsolete("Subscribe to AppearanceChangeEvent instead.")]
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
var entities = IoCManager.Resolve();
if (entities.TryGetComponent(component.Owner, out SpriteComponent? spriteComponent))
{
if (_isComposite)
{
ProcessCompositeSprites(component, spriteComponent);
}
else
{
ProcessOpaqueSprites(component, spriteComponent);
}
}
}
private void ProcessOpaqueSprites(AppearanceComponent component, SpriteComponent spriteComponent)
{
// Skip processing if no actual
if (!component.TryGetData(StackVisuals.Actual, out var actual)) return;
if (!component.TryGetData(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, SpriteComponent spriteComponent)
{
// If hidden, don't render any sprites
if (component.TryGetData(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(StackVisuals.Actual, out var actual)) return;
if (!component.TryGetData(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);
}
}
}
}