diff --git a/Content.Client/SubFloor/TrayScannerSubFloorVisualizer.cs b/Content.Client/SubFloor/TrayScannerSubFloorVisualizer.cs new file mode 100644 index 0000000000..f4d2a9c124 --- /dev/null +++ b/Content.Client/SubFloor/TrayScannerSubFloorVisualizer.cs @@ -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, +} diff --git a/Content.Client/SubFloor/TrayScannerSystem.cs b/Content.Client/SubFloor/TrayScannerSystem.cs new file mode 100644 index 0000000000..d350585af1 --- /dev/null +++ b/Content.Client/SubFloor/TrayScannerSystem.cs @@ -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(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 _activeScanners = new(); + private RemQueue _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(); + } + + /// + /// Updates a T-Ray scanner. Should be called on immediate + /// state change (turned on/off), or during the update + /// loop. + /// + /// true if the update was successful, false otherwise + 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 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 _visualizerKeys = new List + { + SubFloorVisuals.SubFloor, + TrayScannerTransparency.Key + }; + + private bool FilterAnchored(EntityUid uid) + { + return EntityManager.TryGetComponent(uid, out var transform) + && transform.Anchored; + } +} diff --git a/Content.Server/SubFloor/TrayScannerSystem.cs b/Content.Server/SubFloor/TrayScannerSystem.cs new file mode 100644 index 0000000000..8f37e528ff --- /dev/null +++ b/Content.Server/SubFloor/TrayScannerSystem.cs @@ -0,0 +1,11 @@ +using Content.Shared.SubFloor; + +namespace Content.Server.SubFloor; + +public class TrayScannerSystem : SharedTrayScannerSystem +{ + public override void Initialize() + { + base.Initialize(); + } +} diff --git a/Content.Shared/SubFloor/SharedTrayScannerSystem.cs b/Content.Shared/SubFloor/SharedTrayScannerSystem.cs new file mode 100644 index 0000000000..7d3c40327c --- /dev/null +++ b/Content.Shared/SubFloor/SharedTrayScannerSystem.cs @@ -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(OnTrayScannerGetState); + SubscribeLocalEvent(OnTrayScannerHandleState); + SubscribeLocalEvent(OnTrayScannerUsed); + SubscribeLocalEvent(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(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 +} diff --git a/Content.Shared/SubFloor/SubFloorHideComponent.cs b/Content.Shared/SubFloor/SubFloorHideComponent.cs index e46c492277..03d598bd53 100644 --- a/Content.Shared/SubFloor/SubFloorHideComponent.cs +++ b/Content.Shared/SubFloor/SubFloorHideComponent.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Robust.Shared.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.Players; @@ -39,6 +40,26 @@ namespace Content.Shared.SubFloor { return new SubFloorHideComponentState(Enabled, RequireAnchored); } + + /// + /// Whether or not this entity is supposed + /// to be visible. + /// + [ViewVariables] + public bool Visible { get; set; } + + /// + /// The entities this subfloor is revealed by. + /// + [ViewVariables] + public HashSet RevealedBy { get; set; } = new(); + + /// + /// Whether or not this entity was revealed with or without + /// an entity. + /// + [ViewVariables] + public bool RevealedWithoutEntity { get; set; } } [Serializable, NetSerializable] diff --git a/Content.Shared/SubFloor/SubFloorHideSystem.cs b/Content.Shared/SubFloor/SubFloorHideSystem.cs index e127431bcf..0d1cab6a57 100644 --- a/Content.Shared/SubFloor/SubFloorHideSystem.cs +++ b/Content.Shared/SubFloor/SubFloorHideSystem.cs @@ -1,9 +1,12 @@ using System; +using System.Collections.Generic; +using Content.Shared.Interaction; using Content.Shared.Maps; using JetBrains.Annotations; using Robust.Shared.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.IoC; +using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Serialization; @@ -46,6 +49,7 @@ namespace Content.Shared.SubFloor SubscribeLocalEvent(OnSubFloorTerminating); SubscribeLocalEvent(HandleAnchorChanged); SubscribeLocalEvent(HandleComponentState); + SubscribeLocalEvent(OnInteractionAttempt); } public override void Shutdown() @@ -70,6 +74,18 @@ namespace Content.Shared.SubFloor 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 _) { UpdateEntity(uid); @@ -153,39 +169,80 @@ namespace Content.Shared.SubFloor } // 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 entities, bool visible, EntityUid? uid = null, IEnumerable? appearanceKeys = null) { - // We raise an event to allow other entity systems to handle this. - 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)) + foreach (var entity in entities) { - // 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 (!EntityManager.HasComponent(entity)) + continue; + + UpdateEntity(entity, visible, uid, appearanceKeys); } + } + + private void UpdateEntity(EntityUid uid, bool subFloor, EntityUid? revealedUid = null, IEnumerable? 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. 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 appearanceKeys) + { // Show sprite 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. 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 - { - public bool SubFloor { get; } - - public SubFloorHideEvent(bool subFloor) - { - SubFloor = subFloor; - } + private static List _defaultVisualizerKeys = new List{ SubFloorVisuals.SubFloor }; } [Serializable, NetSerializable] diff --git a/Content.Shared/SubFloor/TrayScannerComponent.cs b/Content.Shared/SubFloor/TrayScannerComponent.cs new file mode 100644 index 0000000000..9a52872997 --- /dev/null +++ b/Content.Shared/SubFloor/TrayScannerComponent.cs @@ -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 RevealedSubfloors = new(); +} + +[Serializable, NetSerializable] +public sealed class TrayScannerState : ComponentState +{ + public bool Toggled { get; } + + public TrayScannerState(bool toggle) + { + Toggled = toggle; + } +} diff --git a/Resources/Prototypes/Entities/Objects/Tools/t-ray.yml b/Resources/Prototypes/Entities/Objects/Tools/t-ray.yml new file mode 100644 index 0000000000..be3e90b46e --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Tools/t-ray.yml @@ -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 diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/pipes.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/pipes.yml index 3e6c3e098a..cf9a251241 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/pipes.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/pipes.yml @@ -39,6 +39,7 @@ - type: Damageable damageContainer: Inorganic damageModifierSet: Metallic + - type: SubFloorHide - type: Anchorable - type: Rotatable - type: Pullable @@ -58,10 +59,10 @@ visuals: - type: PipeConnectorVisualizer - type: PipeColorVisualizer + - type: TrayScannerSubFloorVisualizer - type: NodeContainer - type: AtmosUnsafeUnanchor - type: AtmosPipeColor - - type: SubFloorHide - type: Tag tags: - Pipe diff --git a/Resources/Prototypes/Entities/Structures/Piping/Disposal/pipes.yml b/Resources/Prototypes/Entities/Structures/Piping/Disposal/pipes.yml index 2a3366d172..c8b9a0d0a3 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Disposal/pipes.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Disposal/pipes.yml @@ -53,6 +53,8 @@ - type: DisposalTransit - type: Appearance visuals: + - type: TrayScannerSubFloorVisualizer + - type: SubFloorShowLayerVisualizer - type: DisposalVisualizer state_free: conpipe-s state_anchored: pipe-s @@ -78,6 +80,8 @@ - type: DisposalTagger - type: Appearance visuals: + - type: TrayScannerSubFloorVisualizer + - type: SubFloorShowLayerVisualizer - type: DisposalVisualizer state_free: conpipe-tagger state_anchored: pipe-tagger @@ -107,6 +111,8 @@ - type: DisposalEntry - type: Appearance visuals: + - type: TrayScannerSubFloorVisualizer + - type: SubFloorShowLayerVisualizer - type: DisposalVisualizer state_free: conpipe-t state_anchored: pipe-t @@ -136,6 +142,8 @@ - 180 - type: Appearance visuals: + - type: TrayScannerSubFloorVisualizer + - type: SubFloorShowLayerVisualizer - type: DisposalVisualizer state_free: conpipe-j1s state_anchored: pipe-j1s @@ -170,6 +178,8 @@ - 180 - type: Appearance visuals: + - type: TrayScannerSubFloorVisualizer + - type: SubFloorShowLayerVisualizer - type: DisposalVisualizer state_free: conpipe-j2s state_anchored: pipe-j2s @@ -201,6 +211,8 @@ - 180 - type: Appearance visuals: + - type: TrayScannerSubFloorVisualizer + - type: SubFloorShowLayerVisualizer - type: DisposalVisualizer state_free: conpipe-j1 state_anchored: pipe-j1 @@ -231,6 +243,8 @@ - 180 - type: Appearance visuals: + - type: TrayScannerSubFloorVisualizer + - type: SubFloorShowLayerVisualizer - type: DisposalVisualizer state_free: conpipe-j2 state_anchored: pipe-j2 @@ -262,6 +276,8 @@ - -90 - type: Appearance visuals: + - type: TrayScannerSubFloorVisualizer + - type: SubFloorShowLayerVisualizer - type: DisposalVisualizer state_free: conpipe-y state_anchored: pipe-y @@ -287,6 +303,8 @@ - type: DisposalBend - type: Appearance visuals: + - type: TrayScannerSubFloorVisualizer + - type: SubFloorShowLayerVisualizer - type: DisposalVisualizer state_free: conpipe-c state_anchored: pipe-c diff --git a/Resources/Prototypes/Entities/Structures/Power/cables.yml b/Resources/Prototypes/Entities/Structures/Power/cables.yml index 55bd5eea9f..5b335535c4 100644 --- a/Resources/Prototypes/Entities/Structures/Power/cables.yml +++ b/Resources/Prototypes/Entities/Structures/Power/cables.yml @@ -77,6 +77,7 @@ acts: [ "Destruction" ] - type: Appearance visuals: + - type: TrayScannerSubFloorVisualizer - type: CableVisualizer base: hvcable_ @@ -117,6 +118,7 @@ acts: [ "Destruction" ] - type: Appearance visuals: + - type: TrayScannerSubFloorVisualizer - type: CableVisualizer base: mvcable_ @@ -160,5 +162,6 @@ acts: [ "Destruction" ] - type: Appearance visuals: + - type: TrayScannerSubFloorVisualizer - type: CableVisualizer base: lvcable_ diff --git a/Resources/Textures/Objects/Tools/t-ray.rsi/meta.json b/Resources/Textures/Objects/Tools/t-ray.rsi/meta.json new file mode 100644 index 0000000000..c1929a1efe --- /dev/null +++ b/Resources/Textures/Objects/Tools/t-ray.rsi/meta.json @@ -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" } + ] +} diff --git a/Resources/Textures/Objects/Tools/t-ray.rsi/tray-off.png b/Resources/Textures/Objects/Tools/t-ray.rsi/tray-off.png new file mode 100644 index 0000000000..d4a6da02cd Binary files /dev/null and b/Resources/Textures/Objects/Tools/t-ray.rsi/tray-off.png differ diff --git a/Resources/Textures/Objects/Tools/t-ray.rsi/tray-on.png b/Resources/Textures/Objects/Tools/t-ray.rsi/tray-on.png new file mode 100644 index 0000000000..5f9c84afd8 Binary files /dev/null and b/Resources/Textures/Objects/Tools/t-ray.rsi/tray-on.png differ