Adds T-ray Scanners (#5420)

This commit is contained in:
Flipp Syder
2021-12-13 23:46:47 -08:00
committed by GitHub
parent 26161e35cb
commit c0bfe43a05
14 changed files with 520 additions and 35 deletions

View File

@@ -0,0 +1,45 @@
using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
namespace Content.Client.SubFloor;
public class TrayScannerSubFloorVisualizer : AppearanceVisualizer
{
[Dependency] IEntityManager _entityManager = default!;
public override void InitializeEntity(EntityUid uid)
{
base.InitializeEntity(uid);
IoCManager.InjectDependencies(this);
}
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
if (!_entityManager.TryGetComponent(component.Owner, out SpriteComponent? sprite))
return;
if (!component.TryGetData(TrayScannerTransparency.Key, out bool transparent))
return;
foreach (var layer in sprite.AllLayers)
{
var transparency = transparent == true ? 0.8f : 1f;
layer.Color = layer.Color.WithAlpha(transparency);
}
if (sprite.LayerMapTryGet(SubFloorShowLayerVisualizer.Layers.FirstLayer, out var firstLayer))
{
sprite.LayerSetColor(firstLayer, Color.White);
}
}
}
public enum TrayScannerTransparency
{
Key,
}

View File

@@ -0,0 +1,176 @@
using System.Collections.Generic;
using System.Linq;
using Content.Shared.SubFloor;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
namespace Content.Client.SubFloor;
public class TrayScannerSystem : SharedTrayScannerSystem
{
[Dependency] private IEntityLookup _entityLookup = default!;
[Dependency] private SubFloorHideSystem _subfloorSystem = default!;
[Dependency] private SharedContainerSystem _containerSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<TrayScannerComponent, ComponentShutdown>(OnComponentShutdown);
}
public void OnComponentShutdown(EntityUid uid, TrayScannerComponent scanner, ComponentShutdown args)
{
_subfloorSystem.ToggleSubfloorEntities(scanner.RevealedSubfloors, false, uid, _visualizerKeys);
_invalidScanners.Add(uid);
}
public override void ToggleTrayScanner(EntityUid uid, bool toggle, TrayScannerComponent? scanner = null)
{
if (!Resolve(uid, ref scanner))
return;
scanner.Toggled = toggle;
UpdateTrayScanner(uid, scanner);
if (toggle) _activeScanners.Add(uid);
}
private HashSet<EntityUid> _activeScanners = new();
private RemQueue<EntityUid> _invalidScanners = new();
public override void Update(float frameTime)
{
if (!_activeScanners.Any()) return;
foreach (var scanner in _activeScanners)
{
if (_invalidScanners.List != null
&& _invalidScanners.List.Contains(scanner))
continue;
if (!UpdateTrayScanner(scanner))
_invalidScanners.Add(scanner);
}
foreach (var invalidScanner in _invalidScanners)
_activeScanners.Remove(invalidScanner);
if (_invalidScanners.List != null) _invalidScanners.List.Clear();
}
/// <summary>
/// Updates a T-Ray scanner. Should be called on immediate
/// state change (turned on/off), or during the update
/// loop.
/// </summary>
/// <returns>true if the update was successful, false otherwise</returns>
private bool UpdateTrayScanner(EntityUid uid, TrayScannerComponent? scanner = null, TransformComponent? transform = null)
{
// whoops?
if (!Resolve(uid, ref scanner, ref transform))
{
return false;
}
// if the scanner was toggled off recently,
// set all the known subfloor to invisible,
// and return false so it's removed from
// the active scanner list
if (!scanner.Toggled)
{
_subfloorSystem.ToggleSubfloorEntities(scanner.RevealedSubfloors, false, uid, _visualizerKeys);
scanner.LastLocation = Vector2.Zero;
scanner.RevealedSubfloors.Clear();
return false;
}
// get the rounded position so that small movements don't cause this to
// update every time
Vector2 flooredPos;
// zero vector implies container
//
// this means we should get the entity transform's parent
if (transform.LocalPosition == Vector2.Zero
&& transform.Parent != null
&& _containerSystem.ContainsEntity(transform.ParentUid, uid))
{
flooredPos = transform.Parent.LocalPosition.Rounded();
// if this is also zero, we can check one more time
//
// could recurse through fully but i think that's useless,
// just attempt to check through the gp's transform and if
// that doesn't work, just don't bother any further
if (flooredPos == Vector2.Zero)
{
var gpTransform = transform.Parent.Parent;
if (gpTransform != null
&& _containerSystem.ContainsEntity(gpTransform.Owner, transform.ParentUid))
{
flooredPos = gpTransform.LocalPosition.Rounded();
}
}
}
else
{
flooredPos = transform.LocalPosition.Rounded();
}
// is the position still logically zero? just clear,
// but we need to keep it as 'true' since this t-ray
// is still technically on
if (flooredPos == Vector2.Zero)
{
_subfloorSystem.ToggleSubfloorEntities(scanner.RevealedSubfloors, false, uid, _visualizerKeys);
scanner.RevealedSubfloors.Clear();
return true;
}
if (flooredPos == scanner.LastLocation
|| (float.IsNaN(flooredPos.X) && float.IsNaN(flooredPos.Y)))
return true;
scanner.LastLocation = flooredPos;
// get all entities in range by uid
// but without using LINQ
HashSet<EntityUid> nearby = new();
foreach (var entityInRange in _entityLookup.GetEntitiesInRange(uid, scanner.Range))
if (FilterAnchored(entityInRange)) nearby.Add(entityInRange);
// get all the old elements that are no longer detected
scanner.RevealedSubfloors.ExceptWith(nearby);
// hide all of them, since they're no longer needed
_subfloorSystem.ToggleSubfloorEntities(scanner.RevealedSubfloors, false, uid, _visualizerKeys);
scanner.RevealedSubfloors.Clear();
// set the revealedsubfloor set to the new nearby set
scanner.RevealedSubfloors.UnionWith(nearby);
// show all the new subfloor
_subfloorSystem.ToggleSubfloorEntities(scanner.RevealedSubfloors, true, uid, _visualizerKeys);
return true;
}
private static IEnumerable<object> _visualizerKeys = new List<object>
{
SubFloorVisuals.SubFloor,
TrayScannerTransparency.Key
};
private bool FilterAnchored(EntityUid uid)
{
return EntityManager.TryGetComponent<TransformComponent>(uid, out var transform)
&& transform.Anchored;
}
}

View File

@@ -0,0 +1,11 @@
using Content.Shared.SubFloor;
namespace Content.Server.SubFloor;
public class TrayScannerSystem : SharedTrayScannerSystem
{
public override void Initialize()
{
base.Initialize();
}
}

View File

@@ -0,0 +1,76 @@
using System;
using Content.Shared.Interaction;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
namespace Content.Shared.SubFloor;
public abstract class SharedTrayScannerSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<TrayScannerComponent, ComponentGetState>(OnTrayScannerGetState);
SubscribeLocalEvent<TrayScannerComponent, ComponentHandleState>(OnTrayScannerHandleState);
SubscribeLocalEvent<TrayScannerComponent, UseInHandEvent>(OnTrayScannerUsed);
SubscribeLocalEvent<TrayScannerComponent, ActivateInWorldEvent>(OnTrayScannerActivate);
}
private void OnTrayScannerUsed(EntityUid uid, TrayScannerComponent scanner, UseInHandEvent args)
{
ActivateTray(uid, scanner);
}
private void OnTrayScannerActivate(EntityUid uid, TrayScannerComponent scanner, ActivateInWorldEvent args)
{
ActivateTray(uid, scanner);
}
private void ActivateTray(EntityUid uid, TrayScannerComponent? scanner = null)
{
if (!Resolve(uid, ref scanner))
return;
ToggleTrayScanner(uid, !scanner.Toggled, scanner);
if (EntityManager.TryGetComponent<AppearanceComponent>(uid, out var appearance))
{
appearance.SetData(TrayScannerVisual.Visual, scanner.Toggled == true ? TrayScannerVisual.On : TrayScannerVisual.Off);
}
}
public virtual void ToggleTrayScanner(EntityUid uid, bool state, TrayScannerComponent? scanner = null)
{
if (!Resolve(uid, ref scanner))
return;
scanner.Toggled = state;
scanner.Dirty();
// RaiseLocalEvent(uid, new TrayScannerToggleEvent(scanner.Toggled));
}
private void OnTrayScannerGetState(EntityUid uid, TrayScannerComponent scanner, ref ComponentGetState args)
{
args.State = new TrayScannerState(scanner.Toggled);
}
private void OnTrayScannerHandleState(EntityUid uid, TrayScannerComponent scanner, ref ComponentHandleState args)
{
if (args.Current is not TrayScannerState state)
return;
ToggleTrayScanner(uid, state.Toggled, scanner);
}
}
[Serializable, NetSerializable]
public enum TrayScannerVisual : sbyte
{
Visual,
On,
Off
}

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Players; using Robust.Shared.Players;
@@ -39,6 +40,26 @@ namespace Content.Shared.SubFloor
{ {
return new SubFloorHideComponentState(Enabled, RequireAnchored); return new SubFloorHideComponentState(Enabled, RequireAnchored);
} }
/// <summary>
/// Whether or not this entity is supposed
/// to be visible.
/// </summary>
[ViewVariables]
public bool Visible { get; set; }
/// <summary>
/// The entities this subfloor is revealed by.
/// </summary>
[ViewVariables]
public HashSet<EntityUid> RevealedBy { get; set; } = new();
/// <summary>
/// Whether or not this entity was revealed with or without
/// an entity.
/// </summary>
[ViewVariables]
public bool RevealedWithoutEntity { get; set; }
} }
[Serializable, NetSerializable] [Serializable, NetSerializable]

View File

@@ -1,9 +1,12 @@
using System; using System;
using System.Collections.Generic;
using Content.Shared.Interaction;
using Content.Shared.Maps; using Content.Shared.Maps;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Maths; using Robust.Shared.Maths;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
@@ -46,6 +49,7 @@ namespace Content.Shared.SubFloor
SubscribeLocalEvent<SubFloorHideComponent, ComponentShutdown>(OnSubFloorTerminating); SubscribeLocalEvent<SubFloorHideComponent, ComponentShutdown>(OnSubFloorTerminating);
SubscribeLocalEvent<SubFloorHideComponent, AnchorStateChangedEvent>(HandleAnchorChanged); SubscribeLocalEvent<SubFloorHideComponent, AnchorStateChangedEvent>(HandleAnchorChanged);
SubscribeLocalEvent<SubFloorHideComponent, ComponentHandleState>(HandleComponentState); SubscribeLocalEvent<SubFloorHideComponent, ComponentHandleState>(HandleComponentState);
SubscribeLocalEvent<SubFloorHideComponent, InteractUsingEvent>(OnInteractionAttempt);
} }
public override void Shutdown() public override void Shutdown()
@@ -70,6 +74,18 @@ namespace Content.Shared.SubFloor
UpdateEntity(subFloor.Owner); UpdateEntity(subFloor.Owner);
} }
private void OnInteractionAttempt(EntityUid uid, SubFloorHideComponent component, InteractUsingEvent args)
{
if (!EntityManager.TryGetComponent(uid, out TransformComponent? transform))
return;
if (_mapManager.TryGetGrid(transform.GridID, out var grid)
&& !IsSubFloor(grid, grid.TileIndicesFor(transform.Coordinates)))
{
args.Handled = true;
}
}
private void OnSubFloorStarted(EntityUid uid, SubFloorHideComponent component, ComponentStartup _) private void OnSubFloorStarted(EntityUid uid, SubFloorHideComponent component, ComponentStartup _)
{ {
UpdateEntity(uid); UpdateEntity(uid);
@@ -153,39 +169,80 @@ namespace Content.Shared.SubFloor
} }
// Update normally. // Update normally.
UpdateEntity(uid, IsSubFloor(grid, grid.TileIndicesFor(transform.Coordinates))); bool isSubFloor = IsSubFloor(grid, grid.TileIndicesFor(transform.Coordinates));
UpdateEntity(uid, isSubFloor);
} }
private void UpdateEntity(EntityUid uid, bool subFloor) // Toggles an enumerable set of entities to display.
public void ToggleSubfloorEntities(IEnumerable<EntityUid> entities, bool visible, EntityUid? uid = null, IEnumerable<object>? appearanceKeys = null)
{ {
// We raise an event to allow other entity systems to handle this. foreach (var entity in entities)
var subFloorHideEvent = new SubFloorHideEvent(subFloor);
RaiseLocalEvent(uid, subFloorHideEvent, false);
// Check if it has been handled by someone else.
if (subFloorHideEvent.Handled)
return;
// We only need to query the subfloor component to check if it's enabled or not when we're not on subfloor.
// Getting components is expensive, after all.
if (!subFloor && EntityManager.TryGetComponent(uid, out SubFloorHideComponent? subFloorHideComponent))
{ {
// If the component isn't enabled, then subfloor will always be true, and the entity will be shown. if (!EntityManager.HasComponent<SubFloorHideComponent>(entity))
if (!subFloorHideComponent.Enabled) continue;
{
subFloor = true; UpdateEntity(entity, visible, uid, appearanceKeys);
}
// We only need to query the TransformComp if the SubfloorHide is enabled and requires anchoring.
else if (subFloorHideComponent.RequireAnchored && EntityManager.TryGetComponent(uid, out TransformComponent? transformComponent))
{
// If we require the entity to be anchored but it's not, this will set subfloor to true, unhiding it.
subFloor = !transformComponent.Anchored;
}
} }
}
private void UpdateEntity(EntityUid uid, bool subFloor, EntityUid? revealedUid = null, IEnumerable<object>? appearanceKeys = null)
{
bool revealedWithoutEntity = false;
if (EntityManager.TryGetComponent(uid, out SubFloorHideComponent? subFloorHideComponent))
{
// We only need to query the subfloor component to check if it's enabled or not when we're not on subfloor.
// Getting components is expensive, after all.
if (!subFloor)
{
// If the component isn't enabled, then subfloor will always be true, and the entity will be shown.
if (!subFloorHideComponent.Enabled)
{
subFloor = true;
}
// We only need to query the TransformComp if the SubfloorHide is enabled and requires anchoring.
else if (subFloorHideComponent.RequireAnchored && EntityManager.TryGetComponent(uid, out TransformComponent? transformComponent))
{
// If we require the entity to be anchored but it's not, this will set subfloor to true, unhiding it.
subFloor = !transformComponent.Anchored;
}
}
// If this was revealed by anything, we need to add it into the
// component's set of entities that reveal it
//
if (revealedUid != null)
{
if (subFloor) subFloorHideComponent.RevealedBy.Add((EntityUid) revealedUid);
else subFloorHideComponent.RevealedBy.Remove((EntityUid) revealedUid);
}
else
{
subFloorHideComponent.RevealedWithoutEntity = subFloor;
}
subFloor = subFloorHideComponent.RevealedBy.Count != 0 || subFloorHideComponent.RevealedWithoutEntity;
revealedWithoutEntity = subFloorHideComponent.RevealedWithoutEntity;
}
// Whether to show this entity as visible, visually. // Whether to show this entity as visible, visually.
var subFloorVisible = ShowAll || subFloor; var subFloorVisible = ShowAll || subFloor;
// if there are no keys given,
// or if the subfloor is already revealed,
// set the keys to the default:
//
// the reason why it's set to default when the subfloor is
// revealed without an entity is because the appearance keys
// should only apply if the visualizer is underneath a subfloor
if (appearanceKeys == null || revealedWithoutEntity) appearanceKeys = _defaultVisualizerKeys;
ShowSubfloorSprite(uid, subFloorVisible, appearanceKeys);
}
private void ShowSubfloorSprite(EntityUid uid, bool subFloorVisible, IEnumerable<object> appearanceKeys)
{
// Show sprite // Show sprite
if (EntityManager.TryGetComponent(uid, out SharedSpriteComponent? spriteComponent)) if (EntityManager.TryGetComponent(uid, out SharedSpriteComponent? spriteComponent))
{ {
@@ -195,19 +252,22 @@ namespace Content.Shared.SubFloor
// Set an appearance data value so visualizers can use this as needed. // Set an appearance data value so visualizers can use this as needed.
if (EntityManager.TryGetComponent(uid, out AppearanceComponent? appearanceComponent)) if (EntityManager.TryGetComponent(uid, out AppearanceComponent? appearanceComponent))
{ {
appearanceComponent.SetData(SubFloorVisuals.SubFloor, subFloorVisible); foreach (var key in appearanceKeys)
{
switch (key)
{
case Enum enumKey:
appearanceComponent.SetData(enumKey, subFloorVisible);
break;
case string stringKey:
appearanceComponent.SetData(stringKey, subFloorVisible);
break;
}
}
} }
} }
}
public class SubFloorHideEvent : HandledEntityEventArgs private static List<object> _defaultVisualizerKeys = new List<object>{ SubFloorVisuals.SubFloor };
{
public bool SubFloor { get; }
public SubFloorHideEvent(bool subFloor)
{
SubFloor = subFloor;
}
} }
[Serializable, NetSerializable] [Serializable, NetSerializable]

View File

@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Shared.SubFloor;
[RegisterComponent]
[NetworkedComponent]
public class TrayScannerComponent : Component
{
public override string Name { get; } = "TrayScanner";
[ViewVariables]
public bool Toggled { get; set; }
// this should always be rounded
[ViewVariables]
public Vector2 LastLocation { get; set; }
// range of the scanner itself
[DataField("range")]
public float Range { get; set; } = 0.5f;
// exclude entities that are not the set
// of entities in range & entities already revealed
[ViewVariables]
public HashSet<EntityUid> RevealedSubfloors = new();
}
[Serializable, NetSerializable]
public sealed class TrayScannerState : ComponentState
{
public bool Toggled { get; }
public TrayScannerState(bool toggle)
{
Toggled = toggle;
}
}

View File

@@ -0,0 +1,20 @@
- type: entity
name: t-ray scanner
parent: BaseItem
id: trayScanner
components:
- type: Sprite
sprite: Objects/Tools/t-ray.rsi
layers:
- state: tray-off
- type: TrayScanner
- type: Item
sprite: Objects/Tools/t-ray.rsi
- type: Appearance
visuals:
- type: GenericEnumVisualizer
key: enum.TrayScannerVisual.Visual
layer: 0
states:
enum.TrayScannerVisual.On: tray-on
enum.TrayScannerVisual.Off: tray-off

View File

@@ -39,6 +39,7 @@
- type: Damageable - type: Damageable
damageContainer: Inorganic damageContainer: Inorganic
damageModifierSet: Metallic damageModifierSet: Metallic
- type: SubFloorHide
- type: Anchorable - type: Anchorable
- type: Rotatable - type: Rotatable
- type: Pullable - type: Pullable
@@ -58,10 +59,10 @@
visuals: visuals:
- type: PipeConnectorVisualizer - type: PipeConnectorVisualizer
- type: PipeColorVisualizer - type: PipeColorVisualizer
- type: TrayScannerSubFloorVisualizer
- type: NodeContainer - type: NodeContainer
- type: AtmosUnsafeUnanchor - type: AtmosUnsafeUnanchor
- type: AtmosPipeColor - type: AtmosPipeColor
- type: SubFloorHide
- type: Tag - type: Tag
tags: tags:
- Pipe - Pipe

View File

@@ -53,6 +53,8 @@
- type: DisposalTransit - type: DisposalTransit
- type: Appearance - type: Appearance
visuals: visuals:
- type: TrayScannerSubFloorVisualizer
- type: SubFloorShowLayerVisualizer
- type: DisposalVisualizer - type: DisposalVisualizer
state_free: conpipe-s state_free: conpipe-s
state_anchored: pipe-s state_anchored: pipe-s
@@ -78,6 +80,8 @@
- type: DisposalTagger - type: DisposalTagger
- type: Appearance - type: Appearance
visuals: visuals:
- type: TrayScannerSubFloorVisualizer
- type: SubFloorShowLayerVisualizer
- type: DisposalVisualizer - type: DisposalVisualizer
state_free: conpipe-tagger state_free: conpipe-tagger
state_anchored: pipe-tagger state_anchored: pipe-tagger
@@ -107,6 +111,8 @@
- type: DisposalEntry - type: DisposalEntry
- type: Appearance - type: Appearance
visuals: visuals:
- type: TrayScannerSubFloorVisualizer
- type: SubFloorShowLayerVisualizer
- type: DisposalVisualizer - type: DisposalVisualizer
state_free: conpipe-t state_free: conpipe-t
state_anchored: pipe-t state_anchored: pipe-t
@@ -136,6 +142,8 @@
- 180 - 180
- type: Appearance - type: Appearance
visuals: visuals:
- type: TrayScannerSubFloorVisualizer
- type: SubFloorShowLayerVisualizer
- type: DisposalVisualizer - type: DisposalVisualizer
state_free: conpipe-j1s state_free: conpipe-j1s
state_anchored: pipe-j1s state_anchored: pipe-j1s
@@ -170,6 +178,8 @@
- 180 - 180
- type: Appearance - type: Appearance
visuals: visuals:
- type: TrayScannerSubFloorVisualizer
- type: SubFloorShowLayerVisualizer
- type: DisposalVisualizer - type: DisposalVisualizer
state_free: conpipe-j2s state_free: conpipe-j2s
state_anchored: pipe-j2s state_anchored: pipe-j2s
@@ -201,6 +211,8 @@
- 180 - 180
- type: Appearance - type: Appearance
visuals: visuals:
- type: TrayScannerSubFloorVisualizer
- type: SubFloorShowLayerVisualizer
- type: DisposalVisualizer - type: DisposalVisualizer
state_free: conpipe-j1 state_free: conpipe-j1
state_anchored: pipe-j1 state_anchored: pipe-j1
@@ -231,6 +243,8 @@
- 180 - 180
- type: Appearance - type: Appearance
visuals: visuals:
- type: TrayScannerSubFloorVisualizer
- type: SubFloorShowLayerVisualizer
- type: DisposalVisualizer - type: DisposalVisualizer
state_free: conpipe-j2 state_free: conpipe-j2
state_anchored: pipe-j2 state_anchored: pipe-j2
@@ -262,6 +276,8 @@
- -90 - -90
- type: Appearance - type: Appearance
visuals: visuals:
- type: TrayScannerSubFloorVisualizer
- type: SubFloorShowLayerVisualizer
- type: DisposalVisualizer - type: DisposalVisualizer
state_free: conpipe-y state_free: conpipe-y
state_anchored: pipe-y state_anchored: pipe-y
@@ -287,6 +303,8 @@
- type: DisposalBend - type: DisposalBend
- type: Appearance - type: Appearance
visuals: visuals:
- type: TrayScannerSubFloorVisualizer
- type: SubFloorShowLayerVisualizer
- type: DisposalVisualizer - type: DisposalVisualizer
state_free: conpipe-c state_free: conpipe-c
state_anchored: pipe-c state_anchored: pipe-c

View File

@@ -77,6 +77,7 @@
acts: [ "Destruction" ] acts: [ "Destruction" ]
- type: Appearance - type: Appearance
visuals: visuals:
- type: TrayScannerSubFloorVisualizer
- type: CableVisualizer - type: CableVisualizer
base: hvcable_ base: hvcable_
@@ -117,6 +118,7 @@
acts: [ "Destruction" ] acts: [ "Destruction" ]
- type: Appearance - type: Appearance
visuals: visuals:
- type: TrayScannerSubFloorVisualizer
- type: CableVisualizer - type: CableVisualizer
base: mvcable_ base: mvcable_
@@ -160,5 +162,6 @@
acts: [ "Destruction" ] acts: [ "Destruction" ]
- type: Appearance - type: Appearance
visuals: visuals:
- type: TrayScannerSubFloorVisualizer
- type: CableVisualizer - type: CableVisualizer
base: lvcable_ base: lvcable_

View File

@@ -0,0 +1,10 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "/tg/station, 'icons/obj/device.dmi', commit 2b8b045d",
"size": { "x": 32, "y": 32 },
"states": [
{ "name": "tray-on" , "delays": [[0.1, 1.0]]},
{ "name": "tray-off" }
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B