diff --git a/Content.Client/GameObjects/Components/StackComponent.cs b/Content.Client/GameObjects/Components/StackComponent.cs
index 5fe8997604..8b54c61130 100644
--- a/Content.Client/GameObjects/Components/StackComponent.cs
+++ b/Content.Client/GameObjects/Components/StackComponent.cs
@@ -1,6 +1,9 @@
-using Content.Client.UserInterface.Stylesheets;
+#nullable enable
+
+using Content.Client.UserInterface.Stylesheets;
using Content.Client.Utility;
using Content.Shared.GameObjects.Components;
+using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
@@ -15,6 +18,7 @@ namespace Content.Client.GameObjects.Components
public class StackComponent : SharedStackComponent, IItemStatus
{
[ViewVariables(VVAccess.ReadWrite)] private bool _uiUpdateNeeded;
+ [ComponentDependency] private readonly AppearanceComponent? _appearanceComponent = default!;
public Control MakeControl() => new StatusControl(this);
@@ -23,12 +27,30 @@ namespace Content.Client.GameObjects.Components
get => base.Count;
set
{
+ var valueChanged = value != Count;
base.Count = value;
+
+ if (valueChanged)
+ {
+ _appearanceComponent?.SetData(StackVisuals.Actual, Count);
+
+ }
_uiUpdateNeeded = true;
}
}
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ if (!Owner.Deleted)
+ {
+ _appearanceComponent?.SetData(StackVisuals.MaxCount, MaxCount);
+ _appearanceComponent?.SetData(StackVisuals.Hide, false);
+ }
+ }
+
private sealed class StatusControl : Control
{
private readonly StackComponent _parent;
@@ -58,4 +80,4 @@ namespace Content.Client.GameObjects.Components
}
}
}
-}
+}
\ No newline at end of file
diff --git a/Content.Client/GameObjects/Components/StackVisualizer.cs b/Content.Client/GameObjects/Components/StackVisualizer.cs
new file mode 100644
index 0000000000..d409e79f0f
--- /dev/null
+++ b/Content.Client/GameObjects/Components/StackVisualizer.cs
@@ -0,0 +1,174 @@
+#nullable enable
+
+using System.Collections.Generic;
+using Content.Shared.GameObjects.Components;
+using Content.Shared.Utility;
+using JetBrains.Annotations;
+using Robust.Client.GameObjects;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Utility;
+using YamlDotNet.RepresentationModel;
+
+namespace Content.Client.GameObjects.Components
+{
+ ///
+ /// 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 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].
+ ///
+ 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 wire coil). Default value
+ ///
+ /// -
+ /// true: they are transparent and thus layered one over another in ascending order first
+ ///
+ ///
+ ///
+ ///
+ private bool _isComposite;
+
+ public override void LoadData(YamlMappingNode mapping)
+ {
+ base.LoadData(mapping);
+
+ if (mapping.TryGetNode("stackLayers", out var spriteSequenceNode))
+ {
+ foreach (var yamlNode in spriteSequenceNode)
+ {
+ _spriteLayers.Add(((YamlScalarNode) yamlNode).Value!);
+ }
+ }
+
+ if (mapping.TryGetNode("composite", out var transparent))
+ {
+ _isComposite = transparent.AsBool();
+ }
+ }
+
+ public override void InitializeEntity(IEntity entity)
+ {
+ base.InitializeEntity(entity);
+
+ if (_isComposite
+ && _spriteLayers.Count > 0
+ && entity.TryGetComponent(out var spriteComponent))
+ {
+ foreach (var sprite in _spriteLayers)
+ {
+ var rsiPath = spriteComponent.BaseRSI!.Path!;
+ spriteComponent.LayerMapReserveBlank(sprite);
+ spriteComponent.LayerSetSprite(sprite, new SpriteSpecifier.Rsi(rsiPath, sprite));
+ spriteComponent.LayerSetVisible(sprite, false);
+ }
+ }
+ }
+
+ public override void OnChangeData(AppearanceComponent component)
+ {
+ base.OnChangeData(component);
+
+ if (component.Owner.TryGetComponent(out var 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(StackVisuals.Actual, out var actual)) return;
+ if (!component.TryGetData(StackVisuals.MaxCount, out var maxCount))
+ {
+ maxCount = _spriteLayers.Count;
+ }
+
+ var activeLayer = ContentHelpers.RoundToNearestLevels(actual, maxCount, _spriteLayers.Count - 1);
+ spriteComponent.LayerSetState(IconLayer, _spriteLayers[activeLayer]);
+ }
+
+ private void ProcessCompositeSprites(AppearanceComponent component, ISpriteComponent 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);
+ }
+ }
+ }
+}
diff --git a/Content.Client/GameObjects/Components/Storage/BagOpenCloseVisualizer.cs b/Content.Client/GameObjects/Components/Storage/BagOpenCloseVisualizer.cs
new file mode 100644
index 0000000000..035fe4e6ce
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Storage/BagOpenCloseVisualizer.cs
@@ -0,0 +1,68 @@
+#nullable enable
+
+using Content.Shared.GameObjects.Components;
+using JetBrains.Annotations;
+using Robust.Client.GameObjects;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Log;
+using Robust.Shared.Utility;
+using YamlDotNet.RepresentationModel;
+
+namespace Content.Client.GameObjects.Components.Storage
+{
+ [UsedImplicitly]
+ public class BagOpenCloseVisualizer : AppearanceVisualizer
+ {
+ private const string OpenIcon = "openIcon";
+ private string? _openIcon;
+
+ public override void LoadData(YamlMappingNode node)
+ {
+ base.LoadData(node);
+
+ if (node.TryGetNode(OpenIcon, out var openIconNode))
+ {
+ _openIcon = openIconNode.Value;
+ }
+ else
+ {
+ Logger.Warning("BagOpenCloseVisualizer is useless with no `openIcon`");
+ }
+ }
+
+ public override void InitializeEntity(IEntity entity)
+ {
+ base.InitializeEntity(entity);
+
+ if (_openIcon != null && entity.TryGetComponent(out var spriteComponent))
+ {
+ var rsiPath = spriteComponent.BaseRSI!.Path!;
+ spriteComponent.LayerMapReserveBlank(OpenIcon);
+ spriteComponent.LayerSetSprite(OpenIcon, new SpriteSpecifier.Rsi(rsiPath, _openIcon));
+ spriteComponent.LayerSetVisible(OpenIcon, false);
+ }
+ }
+
+ public override void OnChangeData(AppearanceComponent component)
+ {
+ base.OnChangeData(component);
+
+ if (_openIcon != null
+ && component.Owner.TryGetComponent(out var spriteComponent))
+ {
+ if (component.TryGetData(SharedBagOpenVisuals.BagState, out var bagState))
+ {
+ switch (bagState)
+ {
+ case SharedBagState.Open:
+ spriteComponent.LayerSetVisible(OpenIcon, true);
+ break;
+ default:
+ spriteComponent.LayerSetVisible(OpenIcon, false);
+ break;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Content.Client/GameObjects/Components/Storage/ClientStorageComponent.cs b/Content.Client/GameObjects/Components/Storage/ClientStorageComponent.cs
index c00aee4f6f..fb5f9117fc 100644
--- a/Content.Client/GameObjects/Components/Storage/ClientStorageComponent.cs
+++ b/Content.Client/GameObjects/Components/Storage/ClientStorageComponent.cs
@@ -1,8 +1,9 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using Content.Client.Animations;
using Content.Client.GameObjects.Components.Items;
+using Content.Shared.GameObjects.Components;
using Content.Shared.GameObjects.Components.Storage;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Client.GameObjects;
@@ -29,9 +30,18 @@ namespace Content.Client.GameObjects.Components.Storage
private int StorageSizeUsed;
private int StorageCapacityMax;
private StorageWindow Window;
+ private SharedBagState _bagState;
public override IReadOnlyList StoredEntities => _storedEntities;
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ // Hide stackVisualizer on start
+ _bagState = SharedBagState.Close;
+ }
+
public override void OnAdd()
{
base.OnAdd();
@@ -69,12 +79,15 @@ namespace Content.Client.GameObjects.Components.Storage
//Updates what we are storing for the UI
case StorageHeldItemsMessage msg:
HandleStorageMessage(msg);
+ ChangeStorageVisualization(_bagState);
break;
//Opens the UI
case OpenStorageUIMessage _:
+ ChangeStorageVisualization(SharedBagState.Open);
ToggleUI();
break;
case CloseStorageUIMessage _:
+ ChangeStorageVisualization(SharedBagState.Close);
CloseUI();
break;
case AnimateInsertingEntitiesMessage msg:
@@ -119,6 +132,7 @@ namespace Content.Client.GameObjects.Components.Storage
private void ToggleUI()
{
if (Window.IsOpen)
+
Window.Close();
else
Window.Open();
@@ -128,6 +142,16 @@ namespace Content.Client.GameObjects.Components.Storage
{
Window.Close();
}
+
+ private void ChangeStorageVisualization(SharedBagState state)
+ {
+ _bagState = state;
+ if (Owner.TryGetComponent(out var appearanceComponent))
+ {
+ appearanceComponent.SetData(SharedBagOpenVisuals.BagState, state);
+ appearanceComponent.SetData(StackVisuals.Hide, state == SharedBagState.Close);
+ }
+ }
///
/// Function for clicking one of the stored entity buttons in the UI, tells server to remove that entity
diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs
index 72e69a76b0..2f97a9199a 100644
--- a/Content.Client/IgnoredComponents.cs
+++ b/Content.Client/IgnoredComponents.cs
@@ -164,6 +164,7 @@ namespace Content.Client
"Firelock",
"AtmosPlaque",
"Spillable",
+ "StorageCounter",
"SpaceVillainArcade",
"Flammable",
"CreamPie",
diff --git a/Content.Server/GameObjects/Components/Items/Storage/StorageCounterComponent.cs b/Content.Server/GameObjects/Components/Items/Storage/StorageCounterComponent.cs
new file mode 100644
index 0000000000..6454ff3147
--- /dev/null
+++ b/Content.Server/GameObjects/Components/Items/Storage/StorageCounterComponent.cs
@@ -0,0 +1,86 @@
+#nullable enable
+
+using System.Collections.Generic;
+using Content.Shared.GameObjects.Components;
+using Content.Shared.GameObjects.Components.Tag;
+using Robust.Server.GameObjects;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Log;
+using Robust.Shared.Serialization;
+
+namespace Content.Server.GameObjects.Components.Items.Storage
+{
+ ///
+ /// Storage that spawns and counts a single item.
+ /// Usually used for things like matchboxes, cigarette packs,
+ /// cigar cases etc.
+ ///
+ ///
+ /// - type: StorageCounter
+ /// amount: 6 # Note: this field can be omitted
+ /// countTag: Cigarette # Note: field doesn't point to entity Id, but its tag
+ ///
+ [RegisterComponent]
+ public class StorageCounterComponent : Component
+ {
+ private string? _countTag;
+ private int? _maxAmount;
+
+ ///
+ /// Single item storage component usually have an attached StackedVisualizer.
+ ///
+ [ComponentDependency] private readonly AppearanceComponent? _appearanceComponent = default;
+
+ public override string Name => "StorageCounter";
+
+ public override void ExposeData(ObjectSerializer serializer)
+ {
+ base.ExposeData(serializer);
+
+ serializer.DataField(ref _countTag, "countTag", null);
+ if (_countTag == null)
+ {
+ Logger.Warning("StorageCounterComponent without a `countTag` is useless");
+ }
+ serializer.DataField(ref _maxAmount, "amount", null);
+ }
+
+ public override void HandleMessage(ComponentMessage message, IComponent? component)
+ {
+ base.HandleMessage(message, component);
+
+ if (_appearanceComponent != null)
+ {
+ switch (message)
+ {
+ case ContainerContentsModifiedMessage msg:
+ var actual = Count(msg.Container.ContainedEntities);
+ _appearanceComponent.SetData(StackVisuals.Actual, actual);
+ if (_maxAmount != null)
+ {
+ _appearanceComponent.SetData(StackVisuals.MaxCount, _maxAmount);
+ }
+
+ break;
+ }
+ }
+ }
+
+ private int Count(IReadOnlyList containerContainedEntities)
+ {
+ var count = 0;
+ if (_countTag != null)
+ {
+ foreach (var entity in containerContainedEntities)
+ {
+ if (entity.HasTag(_countTag))
+ {
+ count++;
+ }
+ }
+ }
+
+ return count;
+ }
+ }
+}
diff --git a/Content.Shared/GameObjects/Components/SharedBagOpenVisuals.cs b/Content.Shared/GameObjects/Components/SharedBagOpenVisuals.cs
new file mode 100644
index 0000000000..3b2178900a
--- /dev/null
+++ b/Content.Shared/GameObjects/Components/SharedBagOpenVisuals.cs
@@ -0,0 +1,19 @@
+#nullable enable
+using System;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.GameObjects.Components
+{
+ [Serializable, NetSerializable]
+ public enum SharedBagOpenVisuals : byte
+ {
+ BagState,
+ }
+
+ [Serializable, NetSerializable]
+ public enum SharedBagState : byte
+ {
+ Open,
+ Close,
+ }
+}
diff --git a/Content.Shared/GameObjects/Components/StackVisuals.cs b/Content.Shared/GameObjects/Components/StackVisuals.cs
new file mode 100644
index 0000000000..30d9d3ae44
--- /dev/null
+++ b/Content.Shared/GameObjects/Components/StackVisuals.cs
@@ -0,0 +1,21 @@
+#nullable enable
+using System;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.GameObjects.Components
+{
+ [Serializable, NetSerializable]
+ public enum StackVisuals : byte
+ {
+ ///
+ /// The amount of elements in the stack
+ ///
+ Actual,
+ ///
+ /// The total amount of elements in the stack. If unspecified, the visualizer assumes
+ /// its
+ ///
+ MaxCount,
+ Hide
+ }
+}
diff --git a/Content.Shared/Utility/ContentHelpers.cs b/Content.Shared/Utility/ContentHelpers.cs
index 69132a4082..772d549288 100644
--- a/Content.Shared/Utility/ContentHelpers.cs
+++ b/Content.Shared/Utility/ContentHelpers.cs
@@ -25,14 +25,17 @@ namespace Content.Shared.Utility
{
throw new ArgumentException("Levels must be greater than 0.", nameof(levels));
}
+
if (actual >= max)
{
return levels - 1;
}
+
if (actual <= 0)
{
return 0;
}
+
var toOne = actual / max;
double threshold;
if (levels % 2 == 0)
@@ -49,11 +52,11 @@ namespace Content.Shared.Utility
var preround = toOne * (levels - 1);
if (toOne <= threshold || levels <= 2)
{
- return (int)Math.Ceiling(preround);
+ return (int) Math.Ceiling(preround);
}
else
{
- return (int)Math.Floor(preround);
+ return (int) Math.Floor(preround);
}
}
@@ -81,28 +84,18 @@ namespace Content.Shared.Utility
{
throw new ArgumentException("Levels must be greater than 1.", nameof(levels));
}
+
if (actual >= max)
{
return levels;
}
+
if (actual <= 0)
{
return 0;
}
- double step = max / levels;
- int nearest = 0;
- double nearestDiff = actual;
- for (var i = 1; i <= levels; i++)
- {
- var diff = Math.Abs(actual - i * step);
- if (diff < nearestDiff)
- {
- nearestDiff = diff;
- nearest = i;
- }
- }
- return nearest;
+ return (int) Math.Round(actual / max * levels, MidpointRounding.AwayFromZero);
}
}
}
diff --git a/Content.Tests/Shared/Utility/ContentHelpers_Test.cs b/Content.Tests/Shared/Utility/ContentHelpers_Test.cs
index cf75f026df..186fde1db4 100644
--- a/Content.Tests/Shared/Utility/ContentHelpers_Test.cs
+++ b/Content.Tests/Shared/Utility/ContentHelpers_Test.cs
@@ -9,41 +9,78 @@ namespace Content.Tests.Shared.Utility
[TestOf(typeof(ContentHelpers))]
public class ContentHelpers_Test
{
+ public static readonly IEnumerable<(double val, double max, int levels, int expected)> TestData =
+ new (double, double, int, int)[]
+ {
+ // Testing odd level counts. These are easy.
+ (-1, 10, 5, 0),
+ (0, 10, 5, 0),
+ (0.01f, 10, 5, 1),
+ (1, 10, 5, 1),
+ (2, 10, 5, 1),
+ (2.5f, 10, 5, 1),
+ (2.51f, 10, 5, 2),
+ (3, 10, 5, 2),
+ (4, 10, 5, 2),
+ (5, 10, 5, 2),
+ (6, 10, 5, 2),
+ (7, 10, 5, 2),
+ (7.49f, 10, 5, 2),
+ (7.5f, 10, 5, 3),
+ (8, 10, 5, 3),
+ (9, 10, 5, 3),
+ (10, 10, 5, 4),
+ (11, 10, 5, 4),
- public static readonly IEnumerable<(double val, double max, int levels, int expected)> TestData = new(double, double, int, int)[]
- {
- // Testing odd level counts. These are easy.
- (-1, 10, 5, 0),
- ( 0, 10, 5, 0),
- ( 0.01f, 10, 5, 1),
- ( 1, 10, 5, 1),
- ( 2, 10, 5, 1),
- ( 2.5f, 10, 5, 1),
- ( 2.51f, 10, 5, 2),
- ( 3, 10, 5, 2),
- ( 4, 10, 5, 2),
- ( 5, 10, 5, 2),
- ( 6, 10, 5, 2),
- ( 7, 10, 5, 2),
- ( 7.49f, 10, 5, 2),
- ( 7.5f, 10, 5, 3),
- ( 8, 10, 5, 3),
- ( 9, 10, 5, 3),
- (10, 10, 5, 4),
- (11, 10, 5, 4),
+ // Even level counts though..
+ (1, 10, 6, 1),
+ (2, 10, 6, 1),
+ (3, 10, 6, 2),
+ (4, 10, 6, 2),
+ (5, 10, 6, 2),
+ (6, 10, 6, 3),
+ (7, 10, 6, 3),
+ (8, 10, 6, 4),
+ (9, 10, 6, 4),
+ (10, 10, 6, 5),
+ };
- // Even level counts though..
- ( 1, 10, 6, 1),
- ( 2, 10, 6, 1),
- ( 3, 10, 6, 2),
- ( 4, 10, 6, 2),
- ( 5, 10, 6, 2),
- ( 6, 10, 6, 3),
- ( 7, 10, 6, 3),
- ( 8, 10, 6, 4),
- ( 9, 10, 6, 4),
- (10, 10, 6, 5),
- };
+ public static readonly IEnumerable<(double val, double max, int levels, int expected)> TestNear =
+ new (double, double, int, int)[]
+ {
+ // Testing odd counts
+ (0, 5, 2, 0),
+ (1, 5, 2, 0),
+ (2, 5, 2, 1),
+ (3, 5, 2, 1),
+ (4, 5, 2, 2),
+ (5, 5, 2, 2),
+
+ // Testing even counts
+ (0, 6, 5, 0),
+ (1, 6, 5, 1),
+ (2, 6, 5, 2),
+ (3, 6, 5, 3),
+ (4, 6, 5, 3),
+ (5, 6, 5, 4),
+ (6, 6, 5, 5),
+
+ // Testing transparency disable use case
+ (0, 6, 6, 0),
+ (1, 6, 6, 1),
+ (2, 6, 6, 2),
+ (3, 6, 6, 3),
+ (4, 6, 6, 4),
+ (5, 6, 6, 5),
+ (6, 6, 6, 6),
+
+ // Testing edge cases
+ (0.1, 6, 5, 0),
+ (-32, 6, 5, 0),
+ (2.4, 6, 5, 2),
+ (2.5, 6, 5, 2),
+ (320, 6, 5, 5),
+ };
[Parallelizable]
[Test]
@@ -52,5 +89,13 @@ namespace Content.Tests.Shared.Utility
(double val, double max, int levels, int expected) = data;
Assert.That(ContentHelpers.RoundToLevels(val, max, levels), Is.EqualTo(expected));
}
+
+ [Parallelizable]
+ [Test]
+ public void TestNearest([ValueSource(nameof(TestNear))] (double val, double max, int size, int expected) data)
+ {
+ (double val, double max, int size, int expected) = data;
+ Assert.That(ContentHelpers.RoundToNearestLevels(val, max, size), Is.EqualTo(expected));
+ }
}
}
diff --git a/Resources/Prototypes/Entities/Objects/Consumable/fancy.yml b/Resources/Prototypes/Entities/Objects/Consumable/fancy.yml
index fcf5d0dc31..be305e3ff9 100644
--- a/Resources/Prototypes/Entities/Objects/Consumable/fancy.yml
+++ b/Resources/Prototypes/Entities/Objects/Consumable/fancy.yml
@@ -1,4 +1,6 @@
-- type: entity
+- type: Tag
+ id: Cigarette
+- type: entity
name: "Base Cigarette"
id: BaseCigarette
parent: BaseItem
@@ -13,6 +15,9 @@
Slots: [ mask ]
HeldPrefix: unlit
size: 1
+ - type: Tag
+ tags:
+ - Cigarette
- type: Smoking
duration: 30
- type: Appearance
@@ -33,10 +38,25 @@
components:
- type: Sprite
sprite: Objects/Consumable/Fancy/cigarettes.rsi
+ netsync: false
layers:
- state: cig
- type: StorageFill
contents:
- name: Cigarette
amount: 6
-
+ - type: StorageCounter
+ countTag: Cigarette
+ - type: Appearance
+ visuals:
+ - type: BagOpenCloseVisualizer
+ openIcon: cig_open
+ - type: StackVisualizer
+ composite: true
+ stackLayers:
+ - cigarette_1
+ - cigarette_2
+ - cigarette_3
+ - cigarette_4
+ - cigarette_5
+ - cigarette_6
diff --git a/Resources/Prototypes/Entities/Objects/Misc/material_stacks.yml b/Resources/Prototypes/Entities/Objects/Misc/material_stacks.yml
index c4aa4a1465..68dd39ce8b 100644
--- a/Resources/Prototypes/Entities/Objects/Misc/material_stacks.yml
+++ b/Resources/Prototypes/Entities/Objects/Misc/material_stacks.yml
@@ -4,9 +4,9 @@
abstract: true
parent: BaseItem
components:
- - type: Stack
- - type: Material
- - type: ItemStatus
+ - type: Stack
+ - type: Material
+ - type: ItemStatus
- type: entity
name: steel sheet
@@ -14,21 +14,21 @@
parent: MaterialStack
suffix: Full
components:
- - type: Material
- materials:
- - key: enum.MaterialKeys.Stack
- mat: steel
- - type: Stack
- stacktype: enum.StackType.Metal
- - type: Sprite
- sprite: Objects/Materials/sheets.rsi
- state: metal
- - type: Item
- sprite: Objects/Materials/sheets.rsi
- HeldPrefix: metal
- - type: FloorTile
- outputs:
- - underplating
+ - type: Material
+ materials:
+ - key: enum.MaterialKeys.Stack
+ mat: steel
+ - type: Stack
+ stacktype: enum.StackType.Metal
+ - type: Sprite
+ sprite: Objects/Materials/sheets.rsi
+ state: metal
+ - type: Item
+ sprite: Objects/Materials/sheets.rsi
+ HeldPrefix: metal
+ - type: FloorTile
+ outputs:
+ - underplating
- type: entity
id: SteelSheet1
@@ -36,9 +36,9 @@
parent: MetalStack
suffix: 1
components:
- - type: Stack
- stacktype: enum.StackType.Metal
- count: 1
+ - type: Stack
+ stacktype: enum.StackType.Metal
+ count: 1
- type: entity
name: glass sheet
@@ -46,18 +46,18 @@
parent: MaterialStack
suffix: Full
components:
- - type: Material
- materials:
- - key: enum.MaterialKeys.Stack
- mat: glass
- - type: Stack
- stacktype: enum.StackType.Glass
- - type: Sprite
- sprite: Objects/Materials/sheets.rsi
- state: glass
- - type: Item
- sprite: Objects/Materials/sheets.rsi
- HeldPrefix: glass
+ - type: Material
+ materials:
+ - key: enum.MaterialKeys.Stack
+ mat: glass
+ - type: Stack
+ stacktype: enum.StackType.Glass
+ - type: Sprite
+ sprite: Objects/Materials/sheets.rsi
+ state: glass
+ - type: Item
+ sprite: Objects/Materials/sheets.rsi
+ HeldPrefix: glass
- type: entity
id: GlassSheet1
@@ -65,9 +65,9 @@
parent: GlassStack
suffix: 1
components:
- - type: Stack
- stacktype: enum.StackType.Glass
- count: 1
+ - type: Stack
+ stacktype: enum.StackType.Glass
+ count: 1
- type: entity
name: Reinforced Glass
@@ -94,9 +94,9 @@
parent: rglass
suffix: 1
components:
- - type: Stack
- StackType: enum.StackType.ReinforcedGlass
- count: 1
+ - type: Stack
+ StackType: enum.StackType.ReinforcedGlass
+ count: 1
- type: entity
@@ -134,15 +134,23 @@
parent: MaterialStack
suffix: Full
components:
- - type: Material
- materials:
- - key: enum.MaterialKeys.Stack
- mat: gold
- - type: Stack
- stacktype: enum.StackType.Gold
- - type: Sprite
- sprite: Objects/Materials/materials.rsi
- state: goldbar_30
+ - type: Material
+ materials:
+ - key: enum.MaterialKeys.Stack
+ mat: gold
+ - type: Stack
+ stacktype: enum.StackType.Gold
+ - type: Sprite
+ sprite: Objects/Materials/materials.rsi
+ state: goldbar_30
+ netsync: false
+ - type: Appearance
+ visuals:
+ - type: StackVisualizer
+ stackLayers:
+ - goldbar_10
+ - goldbar_20
+ - goldbar_30
- type: entity
id: GoldStack1
@@ -150,11 +158,11 @@
parent: GoldStack
suffix: 1
components:
- - type: Sprite
- sprite: Objects/Materials/materials.rsi
- state: goldbar_10
- - type: Stack
- count: 1
+ - type: Sprite
+ sprite: Objects/Materials/materials.rsi
+ state: goldbar_10
+ - type: Stack
+ count: 1
- type: entity
name: plasma ore
diff --git a/Resources/Prototypes/Entities/Objects/Power/cable_coils.yml b/Resources/Prototypes/Entities/Objects/Power/cable_coils.yml
index 7ad05e8511..c7a9a2f11c 100644
--- a/Resources/Prototypes/Entities/Objects/Power/cable_coils.yml
+++ b/Resources/Prototypes/Entities/Objects/Power/cable_coils.yml
@@ -1,4 +1,3 @@
-
# If you're looking at the rsi for this file, you'll probably be confused why
# I didn't just use an alpha for most of this stuff. Well icons don't have the
# ability to have applied colors yet in GUIs. -Swept
@@ -10,14 +9,16 @@
name: cable stack
suffix: Full
components:
- - type: Stack
- stacktype: enum.StackType.Cable
- - type: Sprite
- sprite: Objects/Tools/cables.rsi
- - type: Item
- sprite: Objects/Tools/cables.rsi
- - type: WirePlacer
- - type: Clickable
+ - type: Stack
+ stacktype: enum.StackType.Cable
+ - type: Sprite
+ sprite: Objects/Tools/cables.rsi
+ netsync: false
+ - type: Item
+ sprite: Objects/Tools/cables.rsi
+ - type: WirePlacer
+ - type: Clickable
+
- type: entity
id: HVWireStack
@@ -25,16 +26,23 @@
name: HV cable coil
suffix: Full
components:
- - type: Stack
- stacktype: enum.StackType.HVCable
- - type: Sprite
- state: coilhv-30
- - type: Item
- size: 10
- HeldPrefix: coilhv
- - type: WirePlacer
- wirePrototypeID: HVWire
- blockingWireType: HighVoltage
+ - type: Stack
+ stacktype: enum.StackType.HVCable
+ - type: Sprite
+ state: coilhv-30
+ - type: Item
+ size: 10
+ HeldPrefix: coilhv
+ - type: WirePlacer
+ wirePrototypeID: HVWire
+ blockingWireType: HighVoltage
+ - type: Appearance
+ visuals:
+ - type: StackVisualizer
+ stackLayers:
+ - coilhv-10
+ - coilhv-20
+ - coilhv-30
- type: entity
parent: HVWireStack
@@ -42,7 +50,7 @@
suffix: 1
components:
- type: Sprite
- state: coilhv-10
+ state: coilhv-10
- type: Item
size: 3
- type: Stack
@@ -55,14 +63,21 @@
description: Low-Voltage stack of wires for connecting APCs to machines and other purposes.
suffix: Full
components:
- - type: Sprite
- state: coillv-30
- - type: Item
- size: 10
- HeldPrefix: coillv
- - type: WirePlacer
- wirePrototypeID: ApcExtensionCable
- blockingWireType: Apc
+ - type: Sprite
+ state: coillv-30
+ - type: Item
+ size: 10
+ HeldPrefix: coillv
+ - type: WirePlacer
+ wirePrototypeID: ApcExtensionCable
+ blockingWireType: Apc
+ - type: Appearance
+ visuals:
+ - type: StackVisualizer
+ stackLayers:
+ - coillv-10
+ - coillv-20
+ - coillv-30
- type: entity
parent: ApcExtensionCableStack
@@ -92,6 +107,13 @@
- type: WirePlacer
wirePrototypeID: MVWire
blockingWireType: MediumVoltage
+ - type: Appearance
+ visuals:
+ - type: StackVisualizer
+ stackLayers:
+ - coilmv-10
+ - coilmv-20
+ - coilmv-30
- type: entity
parent: MVWireStack
diff --git a/Resources/Textures/Objects/Consumable/Fancy/cigarettes.rsi/cigarette_1.png b/Resources/Textures/Objects/Consumable/Fancy/cigarettes.rsi/cigarette_1.png
new file mode 100644
index 0000000000..93f34c641c
Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Fancy/cigarettes.rsi/cigarette_1.png differ
diff --git a/Resources/Textures/Objects/Consumable/Fancy/cigarettes.rsi/cigarette_2.png b/Resources/Textures/Objects/Consumable/Fancy/cigarettes.rsi/cigarette_2.png
new file mode 100644
index 0000000000..ec90405507
Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Fancy/cigarettes.rsi/cigarette_2.png differ
diff --git a/Resources/Textures/Objects/Consumable/Fancy/cigarettes.rsi/cigarette_3.png b/Resources/Textures/Objects/Consumable/Fancy/cigarettes.rsi/cigarette_3.png
new file mode 100644
index 0000000000..7e3eb0c1b9
Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Fancy/cigarettes.rsi/cigarette_3.png differ
diff --git a/Resources/Textures/Objects/Consumable/Fancy/cigarettes.rsi/cigarette_4.png b/Resources/Textures/Objects/Consumable/Fancy/cigarettes.rsi/cigarette_4.png
new file mode 100644
index 0000000000..bf9361b9b5
Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Fancy/cigarettes.rsi/cigarette_4.png differ
diff --git a/Resources/Textures/Objects/Consumable/Fancy/cigarettes.rsi/cigarette_5.png b/Resources/Textures/Objects/Consumable/Fancy/cigarettes.rsi/cigarette_5.png
new file mode 100644
index 0000000000..6d0a066c52
Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Fancy/cigarettes.rsi/cigarette_5.png differ
diff --git a/Resources/Textures/Objects/Consumable/Fancy/cigarettes.rsi/cigarette_6.png b/Resources/Textures/Objects/Consumable/Fancy/cigarettes.rsi/cigarette_6.png
new file mode 100644
index 0000000000..53048fed17
Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Fancy/cigarettes.rsi/cigarette_6.png differ
diff --git a/Resources/Textures/Objects/Consumable/Fancy/cigarettes.rsi/meta.json b/Resources/Textures/Objects/Consumable/Fancy/cigarettes.rsi/meta.json
index 7837a45f44..a33f00be16 100644
--- a/Resources/Textures/Objects/Consumable/Fancy/cigarettes.rsi/meta.json
+++ b/Resources/Textures/Objects/Consumable/Fancy/cigarettes.rsi/meta.json
@@ -15,6 +15,24 @@
},
{
"name": "cig_open"
+ },
+ {
+ "name": "cigarette_1"
+ },
+ {
+ "name": "cigarette_2"
+ },
+ {
+ "name": "cigarette_3"
+ },
+ {
+ "name": "cigarette_4"
+ },
+ {
+ "name": "cigarette_5"
+ },
+ {
+ "name": "cigarette_6"
}
]
}