diff --git a/Content.Client/Cargo/CargoSystem.Telepad.cs b/Content.Client/Cargo/CargoSystem.Telepad.cs new file mode 100644 index 0000000000..f369b7cf82 --- /dev/null +++ b/Content.Client/Cargo/CargoSystem.Telepad.cs @@ -0,0 +1,98 @@ +using Content.Shared.Cargo; +using Robust.Client.Animations; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; + +namespace Content.Client.Cargo; + +public sealed partial class CargoSystem +{ + private static readonly Animation CargoTelepadBeamAnimation = new() + { + Length = TimeSpan.FromSeconds(0.5), + AnimationTracks = + { + new AnimationTrackSpriteFlick + { + LayerKey = CargoTelepadLayers.Beam, + KeyFrames = + { + new AnimationTrackSpriteFlick.KeyFrame(new RSI.StateId("beam"), 0f) + } + } + } + }; + + private static readonly Animation CargoTelepadIdleAnimation = new() + { + Length = TimeSpan.FromSeconds(0.8), + AnimationTracks = + { + new AnimationTrackSpriteFlick + { + LayerKey = CargoTelepadLayers.Beam, + KeyFrames = + { + new AnimationTrackSpriteFlick.KeyFrame(new RSI.StateId("idle"), 0f) + } + } + } + }; + + private const string TelepadBeamKey = "cargo-telepad-beam"; + private const string TelepadIdleKey = "cargo-telepad-idle"; + + private void InitializeCargoTelepad() + { + SubscribeLocalEvent(OnCargoAppChange); + SubscribeLocalEvent(OnCargoAnimComplete); + } + + private void OnCargoAppChange(EntityUid uid, CargoTelepadComponent component, ref AppearanceChangeEvent args) + { + OnChangeData(args.Component); + } + + private void OnCargoAnimComplete(EntityUid uid, CargoTelepadComponent component, AnimationCompletedEvent args) + { + if (!TryComp(uid, out var appearance)) return; + + OnChangeData(appearance); + } + + private void OnChangeData(AppearanceComponent component) + { + if (!TryComp(component.Owner, out var sprite)) return; + + component.TryGetData(CargoTelepadVisuals.State, out CargoTelepadState? state); + AnimationPlayerComponent? player = null; + + switch (state) + { + case CargoTelepadState.Teleporting: + if (_player.HasRunningAnimation(component.Owner, TelepadBeamKey)) return; + _player.Stop(component.Owner, player, TelepadIdleKey); + _player.Play(component.Owner, player, CargoTelepadBeamAnimation, TelepadBeamKey); + break; + case CargoTelepadState.Unpowered: + sprite.LayerSetVisible(CargoTelepadLayers.Beam, false); + _player.Stop(component.Owner, player, TelepadBeamKey); + _player.Stop(component.Owner, player, TelepadIdleKey); + break; + default: + sprite.LayerSetVisible(CargoTelepadLayers.Beam, true); + + if (_player.HasRunningAnimation(component.Owner, player, TelepadIdleKey) || + _player.HasRunningAnimation(component.Owner, player, TelepadBeamKey)) return; + + _player.Play(component.Owner, player, CargoTelepadIdleAnimation, TelepadIdleKey); + break; + } + } + + private enum CargoTelepadLayers : byte + { + Base = 0, + Beam = 1, + } +} diff --git a/Content.Client/Cargo/CargoSystem.cs b/Content.Client/Cargo/CargoSystem.cs new file mode 100644 index 0000000000..ce26dc7ecf --- /dev/null +++ b/Content.Client/Cargo/CargoSystem.cs @@ -0,0 +1,15 @@ +using Content.Shared.Cargo; +using Robust.Client.GameObjects; + +namespace Content.Client.Cargo; + +public sealed partial class CargoSystem : SharedCargoSystem +{ + [Dependency] private readonly AnimationPlayerSystem _player = default!; + + public override void Initialize() + { + base.Initialize(); + InitializeCargoTelepad(); + } +} diff --git a/Content.Client/Cargo/CargoTelepadComponent.cs b/Content.Client/Cargo/CargoTelepadComponent.cs new file mode 100644 index 0000000000..884718f783 --- /dev/null +++ b/Content.Client/Cargo/CargoTelepadComponent.cs @@ -0,0 +1,10 @@ +using Content.Shared.Cargo.Components; +using Robust.Shared.GameObjects; + +namespace Content.Client.Cargo; + +[RegisterComponent] +public sealed class CargoTelepadComponent : SharedCargoTelepadComponent +{ + +} diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs index 20bc61f63c..1d0a90ee68 100644 --- a/Content.Client/Entry/IgnoredComponents.cs +++ b/Content.Client/Entry/IgnoredComponents.cs @@ -227,7 +227,6 @@ namespace Content.Client.Entry "MachineFrame", "MachineBoard", "ChemicalAmmo", - "CargoTelepad", "TraitorDeathMatchRedemption", "CanHostGuardian", "SliceableFood", diff --git a/Content.Server/Cargo/CargoConsoleSystem.cs b/Content.Server/Cargo/CargoSystem.Console.cs similarity index 93% rename from Content.Server/Cargo/CargoConsoleSystem.cs rename to Content.Server/Cargo/CargoSystem.Console.cs index 317fa8b9d0..fd6a76c4c0 100644 --- a/Content.Server/Cargo/CargoConsoleSystem.cs +++ b/Content.Server/Cargo/CargoSystem.Console.cs @@ -11,7 +11,7 @@ using Robust.Shared.IoC; namespace Content.Server.Cargo { - public class CargoConsoleSystem : EntitySystem + public sealed partial class CargoSystem { /// /// How much time to wait (in seconds) before increasing bank accounts balance. @@ -50,36 +50,41 @@ namespace Content.Server.Cargo [Dependency] private readonly IdCardSystem _idCardSystem = default!; [Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!; - public override void Initialize() + private void InitializeConsole() { SubscribeLocalEvent(Reset); - - CreateBankAccount("Space Station 14", 1000); - CreateOrderDatabase(0); + Reset(); } - public override void Update(float frameTime) + private void Reset(RoundRestartCleanupEvent ev) { - _timer += frameTime; - if (_timer < Delay) - { - return; - } - - _timer -= Delay; - foreach (var account in BankAccounts) - { - account.Balance += PointIncrease; - } + Reset(); } - public void Reset(RoundRestartCleanupEvent ev) + private void Reset() { _accountsDict.Clear(); _databasesDict.Clear(); _timer = 0; _accountIndex = 0; - Initialize(); + + CreateBankAccount("Space Station 14", 1000); + CreateOrderDatabase(0); + } + + private void UpdateConsole(float frameTime) + { + _timer += frameTime; + + while (_timer > Delay) + { + _timer -= Delay; + + foreach (var account in BankAccounts) + { + account.Balance += PointIncrease; + } + } } /// diff --git a/Content.Server/Cargo/CargoSystem.Telepad.cs b/Content.Server/Cargo/CargoSystem.Telepad.cs new file mode 100644 index 0000000000..b4abde682f --- /dev/null +++ b/Content.Server/Cargo/CargoSystem.Telepad.cs @@ -0,0 +1,130 @@ +using Content.Server.Cargo.Components; +using Content.Server.Labels.Components; +using Content.Server.Paper; +using Content.Server.Power.Components; +using Content.Shared.Cargo; +using Robust.Shared.Audio; +using Robust.Shared.GameObjects; +using Robust.Shared.Localization; +using Robust.Shared.Player; + +namespace Content.Server.Cargo; + +public sealed partial class CargoSystem +{ + private void InitializeTelepad() + { + SubscribeLocalEvent(OnTelepadPowerChange); + SubscribeLocalEvent(OnTelepadAnchorChange); + } + + private void UpdateTelepad(float frameTime) + { + foreach (var comp in EntityManager.EntityQuery()) + { + // Don't EntityQuery for it as it's not required. + TryComp(comp.Owner, out var appearance); + + if (comp.CurrentState == CargoTelepadState.Unpowered || comp.TeleportQueue.Count <= 0) + { + comp.CurrentState = CargoTelepadState.Idle; + appearance?.SetData(CargoTelepadVisuals.State, CargoTelepadState.Idle); + comp.Accumulator = comp.Delay; + continue; + } + + comp.Accumulator -= frameTime; + + // Uhh listen teleporting takes time and I just want the 1 float. + if (comp.Accumulator > 0f) + { + comp.CurrentState = CargoTelepadState.Idle; + appearance?.SetData(CargoTelepadVisuals.State, CargoTelepadState.Idle); + continue; + } + + var product = comp.TeleportQueue.Pop(); + + SoundSystem.Play(Filter.Pvs(comp.Owner), comp.TeleportSound.GetSound(), comp.Owner, AudioParams.Default.WithVolume(-8f)); + SpawnProduct(comp, product); + + comp.CurrentState = CargoTelepadState.Teleporting; + appearance?.SetData(CargoTelepadVisuals.State, CargoTelepadState.Teleporting); + comp.Accumulator = comp.Delay; + } + } + + private void SetEnabled(CargoTelepadComponent component, ApcPowerReceiverComponent? receiver = null, + TransformComponent? xform = null) + { + // False due to AllCompsOneEntity test where they may not have the powerreceiver. + if (!Resolve(component.Owner, ref receiver, ref xform, false)) return; + + var disabled = !receiver.Powered || !xform.Anchored; + + // Setting idle state should be handled by Update(); + if (disabled) return; + + TryComp(component.Owner, out var appearance); + component.CurrentState = CargoTelepadState.Unpowered; + appearance?.SetData(CargoTelepadVisuals.State, CargoTelepadState.Unpowered); + } + + private void OnTelepadPowerChange(EntityUid uid, CargoTelepadComponent component, PowerChangedEvent args) + { + SetEnabled(component); + } + + private void OnTelepadAnchorChange(EntityUid uid, CargoTelepadComponent component, ref AnchorStateChangedEvent args) + { + SetEnabled(component); + } + + public void QueueTeleport(CargoTelepadComponent component, CargoOrderData order) + { + for (var i = 0; i < order.Amount; i++) + { + component.TeleportQueue.Push(order); + } + } + + /// + /// Spawn the product and a piece of paper. Attempt to attach the paper to the product. + /// + private void SpawnProduct(CargoTelepadComponent component, CargoOrderData data) + { + // spawn the order + if (!_protoMan.TryIndex(data.ProductId, out CargoProductPrototype? prototype)) + return; + + var xform = Transform(component.Owner); + + var product = EntityManager.SpawnEntity(prototype.Product, xform.Coordinates); + + Transform(product).Anchored = false; + + // spawn a piece of paper. + var printed = EntityManager.SpawnEntity(component.PrinterOutput, xform.Coordinates); + + if (!TryComp(printed, out var paper)) + return; + + // fill in the order data + var val = Loc.GetString("cargo-console-paper-print-name", ("orderNumber", data.OrderNumber)); + + MetaData(printed).EntityName = val; + + paper.SetContent(Loc.GetString( + "cargo-console-paper-print-text", + ("orderNumber", data.OrderNumber), + ("requester", data.Requester), + ("reason", data.Reason), + ("approver", data.Approver))); + + // attempt to attach the label + if (TryComp(product, out var label)) + { + _slots.TryInsert(component.Owner, label.LabelSlot, printed, null); + } + } +} diff --git a/Content.Server/Cargo/CargoSystem.cs b/Content.Server/Cargo/CargoSystem.cs new file mode 100644 index 0000000000..e387569615 --- /dev/null +++ b/Content.Server/Cargo/CargoSystem.cs @@ -0,0 +1,27 @@ +using Content.Shared.Cargo; +using Content.Shared.Containers.ItemSlots; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Prototypes; + +namespace Content.Server.Cargo; + +public sealed partial class CargoSystem : SharedCargoSystem +{ + [Dependency] private readonly IPrototypeManager _protoMan = default!; + [Dependency] private readonly ItemSlotsSystem _slots = default!; + + public override void Initialize() + { + base.Initialize(); + InitializeConsole(); + InitializeTelepad(); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + UpdateConsole(frameTime); + UpdateTelepad(frameTime); + } +} diff --git a/Content.Server/Cargo/Components/CargoConsoleComponent.cs b/Content.Server/Cargo/Components/CargoConsoleComponent.cs index c713623b78..b5aaf4c4d8 100644 --- a/Content.Server/Cargo/Components/CargoConsoleComponent.cs +++ b/Content.Server/Cargo/Components/CargoConsoleComponent.cs @@ -60,7 +60,7 @@ namespace Content.Server.Cargo.Components private SoundSpecifier _errorSound = new SoundPathSpecifier("/Audio/Effects/error.ogg"); private bool Powered => !_entMan.TryGetComponent(Owner, out ApcPowerReceiverComponent? receiver) || receiver.Powered; - private CargoConsoleSystem _cargoConsoleSystem = default!; + private CargoSystem _cargoConsoleSystem = default!; [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(CargoConsoleUiKey.Key); @@ -76,7 +76,7 @@ namespace Content.Server.Cargo.Components UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; } - _cargoConsoleSystem = EntitySystem.Get(); + _cargoConsoleSystem = EntitySystem.Get(); BankAccount = _cargoConsoleSystem.StationAccount; } @@ -188,7 +188,7 @@ namespace Content.Server.Cargo.Components orders.Database.ClearOrderCapacity(); foreach (var order in approvedOrders) { - telepadComponent.QueueTeleport(order); + _cargoConsoleSystem.QueueTeleport(telepadComponent, order); } } } diff --git a/Content.Server/Cargo/Components/CargoOrderDatabaseComponent.cs b/Content.Server/Cargo/Components/CargoOrderDatabaseComponent.cs index 57760739c4..c184a0fe12 100644 --- a/Content.Server/Cargo/Components/CargoOrderDatabaseComponent.cs +++ b/Content.Server/Cargo/Components/CargoOrderDatabaseComponent.cs @@ -14,7 +14,7 @@ namespace Content.Server.Cargo.Components { base.Initialize(); - Database = EntitySystem.Get().StationOrderDatabase; + Database = EntitySystem.Get().StationOrderDatabase; } public override ComponentState GetComponentState() diff --git a/Content.Server/Cargo/Components/CargoTelepadComponent.cs b/Content.Server/Cargo/Components/CargoTelepadComponent.cs index e7314571e0..cab5b63576 100644 --- a/Content.Server/Cargo/Components/CargoTelepadComponent.cs +++ b/Content.Server/Cargo/Components/CargoTelepadComponent.cs @@ -1,150 +1,38 @@ -using System; -using System.Collections.Generic; -using Content.Server.Labels.Components; -using Content.Server.Paper; -using Content.Server.Power.Components; using Content.Shared.Cargo; -using Content.Shared.Containers.ItemSlots; +using Content.Shared.Cargo.Components; using Content.Shared.Sound; -using Robust.Server.GameObjects; -using Robust.Shared.Audio; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Player; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Server.Cargo.Components { - - //This entire class is a PLACEHOLDER for the cargo shuttle. - //welp only need auto-docking now. - - [RegisterComponent] - public class CargoTelepadComponent : Component + /// + /// Handles teleporting in requested cargo after the specified delay. + /// + [RegisterComponent, Friend(typeof(CargoSystem))] + public sealed class CargoTelepadComponent : SharedCargoTelepadComponent { - [Dependency] private readonly IEntityManager _entMan = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [DataField("delay")] + public float Delay = 20f; - private const float TeleportDuration = 0.5f; - private const float TeleportDelay = 15f; - private List _teleportQueue = new(); - private CargoTelepadState _currentState = CargoTelepadState.Unpowered; - [DataField("teleportSound")] private SoundSpecifier _teleportSound = new SoundPathSpecifier("/Audio/Machines/phasein.ogg"); + /// + /// How much time we've accumulated until next teleport. + /// + [ViewVariables] + public float Accumulator = 0f; + + [ViewVariables] + public readonly Stack TeleportQueue = new(); + + [ViewVariables] + public CargoTelepadState CurrentState = CargoTelepadState.Unpowered; + + [DataField("teleportSound")] public SoundSpecifier TeleportSound = new SoundPathSpecifier("/Audio/Machines/phasein.ogg"); /// /// The paper-type prototype to spawn with the order information. /// [DataField("printerOutput", customTypeSerializer: typeof(PrototypeIdSerializer))] public string PrinterOutput = "Paper"; - - [Obsolete("Component Messages are deprecated, use Entity Events instead.")] - public override void HandleMessage(ComponentMessage message, IComponent? component) - { -#pragma warning disable 618 - base.HandleMessage(message, component); -#pragma warning restore 618 - switch (message) - { - case PowerChangedMessage powerChanged: - PowerUpdate(powerChanged); - break; - } - } - - public void QueueTeleport(CargoOrderData order) - { - for (var i = 0; i < order.Amount; i++) - { - _teleportQueue.Add(order); - } - TeleportLoop(); - } - - private void PowerUpdate(PowerChangedMessage args) - { - if (args.Powered && _currentState == CargoTelepadState.Unpowered) { - _currentState = CargoTelepadState.Idle; - if(_entMan.TryGetComponent(Owner, out var spriteComponent) && spriteComponent.LayerCount > 0) - spriteComponent.LayerSetState(0, "idle"); - TeleportLoop(); - } - else if (!args.Powered) - { - _currentState = CargoTelepadState.Unpowered; - if (_entMan.TryGetComponent(Owner, out var spriteComponent) && spriteComponent.LayerCount > 0) - spriteComponent.LayerSetState(0, "offline"); - } - } - private void TeleportLoop() - { - if (_currentState == CargoTelepadState.Idle && _teleportQueue.Count > 0) - { - _currentState = CargoTelepadState.Charging; - if (_entMan.TryGetComponent(Owner, out var spriteComponent) && spriteComponent.LayerCount > 0) - spriteComponent.LayerSetState(0, "idle"); - Owner.SpawnTimer((int) (TeleportDelay * 1000), () => - { - if (!Deleted && !_entMan.Deleted(Owner) && _currentState == CargoTelepadState.Charging && _teleportQueue.Count > 0) - { - _currentState = CargoTelepadState.Teleporting; - if (_entMan.TryGetComponent(Owner, out var spriteComponent) && spriteComponent.LayerCount > 0) - spriteComponent.LayerSetState(0, "beam"); - Owner.SpawnTimer((int) (TeleportDuration * 1000), () => - { - if (!Deleted && !((!_entMan.EntityExists(Owner) ? EntityLifeStage.Deleted : _entMan.GetComponent(Owner).EntityLifeStage) >= EntityLifeStage.Deleted) && _currentState == CargoTelepadState.Teleporting && _teleportQueue.Count > 0) - { - SoundSystem.Play(Filter.Pvs(Owner), _teleportSound.GetSound(), Owner, AudioParams.Default.WithVolume(-8f)); - SpawnProduct(_teleportQueue[0]); - _teleportQueue.RemoveAt(0); - if (_entMan.TryGetComponent(Owner, out var spriteComponent) && spriteComponent.LayerCount > 0) - spriteComponent.LayerSetState(0, "idle"); - _currentState = CargoTelepadState.Idle; - TeleportLoop(); - } - }); - } - }); - } - } - - /// - /// Spawn the product and a piece of paper. Attempt to attach the paper to the product. - /// - private void SpawnProduct(CargoOrderData data) - { - // spawn the order - if (!_prototypeManager.TryIndex(data.ProductId, out CargoProductPrototype? prototype)) - return; - - var product = _entMan.SpawnEntity(prototype.Product, _entMan.GetComponent(Owner).Coordinates); - - _entMan.GetComponent(product).Anchored = false; - - // spawn a piece of paper. - var printed = _entMan.SpawnEntity(PrinterOutput, _entMan.GetComponent(Owner).Coordinates); - if (!_entMan.TryGetComponent(printed, out PaperComponent paper)) - return; - - // fill in the order data - string val = Loc.GetString("cargo-console-paper-print-name", ("orderNumber", data.OrderNumber)); - _entMan.GetComponent(printed).EntityName = val; - paper.SetContent(Loc.GetString( - "cargo-console-paper-print-text", - ("orderNumber", data.OrderNumber), - ("requester", data.Requester), - ("reason", data.Reason), - ("approver", data.Approver))); - - // attempt to attach the label - if (_entMan.TryGetComponent(product, out PaperLabelComponent label)) - { - EntitySystem.Get().TryInsert(Owner, label.LabelSlot, printed, null); - } - } - - private enum CargoTelepadState { Unpowered, Idle, Charging, Teleporting }; } } diff --git a/Content.Shared/Cargo/Components/SharedCargoTelepadComponent.cs b/Content.Shared/Cargo/Components/SharedCargoTelepadComponent.cs new file mode 100644 index 0000000000..6b8df740e6 --- /dev/null +++ b/Content.Shared/Cargo/Components/SharedCargoTelepadComponent.cs @@ -0,0 +1,8 @@ +using Robust.Shared.GameObjects; + +namespace Content.Shared.Cargo.Components; + +public abstract class SharedCargoTelepadComponent : Component +{ + +} diff --git a/Content.Shared/Cargo/SharedCargoSystem.cs b/Content.Shared/Cargo/SharedCargoSystem.cs new file mode 100644 index 0000000000..eba723261c --- /dev/null +++ b/Content.Shared/Cargo/SharedCargoSystem.cs @@ -0,0 +1,21 @@ +using System; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; + +namespace Content.Shared.Cargo; + +public abstract class SharedCargoSystem : EntitySystem {} + +[Serializable, NetSerializable] +public enum CargoTelepadState : byte +{ + Unpowered, + Idle, + Teleporting, +}; + +[Serializable, NetSerializable] +public enum CargoTelepadVisuals : byte +{ + State, +}; diff --git a/Resources/Prototypes/Entities/Structures/cargo_telepad.yml b/Resources/Prototypes/Entities/Structures/cargo_telepad.yml index 8bf778eb4a..7004fc8025 100644 --- a/Resources/Prototypes/Entities/Structures/cargo_telepad.yml +++ b/Resources/Prototypes/Entities/Structures/cargo_telepad.yml @@ -6,7 +6,7 @@ components: - type: InteractionOutline - type: Physics - bodyType: Static + bodyType: Static - type: Transform anchored: true - type: Fixtures @@ -18,9 +18,15 @@ layer: - Passable - type: Sprite + netsync: false sprite: Structures/cargo_telepad.rsi - state: offline drawdepth: FloorObjects + layers: + - state: offline + map: ["enum.CargoTelepadLayers.Base"] + - state: idle + map: [ "enum.CargoTelepadLayers.Beam" ] + shader: unshaded - type: Damageable damageContainer: Inorganic damageModifierSet: Metallic @@ -40,3 +46,4 @@ - type: ApcPowerReceiver - type: ExtensionCableReceiver - type: CargoTelepad + - type: Appearance diff --git a/Resources/Textures/Structures/cargo_telepad.rsi/beam.png b/Resources/Textures/Structures/cargo_telepad.rsi/beam.png index 08e4dd0eda..2f571bcd3d 100644 Binary files a/Resources/Textures/Structures/cargo_telepad.rsi/beam.png and b/Resources/Textures/Structures/cargo_telepad.rsi/beam.png differ diff --git a/Resources/Textures/Structures/cargo_telepad.rsi/idle.png b/Resources/Textures/Structures/cargo_telepad.rsi/idle.png index bf79fade7d..328ce81383 100644 Binary files a/Resources/Textures/Structures/cargo_telepad.rsi/idle.png and b/Resources/Textures/Structures/cargo_telepad.rsi/idle.png differ diff --git a/Resources/Textures/Structures/cargo_telepad.rsi/meta.json b/Resources/Textures/Structures/cargo_telepad.rsi/meta.json index e924ea5fbd..78aca01881 100644 --- a/Resources/Textures/Structures/cargo_telepad.rsi/meta.json +++ b/Resources/Textures/Structures/cargo_telepad.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from /vg/station at commit 5c50dee8fb2a55d6be3b3c9c90a782a618a5506e", + "copyright": "Taken from /vg/station at commit 5c50dee8fb2a55d6be3b3c9c90a782a618a5506e Cut out for unshaded by metalgearsloth", "size": { "x": 32, "y": 32