Stacked sprite visualizer (#3096)
* Add Stack Visualizer * Add cigarette pack resources Adds transparent layers for visualizing cigarettes * Add Bag Open/Close Visualizer So storage opened in inventory can have different icons when opened or closed. * Create a component that only enumerates single item Used for creating stuff like matchbox, or cigarettes. As a bonus. It will only update stack visualizer for that particullar item. * Refactoring stuff * Fix other usage of stack in Resources * Add docs * Apply suggestions from code review Apply metalgearsloth suggestions Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> * Applied suggestions from metalgearsloth * Changed SingleItemStorageComponent to StorageCounterComponent Difference. New component doesn't spawn items, merely counts them. * Refactored StackVisualizer * Fix breakage with master * Update Resources/Prototypes/Entities/Objects/Consumable/fancy.yml Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> * Update with MGS suggestions Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
This commit is contained in:
@@ -1,6 +1,9 @@
|
|||||||
using Content.Client.UserInterface.Stylesheets;
|
#nullable enable
|
||||||
|
|
||||||
|
using Content.Client.UserInterface.Stylesheets;
|
||||||
using Content.Client.Utility;
|
using Content.Client.Utility;
|
||||||
using Content.Shared.GameObjects.Components;
|
using Content.Shared.GameObjects.Components;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
@@ -15,6 +18,7 @@ namespace Content.Client.GameObjects.Components
|
|||||||
public class StackComponent : SharedStackComponent, IItemStatus
|
public class StackComponent : SharedStackComponent, IItemStatus
|
||||||
{
|
{
|
||||||
[ViewVariables(VVAccess.ReadWrite)] private bool _uiUpdateNeeded;
|
[ViewVariables(VVAccess.ReadWrite)] private bool _uiUpdateNeeded;
|
||||||
|
[ComponentDependency] private readonly AppearanceComponent? _appearanceComponent = default!;
|
||||||
|
|
||||||
public Control MakeControl() => new StatusControl(this);
|
public Control MakeControl() => new StatusControl(this);
|
||||||
|
|
||||||
@@ -23,12 +27,30 @@ namespace Content.Client.GameObjects.Components
|
|||||||
get => base.Count;
|
get => base.Count;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
|
var valueChanged = value != Count;
|
||||||
base.Count = value;
|
base.Count = value;
|
||||||
|
|
||||||
|
if (valueChanged)
|
||||||
|
{
|
||||||
|
_appearanceComponent?.SetData(StackVisuals.Actual, Count);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
_uiUpdateNeeded = true;
|
_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 sealed class StatusControl : Control
|
||||||
{
|
{
|
||||||
private readonly StackComponent _parent;
|
private readonly StackComponent _parent;
|
||||||
@@ -58,4 +80,4 @@ namespace Content.Client.GameObjects.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
174
Content.Client/GameObjects/Components/StackVisualizer.cs
Normal file
174
Content.Client/GameObjects/Components/StackVisualizer.cs
Normal file
@@ -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
|
||||||
|
{
|
||||||
|
/// <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 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>
|
||||||
|
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 wire 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>
|
||||||
|
private bool _isComposite;
|
||||||
|
|
||||||
|
public override void LoadData(YamlMappingNode mapping)
|
||||||
|
{
|
||||||
|
base.LoadData(mapping);
|
||||||
|
|
||||||
|
if (mapping.TryGetNode<YamlSequenceNode>("stackLayers", out var spriteSequenceNode))
|
||||||
|
{
|
||||||
|
foreach (var yamlNode in spriteSequenceNode)
|
||||||
|
{
|
||||||
|
_spriteLayers.Add(((YamlScalarNode) yamlNode).Value!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mapping.TryGetNode<YamlScalarNode>("composite", out var transparent))
|
||||||
|
{
|
||||||
|
_isComposite = transparent.AsBool();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void InitializeEntity(IEntity entity)
|
||||||
|
{
|
||||||
|
base.InitializeEntity(entity);
|
||||||
|
|
||||||
|
if (_isComposite
|
||||||
|
&& _spriteLayers.Count > 0
|
||||||
|
&& entity.TryGetComponent<ISpriteComponent>(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<ISpriteComponent>(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<int>(StackVisuals.Actual, out var actual)) return;
|
||||||
|
if (!component.TryGetData<int>(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<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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<YamlScalarNode>(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<SpriteComponent>(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<SpriteComponent>(out var spriteComponent))
|
||||||
|
{
|
||||||
|
if (component.TryGetData<SharedBagState>(SharedBagOpenVisuals.BagState, out var bagState))
|
||||||
|
{
|
||||||
|
switch (bagState)
|
||||||
|
{
|
||||||
|
case SharedBagState.Open:
|
||||||
|
spriteComponent.LayerSetVisible(OpenIcon, true);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
spriteComponent.LayerSetVisible(OpenIcon, false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Client.Animations;
|
using Content.Client.Animations;
|
||||||
using Content.Client.GameObjects.Components.Items;
|
using Content.Client.GameObjects.Components.Items;
|
||||||
|
using Content.Shared.GameObjects.Components;
|
||||||
using Content.Shared.GameObjects.Components.Storage;
|
using Content.Shared.GameObjects.Components.Storage;
|
||||||
using Content.Shared.Interfaces.GameObjects.Components;
|
using Content.Shared.Interfaces.GameObjects.Components;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
@@ -29,9 +30,18 @@ namespace Content.Client.GameObjects.Components.Storage
|
|||||||
private int StorageSizeUsed;
|
private int StorageSizeUsed;
|
||||||
private int StorageCapacityMax;
|
private int StorageCapacityMax;
|
||||||
private StorageWindow Window;
|
private StorageWindow Window;
|
||||||
|
private SharedBagState _bagState;
|
||||||
|
|
||||||
public override IReadOnlyList<IEntity> StoredEntities => _storedEntities;
|
public override IReadOnlyList<IEntity> StoredEntities => _storedEntities;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
// Hide stackVisualizer on start
|
||||||
|
_bagState = SharedBagState.Close;
|
||||||
|
}
|
||||||
|
|
||||||
public override void OnAdd()
|
public override void OnAdd()
|
||||||
{
|
{
|
||||||
base.OnAdd();
|
base.OnAdd();
|
||||||
@@ -69,12 +79,15 @@ namespace Content.Client.GameObjects.Components.Storage
|
|||||||
//Updates what we are storing for the UI
|
//Updates what we are storing for the UI
|
||||||
case StorageHeldItemsMessage msg:
|
case StorageHeldItemsMessage msg:
|
||||||
HandleStorageMessage(msg);
|
HandleStorageMessage(msg);
|
||||||
|
ChangeStorageVisualization(_bagState);
|
||||||
break;
|
break;
|
||||||
//Opens the UI
|
//Opens the UI
|
||||||
case OpenStorageUIMessage _:
|
case OpenStorageUIMessage _:
|
||||||
|
ChangeStorageVisualization(SharedBagState.Open);
|
||||||
ToggleUI();
|
ToggleUI();
|
||||||
break;
|
break;
|
||||||
case CloseStorageUIMessage _:
|
case CloseStorageUIMessage _:
|
||||||
|
ChangeStorageVisualization(SharedBagState.Close);
|
||||||
CloseUI();
|
CloseUI();
|
||||||
break;
|
break;
|
||||||
case AnimateInsertingEntitiesMessage msg:
|
case AnimateInsertingEntitiesMessage msg:
|
||||||
@@ -119,6 +132,7 @@ namespace Content.Client.GameObjects.Components.Storage
|
|||||||
private void ToggleUI()
|
private void ToggleUI()
|
||||||
{
|
{
|
||||||
if (Window.IsOpen)
|
if (Window.IsOpen)
|
||||||
|
|
||||||
Window.Close();
|
Window.Close();
|
||||||
else
|
else
|
||||||
Window.Open();
|
Window.Open();
|
||||||
@@ -128,6 +142,16 @@ namespace Content.Client.GameObjects.Components.Storage
|
|||||||
{
|
{
|
||||||
Window.Close();
|
Window.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ChangeStorageVisualization(SharedBagState state)
|
||||||
|
{
|
||||||
|
_bagState = state;
|
||||||
|
if (Owner.TryGetComponent<AppearanceComponent>(out var appearanceComponent))
|
||||||
|
{
|
||||||
|
appearanceComponent.SetData(SharedBagOpenVisuals.BagState, state);
|
||||||
|
appearanceComponent.SetData(StackVisuals.Hide, state == SharedBagState.Close);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Function for clicking one of the stored entity buttons in the UI, tells server to remove that entity
|
/// Function for clicking one of the stored entity buttons in the UI, tells server to remove that entity
|
||||||
|
|||||||
@@ -164,6 +164,7 @@ namespace Content.Client
|
|||||||
"Firelock",
|
"Firelock",
|
||||||
"AtmosPlaque",
|
"AtmosPlaque",
|
||||||
"Spillable",
|
"Spillable",
|
||||||
|
"StorageCounter",
|
||||||
"SpaceVillainArcade",
|
"SpaceVillainArcade",
|
||||||
"Flammable",
|
"Flammable",
|
||||||
"CreamPie",
|
"CreamPie",
|
||||||
|
|||||||
@@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Storage that spawns and counts a single item.
|
||||||
|
/// Usually used for things like matchboxes, cigarette packs,
|
||||||
|
/// cigar cases etc.
|
||||||
|
/// </summary>
|
||||||
|
/// <code>
|
||||||
|
/// - type: StorageCounter
|
||||||
|
/// amount: 6 # Note: this field can be omitted
|
||||||
|
/// countTag: Cigarette # Note: field doesn't point to entity Id, but its tag
|
||||||
|
/// </code>
|
||||||
|
[RegisterComponent]
|
||||||
|
public class StorageCounterComponent : Component
|
||||||
|
{
|
||||||
|
private string? _countTag;
|
||||||
|
private int? _maxAmount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Single item storage component usually have an attached StackedVisualizer.
|
||||||
|
/// </summary>
|
||||||
|
[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<IEntity> containerContainedEntities)
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
if (_countTag != null)
|
||||||
|
{
|
||||||
|
foreach (var entity in containerContainedEntities)
|
||||||
|
{
|
||||||
|
if (entity.HasTag(_countTag))
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
21
Content.Shared/GameObjects/Components/StackVisuals.cs
Normal file
21
Content.Shared/GameObjects/Components/StackVisuals.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.GameObjects.Components
|
||||||
|
{
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum StackVisuals : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The amount of elements in the stack
|
||||||
|
/// </summary>
|
||||||
|
Actual,
|
||||||
|
/// <summary>
|
||||||
|
/// The total amount of elements in the stack. If unspecified, the visualizer assumes
|
||||||
|
/// its
|
||||||
|
/// </summary>
|
||||||
|
MaxCount,
|
||||||
|
Hide
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,14 +25,17 @@ namespace Content.Shared.Utility
|
|||||||
{
|
{
|
||||||
throw new ArgumentException("Levels must be greater than 0.", nameof(levels));
|
throw new ArgumentException("Levels must be greater than 0.", nameof(levels));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actual >= max)
|
if (actual >= max)
|
||||||
{
|
{
|
||||||
return levels - 1;
|
return levels - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actual <= 0)
|
if (actual <= 0)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
var toOne = actual / max;
|
var toOne = actual / max;
|
||||||
double threshold;
|
double threshold;
|
||||||
if (levels % 2 == 0)
|
if (levels % 2 == 0)
|
||||||
@@ -49,11 +52,11 @@ namespace Content.Shared.Utility
|
|||||||
var preround = toOne * (levels - 1);
|
var preround = toOne * (levels - 1);
|
||||||
if (toOne <= threshold || levels <= 2)
|
if (toOne <= threshold || levels <= 2)
|
||||||
{
|
{
|
||||||
return (int)Math.Ceiling(preround);
|
return (int) Math.Ceiling(preround);
|
||||||
}
|
}
|
||||||
else
|
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));
|
throw new ArgumentException("Levels must be greater than 1.", nameof(levels));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actual >= max)
|
if (actual >= max)
|
||||||
{
|
{
|
||||||
return levels;
|
return levels;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actual <= 0)
|
if (actual <= 0)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
double step = max / levels;
|
|
||||||
|
|
||||||
int nearest = 0;
|
return (int) Math.Round(actual / max * levels, MidpointRounding.AwayFromZero);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,41 +9,78 @@ namespace Content.Tests.Shared.Utility
|
|||||||
[TestOf(typeof(ContentHelpers))]
|
[TestOf(typeof(ContentHelpers))]
|
||||||
public class ContentHelpers_Test
|
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)[]
|
// Even level counts though..
|
||||||
{
|
(1, 10, 6, 1),
|
||||||
// Testing odd level counts. These are easy.
|
(2, 10, 6, 1),
|
||||||
(-1, 10, 5, 0),
|
(3, 10, 6, 2),
|
||||||
( 0, 10, 5, 0),
|
(4, 10, 6, 2),
|
||||||
( 0.01f, 10, 5, 1),
|
(5, 10, 6, 2),
|
||||||
( 1, 10, 5, 1),
|
(6, 10, 6, 3),
|
||||||
( 2, 10, 5, 1),
|
(7, 10, 6, 3),
|
||||||
( 2.5f, 10, 5, 1),
|
(8, 10, 6, 4),
|
||||||
( 2.51f, 10, 5, 2),
|
(9, 10, 6, 4),
|
||||||
( 3, 10, 5, 2),
|
(10, 10, 6, 5),
|
||||||
( 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..
|
public static readonly IEnumerable<(double val, double max, int levels, int expected)> TestNear =
|
||||||
( 1, 10, 6, 1),
|
new (double, double, int, int)[]
|
||||||
( 2, 10, 6, 1),
|
{
|
||||||
( 3, 10, 6, 2),
|
// Testing odd counts
|
||||||
( 4, 10, 6, 2),
|
(0, 5, 2, 0),
|
||||||
( 5, 10, 6, 2),
|
(1, 5, 2, 0),
|
||||||
( 6, 10, 6, 3),
|
(2, 5, 2, 1),
|
||||||
( 7, 10, 6, 3),
|
(3, 5, 2, 1),
|
||||||
( 8, 10, 6, 4),
|
(4, 5, 2, 2),
|
||||||
( 9, 10, 6, 4),
|
(5, 5, 2, 2),
|
||||||
(10, 10, 6, 5),
|
|
||||||
};
|
// 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]
|
[Parallelizable]
|
||||||
[Test]
|
[Test]
|
||||||
@@ -52,5 +89,13 @@ namespace Content.Tests.Shared.Utility
|
|||||||
(double val, double max, int levels, int expected) = data;
|
(double val, double max, int levels, int expected) = data;
|
||||||
Assert.That(ContentHelpers.RoundToLevels(val, max, levels), Is.EqualTo(expected));
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
- type: entity
|
- type: Tag
|
||||||
|
id: Cigarette
|
||||||
|
- type: entity
|
||||||
name: "Base Cigarette"
|
name: "Base Cigarette"
|
||||||
id: BaseCigarette
|
id: BaseCigarette
|
||||||
parent: BaseItem
|
parent: BaseItem
|
||||||
@@ -13,6 +15,9 @@
|
|||||||
Slots: [ mask ]
|
Slots: [ mask ]
|
||||||
HeldPrefix: unlit
|
HeldPrefix: unlit
|
||||||
size: 1
|
size: 1
|
||||||
|
- type: Tag
|
||||||
|
tags:
|
||||||
|
- Cigarette
|
||||||
- type: Smoking
|
- type: Smoking
|
||||||
duration: 30
|
duration: 30
|
||||||
- type: Appearance
|
- type: Appearance
|
||||||
@@ -33,10 +38,25 @@
|
|||||||
components:
|
components:
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: Objects/Consumable/Fancy/cigarettes.rsi
|
sprite: Objects/Consumable/Fancy/cigarettes.rsi
|
||||||
|
netsync: false
|
||||||
layers:
|
layers:
|
||||||
- state: cig
|
- state: cig
|
||||||
- type: StorageFill
|
- type: StorageFill
|
||||||
contents:
|
contents:
|
||||||
- name: Cigarette
|
- name: Cigarette
|
||||||
amount: 6
|
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
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
abstract: true
|
abstract: true
|
||||||
parent: BaseItem
|
parent: BaseItem
|
||||||
components:
|
components:
|
||||||
- type: Stack
|
- type: Stack
|
||||||
- type: Material
|
- type: Material
|
||||||
- type: ItemStatus
|
- type: ItemStatus
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: steel sheet
|
name: steel sheet
|
||||||
@@ -14,21 +14,21 @@
|
|||||||
parent: MaterialStack
|
parent: MaterialStack
|
||||||
suffix: Full
|
suffix: Full
|
||||||
components:
|
components:
|
||||||
- type: Material
|
- type: Material
|
||||||
materials:
|
materials:
|
||||||
- key: enum.MaterialKeys.Stack
|
- key: enum.MaterialKeys.Stack
|
||||||
mat: steel
|
mat: steel
|
||||||
- type: Stack
|
- type: Stack
|
||||||
stacktype: enum.StackType.Metal
|
stacktype: enum.StackType.Metal
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: Objects/Materials/sheets.rsi
|
sprite: Objects/Materials/sheets.rsi
|
||||||
state: metal
|
state: metal
|
||||||
- type: Item
|
- type: Item
|
||||||
sprite: Objects/Materials/sheets.rsi
|
sprite: Objects/Materials/sheets.rsi
|
||||||
HeldPrefix: metal
|
HeldPrefix: metal
|
||||||
- type: FloorTile
|
- type: FloorTile
|
||||||
outputs:
|
outputs:
|
||||||
- underplating
|
- underplating
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: SteelSheet1
|
id: SteelSheet1
|
||||||
@@ -36,9 +36,9 @@
|
|||||||
parent: MetalStack
|
parent: MetalStack
|
||||||
suffix: 1
|
suffix: 1
|
||||||
components:
|
components:
|
||||||
- type: Stack
|
- type: Stack
|
||||||
stacktype: enum.StackType.Metal
|
stacktype: enum.StackType.Metal
|
||||||
count: 1
|
count: 1
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: glass sheet
|
name: glass sheet
|
||||||
@@ -46,18 +46,18 @@
|
|||||||
parent: MaterialStack
|
parent: MaterialStack
|
||||||
suffix: Full
|
suffix: Full
|
||||||
components:
|
components:
|
||||||
- type: Material
|
- type: Material
|
||||||
materials:
|
materials:
|
||||||
- key: enum.MaterialKeys.Stack
|
- key: enum.MaterialKeys.Stack
|
||||||
mat: glass
|
mat: glass
|
||||||
- type: Stack
|
- type: Stack
|
||||||
stacktype: enum.StackType.Glass
|
stacktype: enum.StackType.Glass
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: Objects/Materials/sheets.rsi
|
sprite: Objects/Materials/sheets.rsi
|
||||||
state: glass
|
state: glass
|
||||||
- type: Item
|
- type: Item
|
||||||
sprite: Objects/Materials/sheets.rsi
|
sprite: Objects/Materials/sheets.rsi
|
||||||
HeldPrefix: glass
|
HeldPrefix: glass
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: GlassSheet1
|
id: GlassSheet1
|
||||||
@@ -65,9 +65,9 @@
|
|||||||
parent: GlassStack
|
parent: GlassStack
|
||||||
suffix: 1
|
suffix: 1
|
||||||
components:
|
components:
|
||||||
- type: Stack
|
- type: Stack
|
||||||
stacktype: enum.StackType.Glass
|
stacktype: enum.StackType.Glass
|
||||||
count: 1
|
count: 1
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: Reinforced Glass
|
name: Reinforced Glass
|
||||||
@@ -94,9 +94,9 @@
|
|||||||
parent: rglass
|
parent: rglass
|
||||||
suffix: 1
|
suffix: 1
|
||||||
components:
|
components:
|
||||||
- type: Stack
|
- type: Stack
|
||||||
StackType: enum.StackType.ReinforcedGlass
|
StackType: enum.StackType.ReinforcedGlass
|
||||||
count: 1
|
count: 1
|
||||||
|
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
@@ -134,15 +134,23 @@
|
|||||||
parent: MaterialStack
|
parent: MaterialStack
|
||||||
suffix: Full
|
suffix: Full
|
||||||
components:
|
components:
|
||||||
- type: Material
|
- type: Material
|
||||||
materials:
|
materials:
|
||||||
- key: enum.MaterialKeys.Stack
|
- key: enum.MaterialKeys.Stack
|
||||||
mat: gold
|
mat: gold
|
||||||
- type: Stack
|
- type: Stack
|
||||||
stacktype: enum.StackType.Gold
|
stacktype: enum.StackType.Gold
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: Objects/Materials/materials.rsi
|
sprite: Objects/Materials/materials.rsi
|
||||||
state: goldbar_30
|
state: goldbar_30
|
||||||
|
netsync: false
|
||||||
|
- type: Appearance
|
||||||
|
visuals:
|
||||||
|
- type: StackVisualizer
|
||||||
|
stackLayers:
|
||||||
|
- goldbar_10
|
||||||
|
- goldbar_20
|
||||||
|
- goldbar_30
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: GoldStack1
|
id: GoldStack1
|
||||||
@@ -150,11 +158,11 @@
|
|||||||
parent: GoldStack
|
parent: GoldStack
|
||||||
suffix: 1
|
suffix: 1
|
||||||
components:
|
components:
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: Objects/Materials/materials.rsi
|
sprite: Objects/Materials/materials.rsi
|
||||||
state: goldbar_10
|
state: goldbar_10
|
||||||
- type: Stack
|
- type: Stack
|
||||||
count: 1
|
count: 1
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: plasma ore
|
name: plasma ore
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
# If you're looking at the rsi for this file, you'll probably be confused why
|
# 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
|
# 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
|
# ability to have applied colors yet in GUIs. -Swept
|
||||||
@@ -10,14 +9,16 @@
|
|||||||
name: cable stack
|
name: cable stack
|
||||||
suffix: Full
|
suffix: Full
|
||||||
components:
|
components:
|
||||||
- type: Stack
|
- type: Stack
|
||||||
stacktype: enum.StackType.Cable
|
stacktype: enum.StackType.Cable
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: Objects/Tools/cables.rsi
|
sprite: Objects/Tools/cables.rsi
|
||||||
- type: Item
|
netsync: false
|
||||||
sprite: Objects/Tools/cables.rsi
|
- type: Item
|
||||||
- type: WirePlacer
|
sprite: Objects/Tools/cables.rsi
|
||||||
- type: Clickable
|
- type: WirePlacer
|
||||||
|
- type: Clickable
|
||||||
|
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: HVWireStack
|
id: HVWireStack
|
||||||
@@ -25,16 +26,23 @@
|
|||||||
name: HV cable coil
|
name: HV cable coil
|
||||||
suffix: Full
|
suffix: Full
|
||||||
components:
|
components:
|
||||||
- type: Stack
|
- type: Stack
|
||||||
stacktype: enum.StackType.HVCable
|
stacktype: enum.StackType.HVCable
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
state: coilhv-30
|
state: coilhv-30
|
||||||
- type: Item
|
- type: Item
|
||||||
size: 10
|
size: 10
|
||||||
HeldPrefix: coilhv
|
HeldPrefix: coilhv
|
||||||
- type: WirePlacer
|
- type: WirePlacer
|
||||||
wirePrototypeID: HVWire
|
wirePrototypeID: HVWire
|
||||||
blockingWireType: HighVoltage
|
blockingWireType: HighVoltage
|
||||||
|
- type: Appearance
|
||||||
|
visuals:
|
||||||
|
- type: StackVisualizer
|
||||||
|
stackLayers:
|
||||||
|
- coilhv-10
|
||||||
|
- coilhv-20
|
||||||
|
- coilhv-30
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: HVWireStack
|
parent: HVWireStack
|
||||||
@@ -42,7 +50,7 @@
|
|||||||
suffix: 1
|
suffix: 1
|
||||||
components:
|
components:
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
state: coilhv-10
|
state: coilhv-10
|
||||||
- type: Item
|
- type: Item
|
||||||
size: 3
|
size: 3
|
||||||
- type: Stack
|
- type: Stack
|
||||||
@@ -55,14 +63,21 @@
|
|||||||
description: Low-Voltage stack of wires for connecting APCs to machines and other purposes.
|
description: Low-Voltage stack of wires for connecting APCs to machines and other purposes.
|
||||||
suffix: Full
|
suffix: Full
|
||||||
components:
|
components:
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
state: coillv-30
|
state: coillv-30
|
||||||
- type: Item
|
- type: Item
|
||||||
size: 10
|
size: 10
|
||||||
HeldPrefix: coillv
|
HeldPrefix: coillv
|
||||||
- type: WirePlacer
|
- type: WirePlacer
|
||||||
wirePrototypeID: ApcExtensionCable
|
wirePrototypeID: ApcExtensionCable
|
||||||
blockingWireType: Apc
|
blockingWireType: Apc
|
||||||
|
- type: Appearance
|
||||||
|
visuals:
|
||||||
|
- type: StackVisualizer
|
||||||
|
stackLayers:
|
||||||
|
- coillv-10
|
||||||
|
- coillv-20
|
||||||
|
- coillv-30
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: ApcExtensionCableStack
|
parent: ApcExtensionCableStack
|
||||||
@@ -92,6 +107,13 @@
|
|||||||
- type: WirePlacer
|
- type: WirePlacer
|
||||||
wirePrototypeID: MVWire
|
wirePrototypeID: MVWire
|
||||||
blockingWireType: MediumVoltage
|
blockingWireType: MediumVoltage
|
||||||
|
- type: Appearance
|
||||||
|
visuals:
|
||||||
|
- type: StackVisualizer
|
||||||
|
stackLayers:
|
||||||
|
- coilmv-10
|
||||||
|
- coilmv-20
|
||||||
|
- coilmv-30
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: MVWireStack
|
parent: MVWireStack
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 117 B |
Binary file not shown.
|
After Width: | Height: | Size: 116 B |
Binary file not shown.
|
After Width: | Height: | Size: 116 B |
Binary file not shown.
|
After Width: | Height: | Size: 116 B |
Binary file not shown.
|
After Width: | Height: | Size: 114 B |
Binary file not shown.
|
After Width: | Height: | Size: 114 B |
@@ -15,6 +15,24 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "cig_open"
|
"name": "cig_open"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cigarette_1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cigarette_2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cigarette_3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cigarette_4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cigarette_5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cigarette_6"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user