From cb06c41fc07275e1f15af916babb44368c0c26c2 Mon Sep 17 00:00:00 2001 From: Eris Date: Sat, 25 Jan 2025 12:17:44 -0500 Subject: [PATCH] MODsuits (Port From Goob #1242) (#1640) # Description Ports MODsuits from Goobstation PR https://github.com/Goob-Station/Goob-Station/pull/1242. The PR author has confirmed that he is okay with me doing this. --- # TODO - [X] Port in sprites - [x] Port in YMLs - [X] Port code - [x] Port code PATCHES - [x] Update EE with required fixes ---

Media

## Modsuit crafting https://github.com/user-attachments/assets/8ff03d3a-0fc1-4818-b710-bfc43f0e2a68 ## Modsuit sealing https://github.com/user-attachments/assets/6671459a-7767-499b-8678-062fc1db7134

--- # Changelog :cl: - add: Modsuits have been ported from Goobstation! --------- Signed-off-by: Eris Co-authored-by: DEATHB4DEFEAT <77995199+DEATHB4DEFEAT@users.noreply.github.com> Co-authored-by: VMSolidus --- Content.Client/DoAfter/DoAfterOverlay.cs | 3 +- .../SealableClothingVisualsComponent.cs | 11 + .../PoweredSealableClothingSystem.cs | 7 + .../EntitySystems/SealableClothingSystem.cs | 7 + .../SealableClothingVisualizerSystem.cs | 57 +++ .../ToggleableClothingBoundUserInterface.cs | 39 ++ .../ToggleableClothingRadialMenu.xaml | 6 + .../ToggleableClothingRadialMenu.xaml.cs | 96 ++++ .../Atmos/EntitySystems/BarotraumaSystem.cs | 33 +- .../Body/Components/LungComponent.cs | 6 + Content.Server/Body/Systems/LungSystem.cs | 42 +- .../IdentityManagement/IdentitySystem.cs | 16 + .../Power/EntitySystems/ChargerSystem.cs | 43 +- .../Systems/PoweredSealableClothingSystem.cs | 112 +++++ .../Systems/SealableClothingSystem.cs | 5 + .../Actions/ConfirmableActionComponent.cs | 12 +- .../Actions/ConfirmableActionSystem.cs | 11 +- Content.Shared/Armor/ArmorComponent.cs | 7 +- .../Components/AttachedClothingComponent.cs | 13 + .../Components/ToggleableClothingComponent.cs | 65 ++- .../EntitySystems/ToggleableClothingSystem.cs | 450 ++++++++++++++---- Content.Shared/DoAfter/DoAfterArgs.cs | 68 ++- Content.Shared/DoAfter/SharedDoAfterSystem.cs | 1 + .../Inventory/InventorySystem.Helpers.cs | 13 + .../Item/ItemToggle/ComponentTogglerSystem.cs | 17 +- .../Components/SealableClothingComponent.cs | 30 ++ .../SealableClothingControlComponent.cs | 75 +++ .../SealableClothingRequiresPowerComponent.cs | 30 ++ .../Clothing/SealableClothingVisuals.cs | 9 + .../SharedPoweredSealableClothingSystem.cs | 73 +++ .../Systems/SharedSealableClothingSystem.cs | 392 +++++++++++++++ .../ItemSlotsRequirePanelComponent.cs | 14 + .../Wires/Systems/RequirePanelSystem.cs | 37 ++ Resources/Audio/Machines/attributions.yml | 7 +- Resources/Audio/Machines/scanbuzz.ogg | Bin 0 -> 11613 bytes .../_Goobstation/Machines/attributions.yml | 4 + .../_Goobstation/Machines/computer_end.ogg | Bin 0 -> 29626 bytes .../Audio/_Goobstation/Mecha/attributions.yml | 4 + .../Audio/_Goobstation/Mecha/nominal.ogg | Bin 0 -> 17189 bytes .../_Goobstation/alert-levels/alerts.ftl | 2 + .../clothing/sealable-clothing-component.ftl | 23 + .../en-US/_Goobstation/forensics/fibers.ftl | 1 + .../_Goobstation/lathe/lathe-categories.ftl | 1 + .../_Goobstation/research/technologies.ftl | 1 + .../toggleable-clothing-component.ftl | 3 + Resources/Maps/glacier.yml | 2 +- Resources/Prototypes/Alerts/alerts.yml | 3 +- .../Entities/Clothing/Neck/cloaks.yml | 8 +- .../OuterClothing/base_clothingouter.yml | 6 +- .../Clothing/OuterClothing/softsuits.yml | 2 +- .../Entities/Clothing/OuterClothing/suits.yml | 10 +- .../Clothing/OuterClothing/wintercoats.yml | 2 +- .../Entities/Structures/Machines/lathe.yml | 10 + .../Entities/Structures/Power/chargers.yml | 1 + .../Entities/Clothing/Cult/armor.yml | 2 +- .../_Goobstation/Actions/clothing.yml | 20 + .../Prototypes/_Goobstation/Alerts/alerts.yml | 19 + .../Catalog/Cargo/cargo_science.yml | 9 + .../Catalog/Fills/Crates/science.yml | 10 + .../Entities/Clothing/Back/modsuit.yml | 92 ++++ .../Entities/Clothing/Hands/modsuit.yml | 32 ++ .../Entities/Clothing/Head/modsuit.yml | 128 +++++ .../Clothing/OuterClothing/modsuit.yml | 47 ++ .../Entities/Clothing/Shoes/modsuit.yml | 33 ++ .../Specific/Robotics/modsuit_parts.yml | 162 +++++++ .../Construction/Graphs/clothing/modsuit.yml | 113 +++++ .../Recipes/Lathes/categories.yml | 4 + .../_Goobstation/Recipes/Lathes/robotics.yml | 50 ++ .../_Goobstation/Research/experimental.yml | 19 + Resources/Prototypes/_Goobstation/tags.yml | 30 +- .../Actions/modsuit.rsi/activate-ready.png | Bin 0 -> 811 bytes .../Actions/modsuit.rsi/activate.png | Bin 0 -> 252 bytes .../Actions/modsuit.rsi/meta.json | 17 + .../Modsuits/standard.rsi/control-sealed.png | Bin 0 -> 390 bytes .../Back/Modsuits/standard.rsi/control.png | Bin 0 -> 238 bytes .../standard.rsi/equipped-BACKPACK-sealed.png | Bin 0 -> 392 bytes .../standard.rsi/equipped-BACKPACK.png | Bin 0 -> 673 bytes .../Back/Modsuits/standard.rsi/meta.json | 26 + .../standard.rsi/equipped-HAND-sealed.png | Bin 0 -> 145 bytes .../Modsuits/standard.rsi/equipped-HAND.png | Bin 0 -> 496 bytes .../standard.rsi/gauntlets-sealed.png | Bin 0 -> 277 bytes .../Hands/Modsuits/standard.rsi/gauntlets.png | Bin 0 -> 270 bytes .../Hands/Modsuits/standard.rsi/meta.json | 25 + .../standard.rsi/equipped-HEAD-sealed.png | Bin 0 -> 763 bytes .../Modsuits/standard.rsi/equipped-HEAD.png | Bin 0 -> 474 bytes .../Modsuits/standard.rsi/helmet-sealed.png | Bin 0 -> 315 bytes .../Head/Modsuits/standard.rsi/helmet.png | Bin 0 -> 365 bytes .../Head/Modsuits/standard.rsi/meta.json | 25 + .../standard.rsi/chestplate-sealed.png | Bin 0 -> 110 bytes .../Modsuits/standard.rsi/chestplate.png | Bin 0 -> 428 bytes .../equipped-OUTERCLOTHING-sealed.png | Bin 0 -> 130 bytes .../standard.rsi/equipped-OUTERCLOTHING.png | Bin 0 -> 1093 bytes .../Modsuits/standard.rsi/meta.json | 25 + .../Modsuits/standard.rsi/boots-sealed.png | Bin 0 -> 109 bytes .../Shoes/Modsuits/standard.rsi/boots.png | Bin 0 -> 307 bytes .../standard.rsi/equipped-FEET-sealed.png | Bin 0 -> 131 bytes .../Modsuits/standard.rsi/equipped-FEET.png | Bin 0 -> 501 bytes .../Shoes/Modsuits/standard.rsi/meta.json | 25 + .../Interface/Alerts/modpower.rsi/meta.json | 29 ++ .../Alerts/modpower.rsi/modpower0.png | Bin 0 -> 324 bytes .../Alerts/modpower.rsi/modpower1.png | Bin 0 -> 431 bytes .../Alerts/modpower.rsi/modpower2.png | Bin 0 -> 415 bytes .../Alerts/modpower.rsi/modpower3.png | Bin 0 -> 428 bytes .../Alerts/modpower.rsi/modpower4.png | Bin 0 -> 390 bytes .../Alerts/modpower.rsi/modpower5.png | Bin 0 -> 390 bytes .../Robotics/modsuit_parts.rsi/boots.png | Bin 0 -> 271 bytes .../Robotics/modsuit_parts.rsi/chestplate.png | Bin 0 -> 367 bytes .../Robotics/modsuit_parts.rsi/gauntlets.png | Bin 0 -> 257 bytes .../Robotics/modsuit_parts.rsi/helmet.png | Bin 0 -> 362 bytes .../Robotics/modsuit_parts.rsi/meta.json | 56 +++ .../modsuit_parts.rsi/mod-core-standard.png | Bin 0 -> 922 bytes .../modsuit_parts.rsi/shell-boots.png | Bin 0 -> 474 bytes .../modsuit_parts.rsi/shell-chestplate.png | Bin 0 -> 448 bytes .../modsuit_parts.rsi/shell-core-secured.png | Bin 0 -> 687 bytes .../Robotics/modsuit_parts.rsi/shell-core.png | Bin 0 -> 708 bytes .../modsuit_parts.rsi/shell-gauntlets.png | Bin 0 -> 470 bytes .../modsuit_parts.rsi/shell-helmet.png | Bin 0 -> 462 bytes .../modsuit_parts.rsi/shell-secured.png | Bin 0 -> 504 bytes .../Robotics/modsuit_parts.rsi/shell.png | Bin 0 -> 431 bytes .../modsuit_parts.rsi/standard-plating.png | Bin 0 -> 212 bytes 120 files changed, 2704 insertions(+), 184 deletions(-) create mode 100644 Content.Client/_Goobstation/Clothing/Components/SealableClothingVisualsComponent.cs create mode 100644 Content.Client/_Goobstation/Clothing/EntitySystems/PoweredSealableClothingSystem.cs create mode 100644 Content.Client/_Goobstation/Clothing/EntitySystems/SealableClothingSystem.cs create mode 100644 Content.Client/_Goobstation/Clothing/EntitySystems/SealableClothingVisualizerSystem.cs create mode 100644 Content.Client/_Goobstation/Clothing/ToggleableClothingBoundUserInterface.cs create mode 100644 Content.Client/_Goobstation/Clothing/ToggleableClothingRadialMenu.xaml create mode 100644 Content.Client/_Goobstation/Clothing/ToggleableClothingRadialMenu.xaml.cs create mode 100644 Content.Server/_Goobstation/Clothing/Systems/PoweredSealableClothingSystem.cs create mode 100644 Content.Server/_Goobstation/Clothing/Systems/SealableClothingSystem.cs create mode 100644 Content.Shared/_Goobstation/Clothing/Components/SealableClothingComponent.cs create mode 100644 Content.Shared/_Goobstation/Clothing/Components/SealableClothingControlComponent.cs create mode 100644 Content.Shared/_Goobstation/Clothing/Components/SealableClothingRequiresPowerComponent.cs create mode 100644 Content.Shared/_Goobstation/Clothing/SealableClothingVisuals.cs create mode 100644 Content.Shared/_Goobstation/Clothing/Systems/SharedPoweredSealableClothingSystem.cs create mode 100644 Content.Shared/_Goobstation/Clothing/Systems/SharedSealableClothingSystem.cs create mode 100644 Content.Shared/_Goobstation/Wires/Components/ItemSlotsRequirePanelComponent.cs create mode 100644 Content.Shared/_Goobstation/Wires/Systems/RequirePanelSystem.cs create mode 100644 Resources/Audio/Machines/scanbuzz.ogg create mode 100644 Resources/Audio/_Goobstation/Machines/attributions.yml create mode 100644 Resources/Audio/_Goobstation/Machines/computer_end.ogg create mode 100644 Resources/Audio/_Goobstation/Mecha/attributions.yml create mode 100644 Resources/Audio/_Goobstation/Mecha/nominal.ogg create mode 100644 Resources/Locale/en-US/_Goobstation/alert-levels/alerts.ftl create mode 100644 Resources/Locale/en-US/_Goobstation/clothing/sealable-clothing-component.ftl create mode 100644 Resources/Locale/en-US/_Goobstation/forensics/fibers.ftl create mode 100644 Resources/Prototypes/_Goobstation/Actions/clothing.yml create mode 100644 Resources/Prototypes/_Goobstation/Alerts/alerts.yml create mode 100644 Resources/Prototypes/_Goobstation/Catalog/Cargo/cargo_science.yml create mode 100644 Resources/Prototypes/_Goobstation/Catalog/Fills/Crates/science.yml create mode 100644 Resources/Prototypes/_Goobstation/Entities/Clothing/Back/modsuit.yml create mode 100644 Resources/Prototypes/_Goobstation/Entities/Clothing/Hands/modsuit.yml create mode 100644 Resources/Prototypes/_Goobstation/Entities/Clothing/Head/modsuit.yml create mode 100644 Resources/Prototypes/_Goobstation/Entities/Clothing/OuterClothing/modsuit.yml create mode 100644 Resources/Prototypes/_Goobstation/Entities/Clothing/Shoes/modsuit.yml create mode 100644 Resources/Prototypes/_Goobstation/Entities/Objects/Specific/Robotics/modsuit_parts.yml create mode 100644 Resources/Prototypes/_Goobstation/Recipes/Construction/Graphs/clothing/modsuit.yml create mode 100644 Resources/Prototypes/_Goobstation/Recipes/Lathes/robotics.yml create mode 100644 Resources/Prototypes/_Goobstation/Research/experimental.yml create mode 100644 Resources/Textures/_Goobstation/Actions/modsuit.rsi/activate-ready.png create mode 100644 Resources/Textures/_Goobstation/Actions/modsuit.rsi/activate.png create mode 100644 Resources/Textures/_Goobstation/Actions/modsuit.rsi/meta.json create mode 100644 Resources/Textures/_Goobstation/Clothing/Back/Modsuits/standard.rsi/control-sealed.png create mode 100644 Resources/Textures/_Goobstation/Clothing/Back/Modsuits/standard.rsi/control.png create mode 100644 Resources/Textures/_Goobstation/Clothing/Back/Modsuits/standard.rsi/equipped-BACKPACK-sealed.png create mode 100644 Resources/Textures/_Goobstation/Clothing/Back/Modsuits/standard.rsi/equipped-BACKPACK.png create mode 100644 Resources/Textures/_Goobstation/Clothing/Back/Modsuits/standard.rsi/meta.json create mode 100644 Resources/Textures/_Goobstation/Clothing/Hands/Modsuits/standard.rsi/equipped-HAND-sealed.png create mode 100644 Resources/Textures/_Goobstation/Clothing/Hands/Modsuits/standard.rsi/equipped-HAND.png create mode 100644 Resources/Textures/_Goobstation/Clothing/Hands/Modsuits/standard.rsi/gauntlets-sealed.png create mode 100644 Resources/Textures/_Goobstation/Clothing/Hands/Modsuits/standard.rsi/gauntlets.png create mode 100644 Resources/Textures/_Goobstation/Clothing/Hands/Modsuits/standard.rsi/meta.json create mode 100644 Resources/Textures/_Goobstation/Clothing/Head/Modsuits/standard.rsi/equipped-HEAD-sealed.png create mode 100644 Resources/Textures/_Goobstation/Clothing/Head/Modsuits/standard.rsi/equipped-HEAD.png create mode 100644 Resources/Textures/_Goobstation/Clothing/Head/Modsuits/standard.rsi/helmet-sealed.png create mode 100644 Resources/Textures/_Goobstation/Clothing/Head/Modsuits/standard.rsi/helmet.png create mode 100644 Resources/Textures/_Goobstation/Clothing/Head/Modsuits/standard.rsi/meta.json create mode 100644 Resources/Textures/_Goobstation/Clothing/OuterClothing/Modsuits/standard.rsi/chestplate-sealed.png create mode 100644 Resources/Textures/_Goobstation/Clothing/OuterClothing/Modsuits/standard.rsi/chestplate.png create mode 100644 Resources/Textures/_Goobstation/Clothing/OuterClothing/Modsuits/standard.rsi/equipped-OUTERCLOTHING-sealed.png create mode 100644 Resources/Textures/_Goobstation/Clothing/OuterClothing/Modsuits/standard.rsi/equipped-OUTERCLOTHING.png create mode 100644 Resources/Textures/_Goobstation/Clothing/OuterClothing/Modsuits/standard.rsi/meta.json create mode 100644 Resources/Textures/_Goobstation/Clothing/Shoes/Modsuits/standard.rsi/boots-sealed.png create mode 100644 Resources/Textures/_Goobstation/Clothing/Shoes/Modsuits/standard.rsi/boots.png create mode 100644 Resources/Textures/_Goobstation/Clothing/Shoes/Modsuits/standard.rsi/equipped-FEET-sealed.png create mode 100644 Resources/Textures/_Goobstation/Clothing/Shoes/Modsuits/standard.rsi/equipped-FEET.png create mode 100644 Resources/Textures/_Goobstation/Clothing/Shoes/Modsuits/standard.rsi/meta.json create mode 100644 Resources/Textures/_Goobstation/Interface/Alerts/modpower.rsi/meta.json create mode 100644 Resources/Textures/_Goobstation/Interface/Alerts/modpower.rsi/modpower0.png create mode 100644 Resources/Textures/_Goobstation/Interface/Alerts/modpower.rsi/modpower1.png create mode 100644 Resources/Textures/_Goobstation/Interface/Alerts/modpower.rsi/modpower2.png create mode 100644 Resources/Textures/_Goobstation/Interface/Alerts/modpower.rsi/modpower3.png create mode 100644 Resources/Textures/_Goobstation/Interface/Alerts/modpower.rsi/modpower4.png create mode 100644 Resources/Textures/_Goobstation/Interface/Alerts/modpower.rsi/modpower5.png create mode 100644 Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/boots.png create mode 100644 Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/chestplate.png create mode 100644 Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/gauntlets.png create mode 100644 Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/helmet.png create mode 100644 Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/meta.json create mode 100644 Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/mod-core-standard.png create mode 100644 Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell-boots.png create mode 100644 Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell-chestplate.png create mode 100644 Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell-core-secured.png create mode 100644 Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell-core.png create mode 100644 Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell-gauntlets.png create mode 100644 Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell-helmet.png create mode 100644 Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell-secured.png create mode 100644 Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell.png create mode 100644 Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/standard-plating.png diff --git a/Content.Client/DoAfter/DoAfterOverlay.cs b/Content.Client/DoAfter/DoAfterOverlay.cs index 93cc7e59157..f8e7564edad 100644 --- a/Content.Client/DoAfter/DoAfterOverlay.cs +++ b/Content.Client/DoAfter/DoAfterOverlay.cs @@ -112,7 +112,8 @@ protected override void Draw(in OverlayDrawArgs args) var alpha = 1f; if (doAfter.Args.Hidden) { - if (uid != localEnt) + // Goobstation - Show doAfter progress bar to another entity + if (uid != localEnt && localEnt != doAfter.Args.ShowTo) continue; // Hints to the local player that this do-after is not visible to other players. diff --git a/Content.Client/_Goobstation/Clothing/Components/SealableClothingVisualsComponent.cs b/Content.Client/_Goobstation/Clothing/Components/SealableClothingVisualsComponent.cs new file mode 100644 index 00000000000..813adb9b9b9 --- /dev/null +++ b/Content.Client/_Goobstation/Clothing/Components/SealableClothingVisualsComponent.cs @@ -0,0 +1,11 @@ +namespace Content.Client._Goobstation.Clothing.Components; + +[RegisterComponent] +public sealed partial class SealableClothingVisualsComponent : Component +{ + [DataField] + public string SpriteLayer = "sealed"; + + [DataField] + public Dictionary> VisualLayers = new(); +} diff --git a/Content.Client/_Goobstation/Clothing/EntitySystems/PoweredSealableClothingSystem.cs b/Content.Client/_Goobstation/Clothing/EntitySystems/PoweredSealableClothingSystem.cs new file mode 100644 index 00000000000..2be336e1098 --- /dev/null +++ b/Content.Client/_Goobstation/Clothing/EntitySystems/PoweredSealableClothingSystem.cs @@ -0,0 +1,7 @@ +using Content.Shared._Goobstation.Clothing.Systems; + +namespace Content.Client._Goobstation.Clothing.EntitySystems; + +public sealed class PoweredSealableClothingSystem : SharedPoweredSealableClothingSystem +{ +} diff --git a/Content.Client/_Goobstation/Clothing/EntitySystems/SealableClothingSystem.cs b/Content.Client/_Goobstation/Clothing/EntitySystems/SealableClothingSystem.cs new file mode 100644 index 00000000000..e12774db8e0 --- /dev/null +++ b/Content.Client/_Goobstation/Clothing/EntitySystems/SealableClothingSystem.cs @@ -0,0 +1,7 @@ +using Content.Shared._Goobstation.Clothing.Systems; + +namespace Content.Client._Goobstation.Clothing.EntitySystems; + +public sealed partial class SealableClothingSystem : SharedSealableClothingSystem +{ +} diff --git a/Content.Client/_Goobstation/Clothing/EntitySystems/SealableClothingVisualizerSystem.cs b/Content.Client/_Goobstation/Clothing/EntitySystems/SealableClothingVisualizerSystem.cs new file mode 100644 index 00000000000..235982e3a8b --- /dev/null +++ b/Content.Client/_Goobstation/Clothing/EntitySystems/SealableClothingVisualizerSystem.cs @@ -0,0 +1,57 @@ +using Content.Client._Goobstation.Clothing.Components; +using Content.Client.Clothing; +using Content.Shared._Goobstation.Clothing; +using Content.Shared.Clothing; +using Content.Shared.Item; +using Robust.Client.GameObjects; +using System.Linq; + +namespace Content.Client._Goobstation.Clothing.EntitySystems; + +public sealed class SealableClothingVisualizerSystem : VisualizerSystem +{ + [Dependency] private readonly SharedItemSystem _itemSystem = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnGetEquipmentVisuals, after: new[] { typeof(ClientClothingSystem) }); + } + + protected override void OnAppearanceChange(EntityUid uid, SealableClothingVisualsComponent component, ref AppearanceChangeEvent args) + { + if (!AppearanceSystem.TryGetData(uid, SealableClothingVisuals.Sealed, out var isSealed, args.Component)) + return; + + if (args.Sprite != null && component.SpriteLayer != null && args.Sprite.LayerMapTryGet(component.SpriteLayer, out var layer)) + args.Sprite.LayerSetVisible(layer, isSealed); + + _itemSystem.VisualsChanged(uid); + } + + private void OnGetEquipmentVisuals(Entity sealable, ref GetEquipmentVisualsEvent args) + { + var (uid, comp) = sealable; + + if (!TryComp(uid, out AppearanceComponent? appearance) + || !AppearanceSystem.TryGetData(uid, SealableClothingVisuals.Sealed, out var isSealed, appearance) + || !isSealed) + return; + + if (!comp.VisualLayers.TryGetValue(args.Slot, out var layers)) + return; + + var i = 0; + foreach (var layer in layers) + { + var key = layer.MapKeys?.FirstOrDefault(); + if (key == null) + { + key = i == 0 ? $"{args.Slot}-sealed" : $"{args.Slot}-sealed-{i}"; + i++; + } + + args.Layers.Add((key, layer)); + } + } +} diff --git a/Content.Client/_Goobstation/Clothing/ToggleableClothingBoundUserInterface.cs b/Content.Client/_Goobstation/Clothing/ToggleableClothingBoundUserInterface.cs new file mode 100644 index 00000000000..55407f78347 --- /dev/null +++ b/Content.Client/_Goobstation/Clothing/ToggleableClothingBoundUserInterface.cs @@ -0,0 +1,39 @@ +using Content.Shared.Clothing.Components; +using Robust.Client.Graphics; +using Robust.Client.Input; +using Robust.Client.UserInterface; + +namespace Content.Client._Goobstation.Clothing; + +public sealed class ToggleableClothingBoundUserInterface : BoundUserInterface +{ + [Dependency] private readonly IClyde _displayManager = default!; + [Dependency] private readonly IInputManager _inputManager = default!; + + private IEntityManager _entityManager; + private ToggleableClothingRadialMenu? _menu; + + public ToggleableClothingBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + IoCManager.InjectDependencies(this); + _entityManager = IoCManager.Resolve(); + } + + protected override void Open() + { + base.Open(); + + _menu = this.CreateWindow(); + _menu.SetEntity(Owner); + _menu.SendToggleClothingMessageAction += SendToggleableClothingMessage; + + var vpSize = _displayManager.ScreenSize; + _menu.OpenCenteredAt(_inputManager.MouseScreenPosition.Position / vpSize); + } + + private void SendToggleableClothingMessage(EntityUid uid) + { + var message = new ToggleableClothingUiMessage(_entityManager.GetNetEntity(uid)); + SendPredictedMessage(message); + } +} diff --git a/Content.Client/_Goobstation/Clothing/ToggleableClothingRadialMenu.xaml b/Content.Client/_Goobstation/Clothing/ToggleableClothingRadialMenu.xaml new file mode 100644 index 00000000000..b1e27528a62 --- /dev/null +++ b/Content.Client/_Goobstation/Clothing/ToggleableClothingRadialMenu.xaml @@ -0,0 +1,6 @@ + + + diff --git a/Content.Client/_Goobstation/Clothing/ToggleableClothingRadialMenu.xaml.cs b/Content.Client/_Goobstation/Clothing/ToggleableClothingRadialMenu.xaml.cs new file mode 100644 index 00000000000..ee5fae16b05 --- /dev/null +++ b/Content.Client/_Goobstation/Clothing/ToggleableClothingRadialMenu.xaml.cs @@ -0,0 +1,96 @@ +using Content.Client.UserInterface.Controls; +using Content.Shared.Clothing.Components; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Prototypes; +using System.Numerics; + +namespace Content.Client._Goobstation.Clothing; + +public sealed partial class ToggleableClothingRadialMenu : RadialMenu +{ + [Dependency] private readonly EntityManager _entityManager = default!; + + public event Action? SendToggleClothingMessageAction; + + public EntityUid Entity { get; set; } + + public ToggleableClothingRadialMenu() + { + IoCManager.InjectDependencies(this); + RobustXamlLoader.Load(this); + } + + public void SetEntity(EntityUid uid) + { + Entity = uid; + RefreshUI(); + } + + public void RefreshUI() + { + // Even EmotesMenu has to call this, I'm assuming it's essential. + var main = FindControl("Main"); + + if (!_entityManager.TryGetComponent(Entity, out var clothing) + || clothing.Container is not { } clothingContainer) + return; + + foreach (var attached in clothing.ClothingUids) + { + // Change tooltip text if attached clothing is toggle/untoggled + var tooltipText = Loc.GetString(clothing.UnattachTooltip); + + if (clothingContainer.Contains(attached.Key)) + tooltipText = Loc.GetString(clothing.AttachTooltip); + + var button = new ToggleableClothingRadialMenuButton() + { + StyleClasses = { "RadialMenuButton" }, + SetSize = new Vector2(64, 64), + ToolTip = tooltipText, + AttachedClothingId = attached.Key + }; + + var spriteView = new SpriteView() + { + SetSize = new Vector2(48, 48), + VerticalAlignment = VAlignment.Center, + HorizontalAlignment = HAlignment.Center, + Stretch = SpriteView.StretchMode.Fill + }; + + spriteView.SetEntity(attached.Key); + + button.AddChild(spriteView); + main.AddChild(button); + } + + AddToggleableClothingMenuButtonOnClickAction(main); + } + + private void AddToggleableClothingMenuButtonOnClickAction(Control control) + { + if (control is not RadialContainer mainControl) + return; + + foreach (var child in mainControl.Children) + { + if (child is not ToggleableClothingRadialMenuButton castChild) + continue; + + castChild.OnButtonDown += _ => + { + SendToggleClothingMessageAction?.Invoke(castChild.AttachedClothingId); + mainControl.DisposeAllChildren(); + RefreshUI(); + }; + } + } +} + +public sealed class ToggleableClothingRadialMenuButton : RadialMenuTextureButton +{ + public EntityUid AttachedClothingId { get; set; } +} diff --git a/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs b/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs index 87111aad4a8..df6a812e106 100644 --- a/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs +++ b/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs @@ -18,21 +18,27 @@ public sealed class BarotraumaSystem : EntitySystem [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; [Dependency] private readonly DamageableSystem _damageableSystem = default!; [Dependency] private readonly AlertsSystem _alertsSystem = default!; - [Dependency] private readonly IAdminLogManager _adminLogger= default!; + [Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly InventorySystem _inventorySystem = default!; + [Dependency] private readonly ILogManager _logManager = default!; + private const float UpdateTimer = 1f; + + private ISawmill _sawmill = default!; private float _timer; public override void Initialize() { SubscribeLocalEvent(OnPressureProtectionEquipped); SubscribeLocalEvent(OnPressureProtectionUnequipped); - SubscribeLocalEvent(OnUpdateResistance); - SubscribeLocalEvent(OnUpdateResistance); + SubscribeLocalEvent(OnPressureProtectionChanged); // Goobstation - Update component state on toggle + SubscribeLocalEvent(OnPressureProtectionChanged); // Goobstation - Update component state on toggle SubscribeLocalEvent(OnPressureImmuneInit); SubscribeLocalEvent(OnPressureImmuneRemove); + + // _sawmill = _logManager.GetSawmill("barotrauma"); } private void OnPressureImmuneInit(EntityUid uid, PressureImmunityComponent pressureImmunity, ComponentInit args) @@ -51,6 +57,27 @@ private void OnPressureImmuneRemove(EntityUid uid, PressureImmunityComponent pre } } + // Goobstation - Modsuits - Update component state on toggle + private void OnPressureProtectionChanged(EntityUid uid, PressureProtectionComponent pressureProtection, EntityEventArgs args) + { + var protectionTarget = uid; + string? slotTarget = null; + + if (_inventorySystem.TryGetContainingEntity(uid, out var entity) && _inventorySystem.TryGetContainingSlot(uid, out var slot)) + { + protectionTarget = entity.Value; + slotTarget = slot.Name; + } + + if (!TryComp(protectionTarget, out var barotrauma)) + return; + + if (slotTarget != null && !barotrauma.ProtectionSlots.Contains(slotTarget)) + return; + + UpdateCachedResistances(protectionTarget, barotrauma); + } + /// /// Generic method for updating resistance on component Lifestage events /// diff --git a/Content.Server/Body/Components/LungComponent.cs b/Content.Server/Body/Components/LungComponent.cs index 4fb769d6702..a2c73ebf402 100644 --- a/Content.Server/Body/Components/LungComponent.cs +++ b/Content.Server/Body/Components/LungComponent.cs @@ -34,4 +34,10 @@ public sealed partial class LungComponent : Component /// [DataField] public ProtoId Alert = "LowOxygen"; + + [DataField] + public float MaxVolume = 100f; + + [DataField] + public bool CanReact = false; // No Dexalin lungs... right? } diff --git a/Content.Server/Body/Systems/LungSystem.cs b/Content.Server/Body/Systems/LungSystem.cs index 7e58c24f7e4..5919d958c96 100644 --- a/Content.Server/Body/Systems/LungSystem.cs +++ b/Content.Server/Body/Systems/LungSystem.cs @@ -1,20 +1,25 @@ using Content.Server.Atmos.Components; using Content.Server.Atmos.EntitySystems; using Content.Server.Body.Components; -using Content.Server.Chemistry.Containers.EntitySystems; +using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Atmos; using Content.Shared.Chemistry.Components; using Content.Shared.Clothing; using Content.Shared.Inventory.Events; +using Content.Shared.Inventory; +using Content.Server.Power.EntitySystems; +using Robust.Server.Containers; namespace Content.Server.Body.Systems; public sealed class LungSystem : EntitySystem { - [Dependency] private readonly AtmosphereSystem _atmos = default!; - [Dependency] private readonly InternalsSystem _internals = default!; - [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; + [Dependency] private readonly InternalsSystem _internals = default!; + [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!; + [Dependency] private readonly InventorySystem _inventory = default!; // Goobstation + + public static string LungSolutionName = "Lung"; @@ -22,6 +27,7 @@ public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnComponentInit); + SubscribeLocalEvent(OnBreathToolInit); // Goobstation - Modsuits - Update on component toggle SubscribeLocalEvent(OnGotEquipped); SubscribeLocalEvent(OnGotUnequipped); SubscribeLocalEvent(OnMaskToggled); @@ -50,16 +56,36 @@ private void OnGotEquipped(Entity ent, ref GotEquippedEvent private void OnComponentInit(Entity entity, ref ComponentInit args) { - var solution = _solutionContainerSystem.EnsureSolution(entity.Owner, entity.Comp.SolutionName); - solution.MaxVolume = 100.0f; - solution.CanReact = false; // No dexalin lungs + if (_solutionContainerSystem.EnsureSolution(entity.Owner, entity.Comp.SolutionName, out var solution)) + { + solution.MaxVolume = entity.Comp.MaxVolume; + solution.CanReact = entity.Comp.CanReact; + } + } + + // Goobstation - Update component state on component toggle + private void OnBreathToolInit(Entity ent, ref ComponentInit args) + { + var comp = ent.Comp; + + comp.IsFunctional = true; + + if (!_inventory.TryGetContainingEntity(ent.Owner, out var parent) + || !_inventory.TryGetContainingSlot(ent.Owner, out var slot) + || (slot.SlotFlags & comp.AllowedSlots) == 0 + || !TryComp(parent, out InternalsComponent? internals)) + return; + + ent.Comp.ConnectedInternalsEntity = parent; + _internals.ConnectBreathTool((parent.Value, internals), ent); } + private void OnMaskToggled(Entity ent, ref ItemMaskToggledEvent args) { if (args.IsToggled || args.IsEquip) { - _atmos.DisconnectInternals(ent.Comp); + _atmosphereSystem.DisconnectInternals(ent); } else { diff --git a/Content.Server/IdentityManagement/IdentitySystem.cs b/Content.Server/IdentityManagement/IdentitySystem.cs index e2f57b648a4..65b951dadfd 100644 --- a/Content.Server/IdentityManagement/IdentitySystem.cs +++ b/Content.Server/IdentityManagement/IdentitySystem.cs @@ -29,6 +29,7 @@ public sealed class IdentitySystem : SharedIdentitySystem [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!; [Dependency] private readonly CriminalRecordsConsoleSystem _criminalRecordsConsole = default!; [Dependency] private readonly PsionicsRecordsConsoleSystem _psionicsRecordsConsole = default!; + [Dependency] private readonly InventorySystem _inventorySystem = default!; // Goobstation - Update component state on component toggle private HashSet _queuedIdentityUpdates = new(); @@ -41,7 +42,11 @@ public override void Initialize() SubscribeLocalEvent((uid, _, _) => QueueIdentityUpdate(uid)); SubscribeLocalEvent((uid, _, _) => QueueIdentityUpdate(uid)); SubscribeLocalEvent((uid, _, _) => QueueIdentityUpdate(uid)); + SubscribeLocalEvent((uid, _, _) => QueueIdentityUpdate(uid)); SubscribeLocalEvent(OnMapInit); + + SubscribeLocalEvent(BlockerUpdateIdentity); // Goobstation - Update component state on component toggle + SubscribeLocalEvent(BlockerUpdateIdentity); // Goobstation - Update component state on component toggle } public override void Update(float frameTime) @@ -175,5 +180,16 @@ private IdentityRepresentation GetIdentityRepresentation(EntityUid target, return new(trueName, gender, ageString, presumedName, presumedJob); } + // Goobstation - Update component state on component toggle + private void BlockerUpdateIdentity(EntityUid uid, IdentityBlockerComponent component, EntityEventArgs args) + { + var target = uid; + + if (_inventorySystem.TryGetContainingEntity(uid, out var containing)) + target = containing.Value; + + QueueIdentityUpdate(target); + } + #endregion } diff --git a/Content.Server/Power/EntitySystems/ChargerSystem.cs b/Content.Server/Power/EntitySystems/ChargerSystem.cs index 40b998a95d0..c686906f4e0 100644 --- a/Content.Server/Power/EntitySystems/ChargerSystem.cs +++ b/Content.Server/Power/EntitySystems/ChargerSystem.cs @@ -11,6 +11,8 @@ using Content.Shared.Storage.Components; using Robust.Server.Containers; using Content.Shared.Whitelist; +using Content.Shared.Inventory; +using Content.Shared._Goobstation.Clothing.Systems; namespace Content.Server.Power.EntitySystems; @@ -22,6 +24,7 @@ internal sealed class ChargerSystem : EntitySystem [Dependency] private readonly BatterySystem _battery = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + [Dependency] private readonly InventorySystem _inventorySystem = default!; public override void Initialize() { @@ -256,15 +259,45 @@ private void TransferPower(EntityUid uid, EntityUid targetEntity, ChargerCompone UpdateStatus(uid, component); } + // Goobstation - Modsuits - Changed charger logic to work with suits in cyborg charger private bool SearchForBattery(EntityUid uid, [NotNullWhen(true)] out EntityUid? batteryUid, [NotNullWhen(true)] out BatteryComponent? component) { + batteryUid = null; + component = null; + // try get a battery directly on the inserted entity - if (!TryComp(uid, out component)) + if (TryComp(uid, out component)) { - // or by checking for a power cell slot on the inserted entity - return _powerCell.TryGetBatteryFromSlot(uid, out batteryUid, out component); + batteryUid = uid; + return true; } - batteryUid = uid; - return true; + + // Try to get the battery by checking for a power cell slot on the inserted entity + if (_powerCell.TryGetBatteryFromSlot(uid, out batteryUid, out component)) + return true; + + if (TryComp(uid, out var inventory)) + { + var relayEv = new FindInventoryBatteryEvent(); + _inventorySystem.RelayEvent((uid, inventory), ref relayEv); + + if (relayEv.FoundBattery != null) + { + batteryUid = relayEv.FoundBattery.Value.Owner; + component = relayEv.FoundBattery.Value.Comp; + return true; + } + } + + return false; } } + +// Goobstation - Modsuits stuff +[ByRefEvent] +public record struct FindInventoryBatteryEvent() : IInventoryRelayEvent +{ + public SlotFlags TargetSlots { get; } = SlotFlags.WITHOUT_POCKET; + + public Entity? FoundBattery { get; set; } +} diff --git a/Content.Server/_Goobstation/Clothing/Systems/PoweredSealableClothingSystem.cs b/Content.Server/_Goobstation/Clothing/Systems/PoweredSealableClothingSystem.cs new file mode 100644 index 00000000000..3e7a9d7d9f1 --- /dev/null +++ b/Content.Server/_Goobstation/Clothing/Systems/PoweredSealableClothingSystem.cs @@ -0,0 +1,112 @@ +using Content.Server.Power.EntitySystems; +using Content.Server.PowerCell; +using Content.Shared._Goobstation.Clothing.Components; +using Content.Shared._Goobstation.Clothing.Systems; +using Content.Shared.Alert; +using Content.Shared.Inventory; +using Content.Shared.Movement.Systems; +using Content.Shared.PowerCell; +using Content.Shared.PowerCell.Components; +using Content.Shared.Rounding; + +namespace Content.Server._Goobstation.Clothing.Systems; + +public sealed partial class PoweredSealableClothingSystem : SharedPoweredSealableClothingSystem +{ + [Dependency] private readonly AlertsSystem _alertsSystem = default!; + [Dependency] private readonly PowerCellSystem _powerCellSystem = default!; + [Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent>(OnMovementSpeedChange); + SubscribeLocalEvent(OnPowerCellChanged); + SubscribeLocalEvent(OnPowerCellEmpty); + SubscribeLocalEvent(OnRequiresPowerSealCompleteEvent); + SubscribeLocalEvent>(OnFindInventoryBatteryEvent); + } + + private void OnPowerCellChanged(Entity entity, ref PowerCellChangedEvent args) + { + if (!entity.Comp.IsPowered && _powerCellSystem.HasDrawCharge(entity)) + { + entity.Comp.IsPowered = true; + Dirty(entity); + + ModifySpeed(entity); + } + + UpdateClothingPowerAlert(entity); + } + + private void OnPowerCellEmpty(Entity entity, ref PowerCellSlotEmptyEvent args) + { + entity.Comp.IsPowered = false; + Dirty(entity); + + ModifySpeed(entity); + } + + /// Enables or disables power cell draw on seal/unseal complete + private void OnRequiresPowerSealCompleteEvent(Entity entity, ref ClothingControlSealCompleteEvent args) + { + if (!TryComp(entity, out PowerCellDrawComponent? drawComp)) + return; + + _powerCellSystem.SetDrawEnabled((entity.Owner, drawComp), args.IsSealed); + + UpdateClothingPowerAlert(entity); + ModifySpeed(entity); + } + + private void OnMovementSpeedChange(Entity entity, ref InventoryRelayedEvent args) + { + if (!TryComp(entity, out SealableClothingControlComponent? controlComp)) + return; + + // If suit is unsealed - don't care about penalty + if (!controlComp.IsCurrentlySealed) + return; + + if (!entity.Comp.IsPowered) + args.Args.ModifySpeed(entity.Comp.MovementSpeedPenalty); + } + + private void ModifySpeed(EntityUid uid) + { + if (!TryComp(uid, out SealableClothingControlComponent? controlComp) || controlComp.WearerEntity == null) + return; + + _movementSpeed.RefreshMovementSpeedModifiers(controlComp.WearerEntity.Value); + } + + /// Sets power alert to wearer when clothing is sealed + private void UpdateClothingPowerAlert(Entity entity) + { + var (uid, comp) = entity; + + if (!TryComp(uid, out var controlComp) || controlComp.WearerEntity == null) + return; + + if (!_powerCellSystem.TryGetBatteryFromSlot(entity, out var battery) || !controlComp.IsCurrentlySealed) + { + _alertsSystem.ClearAlert(controlComp.WearerEntity.Value, comp.SuitPowerAlert); + return; + } + + var severity = ContentHelpers.RoundToLevels(MathF.Max(0f, battery.CurrentCharge), battery.MaxCharge, 6); + _alertsSystem.ShowAlert(controlComp.WearerEntity.Value, comp.SuitPowerAlert, (short) severity); + } + + /// Tries to find battery for charger + private void OnFindInventoryBatteryEvent(Entity entity, ref InventoryRelayedEvent args) + { + if (args.Args.FoundBattery != null) + return; + + if (_powerCellSystem.TryGetBatteryFromSlot(entity, out var batteryEnt, out var battery)) + args.Args.FoundBattery = (batteryEnt.Value, battery); + } +} diff --git a/Content.Server/_Goobstation/Clothing/Systems/SealableClothingSystem.cs b/Content.Server/_Goobstation/Clothing/Systems/SealableClothingSystem.cs new file mode 100644 index 00000000000..09f0d9dbfc6 --- /dev/null +++ b/Content.Server/_Goobstation/Clothing/Systems/SealableClothingSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared._Goobstation.Clothing.Systems; + +namespace Content.Server._Goobstation.Clothing.Systems; + +public sealed partial class SealableClothingSystem : SharedSealableClothingSystem { } diff --git a/Content.Shared/Actions/ConfirmableActionComponent.cs b/Content.Shared/Actions/ConfirmableActionComponent.cs index 6c208f47c6e..ca7a15eb5a1 100644 --- a/Content.Shared/Actions/ConfirmableActionComponent.cs +++ b/Content.Shared/Actions/ConfirmableActionComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.Popups; using Robust.Shared.GameStates; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; @@ -15,8 +16,15 @@ public sealed partial class ConfirmableActionComponent : Component /// /// Warning popup shown when priming the action. /// - [DataField(required: true)] - public LocId Popup = string.Empty; + // Goobstation - Modsuits - Removed required string + [DataField] + public LocId? Popup = null; + + /// + /// Type of warning popup - Goobstaiton - Modsuits + /// + [DataField("popupType")] + public PopupType PopupFontType = PopupType.LargeCaution; /// /// If not null, this is when the action can be confirmed at. diff --git a/Content.Shared/Actions/ConfirmableActionSystem.cs b/Content.Shared/Actions/ConfirmableActionSystem.cs index 26cc7111d2c..8a567fa9713 100644 --- a/Content.Shared/Actions/ConfirmableActionSystem.cs +++ b/Content.Shared/Actions/ConfirmableActionSystem.cs @@ -10,6 +10,7 @@ namespace Content.Shared.Actions; public sealed class ConfirmableActionSystem : EntitySystem { [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; // Goobstation [Dependency] private readonly SharedPopupSystem _popup = default!; public override void Initialize() @@ -67,7 +68,12 @@ private void Prime(Entity ent, EntityUid user) comp.NextUnprime = comp.NextConfirm + comp.PrimeTime; Dirty(uid, comp); - _popup.PopupClient(Loc.GetString(comp.Popup), user, user, PopupType.LargeCaution); + // Goobstation - Confirmable action with changed icon - Start + if (!string.IsNullOrEmpty(comp.Popup)) + _popup.PopupClient(Loc.GetString(comp.Popup), user, user, comp.PopupFontType); + + _actions.SetToggled(ent, true); + // Goobstation - Confirmable action with changed icon - End } private void Unprime(Entity ent) @@ -75,6 +81,9 @@ private void Unprime(Entity ent) var (uid, comp) = ent; comp.NextConfirm = null; comp.NextUnprime = null; + + _actions.SetToggled(ent, false); // Goobstation - Confirmable action with changed icon + Dirty(uid, comp); } } diff --git a/Content.Shared/Armor/ArmorComponent.cs b/Content.Shared/Armor/ArmorComponent.cs index fd04c5d29c8..06e4c48e87a 100644 --- a/Content.Shared/Armor/ArmorComponent.cs +++ b/Content.Shared/Armor/ArmorComponent.cs @@ -1,5 +1,6 @@ using Content.Shared.Damage; using Robust.Shared.GameStates; +using Robust.Shared.Serialization; using Robust.Shared.Utility; namespace Content.Shared.Armor; @@ -7,20 +8,20 @@ namespace Content.Shared.Armor; /// /// Used for clothing that reduces damage when worn. /// -[RegisterComponent, NetworkedComponent, Access(typeof(SharedArmorSystem))] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] // Goobstation - remove access restrictions public sealed partial class ArmorComponent : Component { /// /// The damage reduction /// - [DataField(required: true)] + [DataField(required: true), AutoNetworkedField] public DamageModifierSet Modifiers = default!; /// /// A multiplier applied to the calculated point value /// to determine the monetary value of the armor /// - [DataField] + [DataField, AutoNetworkedField] public float PriceMultiplier = 1; } diff --git a/Content.Shared/Clothing/Components/AttachedClothingComponent.cs b/Content.Shared/Clothing/Components/AttachedClothingComponent.cs index c52c875952a..fbe462ed426 100644 --- a/Content.Shared/Clothing/Components/AttachedClothingComponent.cs +++ b/Content.Shared/Clothing/Components/AttachedClothingComponent.cs @@ -1,4 +1,5 @@ using Content.Shared.Clothing.EntitySystems; +using Robust.Shared.Containers; using Robust.Shared.GameStates; namespace Content.Shared.Clothing.Components; @@ -13,9 +14,21 @@ namespace Content.Shared.Clothing.Components; [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class AttachedClothingComponent : Component { + // Goobstation - Modsuits changes this system entirely + public const string DefaultClothingContainerId = "replaced-clothing"; + /// /// The Id of the piece of clothing that this entity belongs to. /// [DataField, AutoNetworkedField] public EntityUid AttachedUid; + + /// + /// Container ID for clothing that will be replaced with this one + /// + [DataField, AutoNetworkedField] + public string ClothingContainerId = DefaultClothingContainerId; + + [ViewVariables, NonSerialized] + public ContainerSlot? ClothingContainer; } diff --git a/Content.Shared/Clothing/Components/ToggleableClothingComponent.cs b/Content.Shared/Clothing/Components/ToggleableClothingComponent.cs index 3053efe89aa..4e9dd91e389 100644 --- a/Content.Shared/Clothing/Components/ToggleableClothingComponent.cs +++ b/Content.Shared/Clothing/Components/ToggleableClothingComponent.cs @@ -3,7 +3,7 @@ using Robust.Shared.Containers; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; +using Robust.Shared.Serialization; namespace Content.Shared.Clothing.Components; @@ -25,18 +25,31 @@ public sealed partial class ToggleableClothingComponent : Component [DataField, AutoNetworkedField] public EntityUid? ActionEntity; + // Goobstation - ClothingPrototype and Slot Fields saved for compatibility with old prototype /// /// Default clothing entity prototype to spawn into the clothing container. /// - [DataField(required: true), AutoNetworkedField] - public EntProtoId ClothingPrototype = default!; + [DataField, AutoNetworkedField] + public EntProtoId? ClothingPrototype; /// /// The inventory slot that the clothing is equipped to. /// [ViewVariables(VVAccess.ReadWrite)] [DataField, AutoNetworkedField] - public string Slot = "head"; + public string Slot = string.Empty; + + /// + /// Dictionary of inventory slots and entity prototypes to spawn into the clothing container. + /// + [DataField, AutoNetworkedField] + public Dictionary ClothingPrototypes = new(); + + /// + /// Dictionary of clothing uids and slots + /// + [DataField, AutoNetworkedField] + public Dictionary ClothingUids = new(); /// /// The inventory slot flags required for this component to function. @@ -51,14 +64,7 @@ public sealed partial class ToggleableClothingComponent : Component public string ContainerId = DefaultClothingContainerId; [ViewVariables] - public ContainerSlot? Container; - - /// - /// The Id of the piece of clothing that belongs to this component. Required for map-saving if the clothing is - /// currently not inside of the container. - /// - [DataField, AutoNetworkedField] - public EntityUid? ClothingUid; + public Container? Container; /// /// Time it takes for this clothing to be toggled via the stripping menu verbs. Null prevents the verb from even showing up. @@ -71,4 +77,39 @@ public sealed partial class ToggleableClothingComponent : Component /// [DataField, AutoNetworkedField] public string? VerbText; + + /// + /// If true it will block unequip of this entity until all attached clothing are removed + /// + [DataField, AutoNetworkedField] + public bool BlockUnequipWhenAttached = false; + + /// + /// If true all attached will replace already equipped clothing on equip attempt + /// + [DataField, AutoNetworkedField] + public bool ReplaceCurrentClothing = false; + + [DataField, AutoNetworkedField] + public string AttachTooltip = "toggleable-clothing-attach-tooltip"; + + [DataField, AutoNetworkedField] + public string UnattachTooltip = "toggleable-clothing-unattach-tooltip"; +} + +[Serializable, NetSerializable] +public enum ToggleClothingUiKey : byte +{ + Key +} + +[Serializable, NetSerializable] +public sealed class ToggleableClothingUiMessage : BoundUserInterfaceMessage +{ + public NetEntity AttachedClothingUid; + + public ToggleableClothingUiMessage(NetEntity attachedClothingUid) + { + AttachedClothingUid = attachedClothingUid; + } } diff --git a/Content.Shared/Clothing/EntitySystems/ToggleableClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/ToggleableClothingSystem.cs index 4cb127bb102..63f817f9bbd 100644 --- a/Content.Shared/Clothing/EntitySystems/ToggleableClothingSystem.cs +++ b/Content.Shared/Clothing/EntitySystems/ToggleableClothingSystem.cs @@ -13,9 +13,11 @@ using Robust.Shared.Serialization; using Robust.Shared.Timing; using Robust.Shared.Utility; +using System.Linq; namespace Content.Shared.Clothing.EntitySystems; +// GOOBSTATION - MODSUITS - THIS SYSTEM FULLY CHANGED public sealed class ToggleableClothingSystem : EntitySystem { [Dependency] private readonly IGameTiming _timing = default!; @@ -27,19 +29,22 @@ public sealed class ToggleableClothingSystem : EntitySystem [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly SharedStrippableSystem _strippable = default!; - [Dependency] private readonly ThievingSystem _thieving = default!; + [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnToggleableInit); SubscribeLocalEvent(OnMapInit); - SubscribeLocalEvent(OnToggleClothing); + SubscribeLocalEvent(OnToggleClothingAction); SubscribeLocalEvent(OnGetActions); SubscribeLocalEvent(OnRemoveToggleable); SubscribeLocalEvent(OnToggleableUnequip); + SubscribeLocalEvent(OnToggleClothingMessage); + SubscribeLocalEvent(OnToggleableUnequipAttempt); + SubscribeLocalEvent(OnAttachedInit); SubscribeLocalEvent(OnInteractHand); SubscribeLocalEvent(OnAttachedUnequip); SubscribeLocalEvent(OnRemoveAttached); @@ -51,62 +56,64 @@ public override void Initialize() SubscribeLocalEvent(OnDoAfterComplete); } - private void GetRelayedVerbs(EntityUid uid, ToggleableClothingComponent component, InventoryRelayedEvent> args) + private void GetRelayedVerbs(Entity toggleable, ref InventoryRelayedEvent> args) { - OnGetVerbs(uid, component, args.Args); + OnGetVerbs(toggleable, ref args.Args); } - private void OnGetVerbs(EntityUid uid, ToggleableClothingComponent component, GetVerbsEvent args) + private void OnGetVerbs(Entity toggleable, ref GetVerbsEvent args) { - if (!args.CanAccess || !args.CanInteract || component.ClothingUid == null || component.Container == null) + var comp = toggleable.Comp; + + if (!args.CanAccess || !args.CanInteract || args.Hands == null || comp.ClothingUids.Count == 0 || comp.Container == null) return; - var text = component.VerbText ?? (component.ActionEntity == null ? null : Name(component.ActionEntity.Value)); + var text = comp.VerbText ?? (comp.ActionEntity == null ? null : Name(comp.ActionEntity.Value)); if (text == null) return; - if (!_inventorySystem.InSlotWithFlags(uid, component.RequiredFlags)) + if (!_inventorySystem.InSlotWithFlags(toggleable.Owner, comp.RequiredFlags)) return; - var wearer = Transform(uid).ParentUid; - if (args.User != wearer && component.StripDelay == null) + var wearer = Transform(toggleable).ParentUid; + if (args.User != wearer && comp.StripDelay == null) return; + var user = args.User; + var verb = new EquipmentVerb() { Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/outfit.svg.192dpi.png")), Text = Loc.GetString(text), }; - if (args.User == wearer) + if (user == wearer) { - verb.EventTarget = uid; - verb.ExecutionEventArgs = new ToggleClothingEvent() { Performer = args.User }; + verb.Act = () => ToggleClothing(user, toggleable); } else { - verb.Act = () => StartDoAfter(args.User, uid, Transform(uid).ParentUid, component); + verb.Act = () => StartDoAfter(user, toggleable, wearer); } args.Verbs.Add(verb); } - private void StartDoAfter(EntityUid user, EntityUid item, EntityUid wearer, ToggleableClothingComponent component) + private void StartDoAfter(EntityUid user, Entity toggleable, EntityUid wearer) { - if (component.StripDelay == null) + var comp = toggleable.Comp; + + if (comp.StripDelay == null) return; - var (time, stealth) = _strippable.GetStripTimeModifiers(user, wearer, component.StripDelay.Value); + var (time, stealth) = _strippable.GetStripTimeModifiers(user, wearer, comp.StripDelay.Value); - bool hidden = (stealth == ThievingStealth.Hidden); + bool hidden = stealth == ThievingStealth.Hidden; - var args = new DoAfterArgs(EntityManager, user, time, new ToggleClothingDoAfterEvent(), item, wearer, item) + var args = new DoAfterArgs(EntityManager, user, time, new ToggleClothingDoAfterEvent(), toggleable, wearer, toggleable) { BreakOnDamage = true, BreakOnMove = true, - // This should just re-use the BUI range checks & cancel the do after if the BUI closes. But that is all - // server-side at the moment. - // TODO BUI REFACTOR. DistanceThreshold = 2, }; @@ -114,189 +121,404 @@ private void StartDoAfter(EntityUid user, EntityUid item, EntityUid wearer, Togg return; if (!hidden) - _strippable.StripPopup("strippable-component-alert-owner-interact", stealth, wearer, user: Identity.Entity(user, EntityManager), item: item); + { + var popup = Loc.GetString("strippable-component-alert-owner-interact", ("user", Identity.Entity(user, EntityManager)), ("item", toggleable)); + _popupSystem.PopupEntity(popup, wearer, wearer, PopupType.Large); + } } - private void OnGetAttachedStripVerbsEvent(EntityUid uid, AttachedClothingComponent component, GetVerbsEvent args) + private void OnGetAttachedStripVerbsEvent(Entity attached, ref GetVerbsEvent args) { + var comp = attached.Comp; + + if (!TryComp(comp.AttachedUid, out var toggleableComp)) + return; + // redirect to the attached entity. - OnGetVerbs(component.AttachedUid, Comp(component.AttachedUid), args); + OnGetVerbs((comp.AttachedUid, toggleableComp), ref args); } - private void OnDoAfterComplete(EntityUid uid, ToggleableClothingComponent component, ToggleClothingDoAfterEvent args) + private void OnDoAfterComplete(Entity toggleable, ref ToggleClothingDoAfterEvent args) { if (args.Cancelled) return; - ToggleClothing(args.User, uid, component); + ToggleClothing(args.User, toggleable); } - private void OnInteractHand(EntityUid uid, AttachedClothingComponent component, InteractHandEvent args) + private void OnInteractHand(Entity attached, ref InteractHandEvent args) { + var comp = attached.Comp; + if (args.Handled) return; - if (!TryComp(component.AttachedUid, out ToggleableClothingComponent? toggleCom) - || toggleCom.Container == null) + if (!TryComp(comp.AttachedUid, out ToggleableClothingComponent? toggleableComp) + || toggleableComp.Container == null) return; - if (!_inventorySystem.TryUnequip(Transform(uid).ParentUid, toggleCom.Slot, force: true)) + // Get slot from dictionary of uid-slot + if (!toggleableComp.ClothingUids.TryGetValue(attached.Owner, out var attachedSlot)) return; - _containerSystem.Insert(uid, toggleCom.Container); + if (!_inventorySystem.TryUnequip(Transform(attached.Owner).ParentUid, attachedSlot, force: true)) + return; + + _containerSystem.Insert(attached.Owner, toggleableComp.Container); args.Handled = true; } + /// + /// Prevents from unequipping entity if all attached not unequipped + /// + private void OnToggleableUnequipAttempt(Entity toggleable, ref BeingUnequippedAttemptEvent args) + { + var comp = toggleable.Comp; + + if (!comp.BlockUnequipWhenAttached) + return; + + if (GetAttachedToggleStatus(toggleable) == ToggleableClothingAttachedStatus.NoneToggled) + return; + + _popupSystem.PopupClient(Loc.GetString("toggleable-clothing-remove-all-attached-first"), args.Unequipee, args.Unequipee); + + args.Cancel(); + } + /// /// Called when the suit is unequipped, to ensure that the helmet also gets unequipped. /// - private void OnToggleableUnequip(EntityUid uid, ToggleableClothingComponent component, GotUnequippedEvent args) + private void OnToggleableUnequip(Entity toggleable, ref GotUnequippedEvent args) { + var comp = toggleable.Comp; + // If it's a part of PVS departure then don't handle it. if (_timing.ApplyingState) return; - // If the attached clothing is not currently in the container, this just assumes that it is currently equipped. - // This should maybe double check that the entity currently in the slot is actually the attached clothing, but - // if its not, then something else has gone wrong already... - if (component.Container != null && component.Container.ContainedEntity == null && component.ClothingUid != null) - _inventorySystem.TryUnequip(args.Equipee, component.Slot, force: true); + // Check if container exists and we have linked clothings + if (comp.Container == null || comp.ClothingUids.Count == 0) + return; + + var parts = comp.ClothingUids; + + foreach (var part in parts) + { + // Check if entity in container what means it already unequipped + if (comp.Container.Contains(part.Key) || part.Value == null) + continue; + + _inventorySystem.TryUnequip(args.Equipee, part.Value, force: true); + } } - private void OnRemoveToggleable(EntityUid uid, ToggleableClothingComponent component, ComponentRemove args) + private void OnRemoveToggleable(Entity toggleable, ref ComponentRemove args) { // If the parent/owner component of the attached clothing is being removed (entity getting deleted?) we will // delete the attached entity. We do this regardless of whether or not the attached entity is currently // "outside" of the container or not. This means that if a hardsuit takes too much damage, the helmet will also // automatically be deleted. - _actionsSystem.RemoveAction(component.ActionEntity); + var comp = toggleable.Comp; + + _actionsSystem.RemoveAction(comp.ActionEntity); + + if (comp.ClothingUids == null || _netMan.IsClient) + return; - if (component.ClothingUid != null && !_netMan.IsClient) - QueueDel(component.ClothingUid.Value); + foreach (var clothing in comp.ClothingUids.Keys) + QueueDel(clothing); } - private void OnAttachedUnequipAttempt(EntityUid uid, AttachedClothingComponent component, BeingUnequippedAttemptEvent args) + private void OnAttachedUnequipAttempt(Entity attached, ref BeingUnequippedAttemptEvent args) { args.Cancel(); } - private void OnRemoveAttached(EntityUid uid, AttachedClothingComponent component, ComponentRemove args) + private void OnRemoveAttached(Entity attached, ref ComponentRemove args) { // if the attached component is being removed (maybe entity is being deleted?) we will just remove the // toggleable clothing component. This means if you had a hard-suit helmet that took too much damage, you would // still be left with a suit that was simply missing a helmet. There is currently no way to fix a partially // broken suit like this. - if (!TryComp(component.AttachedUid, out ToggleableClothingComponent? toggleComp)) + var comp = attached.Comp; + + if (!TryComp(comp.AttachedUid, out ToggleableClothingComponent? toggleableComp) + || toggleableComp.LifeStage > ComponentLifeStage.Running) + return; + + var clothingUids = toggleableComp.ClothingUids; + + if (!clothingUids.Remove(attached.Owner) || clothingUids.Count > 0) return; - if (toggleComp.LifeStage > ComponentLifeStage.Running) + // If no attached clothing left - remove component and action + if (clothingUids.Count > 0) return; - _actionsSystem.RemoveAction(toggleComp.ActionEntity); - RemComp(component.AttachedUid, toggleComp); + _actionsSystem.RemoveAction(toggleableComp.ActionEntity); + RemComp(comp.AttachedUid, toggleableComp); } /// - /// Called if the helmet was unequipped, to ensure that it gets moved into the suit's container. + /// Called if the clothing was unequipped, to ensure that it gets moved into the suit's container. /// - private void OnAttachedUnequip(EntityUid uid, AttachedClothingComponent component, GotUnequippedEvent args) + private void OnAttachedUnequip(Entity attached, ref GotUnequippedEvent args) { - // Let containers worry about it. - if (_timing.ApplyingState) - return; - - if (component.LifeStage > ComponentLifeStage.Running) + var comp = attached.Comp; + + // Death told me to do this- if you need to figure out why each of these are here, idk, figure it out. + if (_timing.ApplyingState + || comp.LifeStage > ComponentLifeStage.Running + || !TryComp(comp.AttachedUid, out ToggleableClothingComponent? toggleableComp) + || toggleableComp.LifeStage > ComponentLifeStage.Running + || !toggleableComp.ClothingUids.ContainsKey(attached.Owner)) return; - if (!TryComp(component.AttachedUid, out ToggleableClothingComponent? toggleComp)) - return; + if (toggleableComp.Container != null) + _containerSystem.Insert(attached.Owner, toggleableComp.Container); + } - if (toggleComp.LifeStage > ComponentLifeStage.Running) - return; + /// + /// Equip or unequip toggle clothing with ui message + /// + private void OnToggleClothingMessage(Entity toggleable, ref ToggleableClothingUiMessage args) + { + var attachedUid = GetEntity(args.AttachedClothingUid); - // As unequipped gets called in the middle of container removal, we cannot call a container-insert without causing issues. - // So we delay it and process it during a system update: - if (toggleComp.ClothingUid != null && toggleComp.Container != null) - _containerSystem.Insert(toggleComp.ClothingUid.Value, toggleComp.Container); + ToggleClothing(args.Actor, toggleable, attachedUid); } /// /// Equip or unequip the toggleable clothing. /// - private void OnToggleClothing(EntityUid uid, ToggleableClothingComponent component, ToggleClothingEvent args) + private void OnToggleClothingAction(Entity toggleable, ref ToggleClothingEvent args) { + var comp = toggleable.Comp; + if (args.Handled) return; + if (comp.Container == null || comp.ClothingUids.Count == 0) + return; + args.Handled = true; - ToggleClothing(args.Performer, uid, component); + + // If clothing have only one attached clothing (like helmets) action will just toggle it + // If it have more attached clothings, it'll open radial menu + if (comp.ClothingUids.Count == 1) + ToggleClothing(args.Performer, toggleable, comp.ClothingUids.First().Key); + else + _uiSystem.OpenUi(toggleable.Owner, ToggleClothingUiKey.Key, args.Performer); } - private void ToggleClothing(EntityUid user, EntityUid target, ToggleableClothingComponent component) + /// + /// Toggle function for single clothing + /// + private void ToggleClothing(EntityUid user, Entity toggleable, EntityUid attachedUid) { - if (component.Container == null || component.ClothingUid == null) + var comp = toggleable.Comp; + var attachedClothings = comp.ClothingUids; + var container = comp.Container; + + if (!CanToggleClothing(user, toggleable)) return; - var parent = Transform(target).ParentUid; - if (component.Container.ContainedEntity == null) - _inventorySystem.TryUnequip(user, parent, component.Slot, force: true); - else if (_inventorySystem.TryGetSlotEntity(parent, component.Slot, out var existing)) - { - _popupSystem.PopupClient(Loc.GetString("toggleable-clothing-remove-first", ("entity", existing)), - user, user); - } + if (!attachedClothings.TryGetValue(attachedUid, out var slot) || string.IsNullOrEmpty(slot)) + return; + + if (!container!.Contains(attachedUid)) + UnequipClothing(user, toggleable, attachedUid, slot!); + else + EquipClothing(user, toggleable, attachedUid, slot!); + } + + /// + /// Toggle function for toggling multiple clothings at once + /// + private void ToggleClothing(EntityUid user, Entity toggleable) + { + var comp = toggleable.Comp; + var attachedClothings = comp.ClothingUids; + var container = comp.Container; + + if (!CanToggleClothing(user, toggleable)) + return; + + if (GetAttachedToggleStatus(toggleable, comp) == ToggleableClothingAttachedStatus.NoneToggled) + foreach (var clothing in attachedClothings) + EquipClothing(user, toggleable, clothing.Key, clothing.Value); else - _inventorySystem.TryEquip(user, parent, component.ClothingUid.Value, component.Slot); + foreach (var clothing in attachedClothings) + if (!container!.Contains(clothing.Key)) + UnequipClothing(user, toggleable, clothing.Key, clothing.Value); } - private void OnGetActions(EntityUid uid, ToggleableClothingComponent component, GetItemActionsEvent args) + private bool CanToggleClothing(EntityUid user, Entity toggleable) { - if (component.ClothingUid != null - && component.ActionEntity != null - && (args.SlotFlags & component.RequiredFlags) == component.RequiredFlags) + var comp = toggleable.Comp; + var attachedClothings = comp.ClothingUids; + var container = comp.Container; + + if (container == null || attachedClothings.Count == 0) + return false; + + var ev = new ToggleClothingAttemptEvent(user, toggleable); + RaiseLocalEvent(toggleable, ev); + + return !ev.Cancelled; + } + + private void UnequipClothing(EntityUid user, Entity toggleable, EntityUid clothing, string slot) + { + var parent = Transform(toggleable.Owner).ParentUid; + + _inventorySystem.TryUnequip(user, parent, slot, force: true); + + // If attached have clothing in container - equip it + if (!TryComp(clothing, out var attachedComp) || attachedComp.ClothingContainer == null) + return; + + var storedClothing = attachedComp.ClothingContainer.ContainedEntity; + + if (storedClothing != null) + _inventorySystem.TryEquip(parent, storedClothing.Value, slot, force: true); + } + private void EquipClothing(EntityUid user, Entity toggleable, EntityUid clothing, string slot) + { + var parent = Transform(toggleable.Owner).ParentUid; + var comp = toggleable.Comp; + + if (_inventorySystem.TryGetSlotEntity(parent, slot, out var currentClothing)) { - args.AddAction(component.ActionEntity.Value); + // Check if we need to replace current clothing + if (!TryComp(clothing, out var attachedComp) || !comp.ReplaceCurrentClothing) + { + _popupSystem.PopupClient(Loc.GetString("toggleable-clothing-remove-first", ("entity", currentClothing)), user, user); + return; + } + + // Check if attached clothing have container or this container not empty + if (attachedComp.ClothingContainer == null || attachedComp.ClothingContainer.ContainedEntity != null) + return; + + if (_inventorySystem.TryUnequip(user, parent, slot)) + _containerSystem.Insert(currentClothing.Value, attachedComp.ClothingContainer); } + + _inventorySystem.TryEquip(user, parent, clothing, slot); } - private void OnInit(EntityUid uid, ToggleableClothingComponent component, ComponentInit args) + private void OnGetActions(Entity toggleable, ref GetItemActionsEvent args) { - component.Container = _containerSystem.EnsureContainer(uid, component.ContainerId); + var comp = toggleable.Comp; + + if (comp.ClothingUids.Count == 0 || comp.ActionEntity == null || args.SlotFlags != comp.RequiredFlags) + return; + + args.AddAction(comp.ActionEntity.Value); + } + + private void OnToggleableInit(Entity toggleable, ref ComponentInit args) + { + var comp = toggleable.Comp; + + comp.Container = _containerSystem.EnsureContainer(toggleable, comp.ContainerId); + } + + private void OnAttachedInit(Entity attached, ref ComponentInit args) + { + var comp = attached.Comp; + + comp.ClothingContainer = _containerSystem.EnsureContainer(attached, comp.ClothingContainerId); } /// /// On map init, either spawn the appropriate entity into the suit slot, or if it already exists, perform some /// sanity checks. Also updates the action icon to show the toggled-entity. /// - private void OnMapInit(EntityUid uid, ToggleableClothingComponent component, MapInitEvent args) + private void OnMapInit(Entity toggleable, ref MapInitEvent args) { - if (component.Container!.ContainedEntity is {} ent) + var comp = toggleable.Comp; + + if (comp.Container!.Count != 0) { - DebugTools.Assert(component.ClothingUid == ent, "Unexpected entity present inside of a toggleable clothing container."); + DebugTools.Assert(comp.ClothingUids.Count != 0, "Unexpected entity present inside of a toggleable clothing container."); return; } - if (component.ClothingUid != null && component.ActionEntity != null) + if (comp.ClothingUids.Count != 0 && comp.ActionEntity != null) + return; + + // Add prototype from ClothingPrototype and Slot field to ClothingPrototypes dictionary + if (comp.ClothingPrototype != null && !string.IsNullOrEmpty(comp.Slot) && !comp.ClothingPrototypes.ContainsKey(comp.Slot)) { - DebugTools.Assert(Exists(component.ClothingUid), "Toggleable clothing is missing expected entity."); - DebugTools.Assert(TryComp(component.ClothingUid, out AttachedClothingComponent? comp), "Toggleable clothing is missing an attached component"); - DebugTools.Assert(comp?.AttachedUid == uid, "Toggleable clothing uid mismatch"); + comp.ClothingPrototypes.Add(comp.Slot, comp.ClothingPrototype.Value); } - else + + var xform = Transform(toggleable.Owner); + + if (comp.ClothingPrototypes == null) + return; + + var prototypes = comp.ClothingPrototypes; + + foreach (var prototype in prototypes) { - var xform = Transform(uid); - component.ClothingUid = Spawn(component.ClothingPrototype, xform.Coordinates); - var attachedClothing = EnsureComp(component.ClothingUid.Value); - attachedClothing.AttachedUid = uid; - Dirty(component.ClothingUid.Value, attachedClothing); - _containerSystem.Insert(component.ClothingUid.Value, component.Container, containerXform: xform); - Dirty(uid, component); + var spawned = Spawn(prototype.Value, xform.Coordinates); + var attachedClothing = EnsureComp(spawned); + attachedClothing.AttachedUid = toggleable; + EnsureComp(spawned); + + comp.ClothingUids.Add(spawned, prototype.Key); + _containerSystem.Insert(spawned, comp.Container, containerXform: xform); + + Dirty(spawned, attachedClothing); } - if (_actionContainer.EnsureAction(uid, ref component.ActionEntity, out var action, component.Action)) - _actionsSystem.SetEntityIcon(component.ActionEntity.Value, component.ClothingUid, action); + Dirty(toggleable, comp); + + if (_actionContainer.EnsureAction(toggleable, ref comp.ActionEntity, out var action, comp.Action)) + _actionsSystem.SetEntityIcon(comp.ActionEntity.Value, toggleable, action); + } + + // Checks status of all attached clothings toggle status + public ToggleableClothingAttachedStatus GetAttachedToggleStatus(EntityUid toggleable, ToggleableClothingComponent? component = null) + { + if (!Resolve(toggleable, ref component)) + return ToggleableClothingAttachedStatus.NoneToggled; + + var container = component.Container; + var attachedClothings = component.ClothingUids; + + // If entity don't have any attached clothings it means none toggled + if (container == null || attachedClothings.Count == 0) + return ToggleableClothingAttachedStatus.NoneToggled; + + var toggledCount = attachedClothings.Count(c => !container.Contains(c.Key)); + + if (toggledCount == 0) + return ToggleableClothingAttachedStatus.NoneToggled; + + if (toggledCount < attachedClothings.Count) + return ToggleableClothingAttachedStatus.PartlyToggled; + + return ToggleableClothingAttachedStatus.AllToggled; + } + + public List? GetAttachedClothingsList(EntityUid toggleable, ToggleableClothingComponent? component = null) + { + if (!Resolve(toggleable, ref component) || component.ClothingUids.Count == 0) + return null; + + var newList = new List(); + + foreach (var attachee in component.ClothingUids) + newList.Add(attachee.Key); + + return newList; } } @@ -308,3 +530,29 @@ public sealed partial class ToggleClothingEvent : InstantActionEvent public sealed partial class ToggleClothingDoAfterEvent : SimpleDoAfterEvent { } + +/// +/// Event raises on toggleable clothing when someone trying to toggle it +/// +public sealed class ToggleClothingAttemptEvent : CancellableEntityEventArgs +{ + public EntityUid User { get; } + public EntityUid Target { get; } + + public ToggleClothingAttemptEvent(EntityUid user, EntityUid target) + { + User = user; + Target = target; + } +} + +/// +/// Status of toggleable clothing attachee +/// +[Serializable, NetSerializable] +public enum ToggleableClothingAttachedStatus : byte +{ + NoneToggled, + PartlyToggled, + AllToggled +} diff --git a/Content.Shared/DoAfter/DoAfterArgs.cs b/Content.Shared/DoAfter/DoAfterArgs.cs index d88f72c965f..97d9e42d74e 100644 --- a/Content.Shared/DoAfter/DoAfterArgs.cs +++ b/Content.Shared/DoAfter/DoAfterArgs.cs @@ -19,14 +19,14 @@ public sealed partial class DoAfterArgs /// /// How long does the do_after require to complete /// - [DataField("delay", required: true)] + [DataField(required: true)] public TimeSpan Delay; /// /// Applicable target (if relevant) /// [NonSerialized] - [DataField("target")] + [DataField] public EntityUid? Target; public NetEntity? NetTarget; @@ -40,17 +40,28 @@ public sealed partial class DoAfterArgs public NetEntity? NetUsed; + // Goobstation - Show doAfter progress bar to another entity + [NonSerialized] + [DataField] + public EntityUid? ShowTo; + + public NetEntity? NetShowTo; + /// /// Whether the progress bar for this DoAfter should be hidden from other players. /// [DataField] public bool Hidden; + /// Whether the delay multiplier event should be raised + [DataField] + public bool MultiplyDelay = true; + #region Event options /// /// The event that will get raised when the DoAfter has finished. If null, this will simply raise a /// - [DataField("event", required: true)] + [DataField(required: true)] public DoAfterEvent Event = default!; /// @@ -64,7 +75,7 @@ public sealed partial class DoAfterArgs /// Entity which will receive the directed event. If null, no directed event will be raised. /// [NonSerialized] - [DataField("eventTarget")] + [DataField] public EntityUid? EventTarget; public NetEntity? NetEventTarget; @@ -72,7 +83,7 @@ public sealed partial class DoAfterArgs /// /// Should the DoAfter event broadcast? If this is false, then should be a valid entity. /// - [DataField("broadcast")] + [DataField] public bool Broadcast; #endregion @@ -81,16 +92,24 @@ public sealed partial class DoAfterArgs /// /// Whether or not this do after requires the user to have hands. /// - [DataField("needHand")] + [DataField] public bool NeedHand; /// /// Whether we need to keep our active hand as is (i.e. can't change hand or change item). This also covers /// requiring the hand to be free (if applicable). This does nothing if is false. /// - [DataField("breakOnHandChange")] + [DataField] public bool BreakOnHandChange = true; + /// + /// Whether the do-after should get interrupted if we drop the + /// active item we started the do-after with + /// This does nothing if is false. + /// + [DataField] + public bool BreakOnDropItem = true; + /// /// If do_after stops when the user or target moves /// @@ -107,31 +126,31 @@ public sealed partial class DoAfterArgs /// /// Threshold for user and target movement /// - [DataField("movementThreshold")] + [DataField] public float MovementThreshold = 0.3f; /// /// Threshold for distance user from the used OR target entities. /// - [DataField("distanceThreshold")] + [DataField] public float? DistanceThreshold; /// /// Whether damage will cancel the DoAfter. See also . /// - [DataField("breakOnDamage")] + [DataField] public bool BreakOnDamage; /// /// Threshold for user damage. This damage has to be dealt in a single event, not over time. /// - [DataField("damageThreshold")] + [DataField] public FixedPoint2 DamageThreshold = 1; /// /// If true, this DoAfter will be canceled if the user can no longer interact with the target. /// - [DataField("requireCanInteract")] + [DataField] public bool RequireCanInteract = true; #endregion @@ -143,7 +162,7 @@ public sealed partial class DoAfterArgs /// Note that this will block even if the duplicate is cancelled because either DoAfter had /// enabled. /// - [DataField("blockDuplicate")] + [DataField] public bool BlockDuplicate = true; //TODO: User pref to not cancel on second use on specific doafters @@ -151,7 +170,7 @@ public sealed partial class DoAfterArgs /// If true, this will cancel any duplicate DoAfters when attempting to add a new DoAfter. See also /// . /// - [DataField("cancelDuplicate")] + [DataField] public bool CancelDuplicate = true; /// @@ -162,7 +181,7 @@ public sealed partial class DoAfterArgs /// Note that both DoAfters may have their own conditions, and they will be considered duplicated if either set /// of conditions is satisfied. /// - [DataField("duplicateCondition")] + [DataField] public DuplicateConditions DuplicateCondition = DuplicateConditions.All; #endregion @@ -184,6 +203,7 @@ public sealed partial class DoAfterArgs /// The entity at which the event will be directed. If null, the event will not be directed. /// The entity being targeted by the DoAFter. Not the same as . /// The entity being used during the DoAfter. E.g., a tool + /// Goobstation - The entity that should see doafter progress bar except doAfter entity public DoAfterArgs( IEntityManager entManager, EntityUid user, @@ -191,7 +211,8 @@ public DoAfterArgs( DoAfterEvent @event, EntityUid? eventTarget, EntityUid? target = null, - EntityUid? used = null) + EntityUid? used = null, + EntityUid? showTo = null) // Goobstation - Show doAfter popup to another entity { User = user; Delay = delay; @@ -199,18 +220,12 @@ public DoAfterArgs( Used = used; EventTarget = eventTarget; Event = @event; + ShowTo = showTo; // Goobstation NetUser = entManager.GetNetEntity(User); NetTarget = entManager.GetNetEntity(Target); NetUsed = entManager.GetNetEntity(Used); - } - - /// - /// An empty do-after constructor. This WILL cause runtime errors if used to create a do-after. Only use this if you really know what you're doing! - /// - [Obsolete("Use the other constructors if possible.")] - public DoAfterArgs() - { + NetShowTo = entManager.GetNetEntity(ShowTo); // Goobstation - Show doAfter popup to another entity } /// @@ -248,6 +263,7 @@ public DoAfterArgs(DoAfterArgs other) Broadcast = other.Broadcast; NeedHand = other.NeedHand; BreakOnHandChange = other.BreakOnHandChange; + BreakOnDropItem = other.BreakOnDropItem; BreakOnMove = other.BreakOnMove; BreakOnWeightlessMove = other.BreakOnWeightlessMove; MovementThreshold = other.MovementThreshold; @@ -259,12 +275,16 @@ public DoAfterArgs(DoAfterArgs other) BlockDuplicate = other.BlockDuplicate; CancelDuplicate = other.CancelDuplicate; DuplicateCondition = other.DuplicateCondition; + ShowTo = other.ShowTo; // Goobstation - Show doAfter popup to another entity + + MultiplyDelay = other.MultiplyDelay; // Goobstation // Networked NetUser = other.NetUser; NetTarget = other.NetTarget; NetUsed = other.NetUsed; NetEventTarget = other.NetEventTarget; + NetShowTo = other.NetShowTo; // Goobstation - Show doAfter popup to another entity Event = other.Event.Clone(); } diff --git a/Content.Shared/DoAfter/SharedDoAfterSystem.cs b/Content.Shared/DoAfter/SharedDoAfterSystem.cs index ed8be1ad657..48051e0a30d 100644 --- a/Content.Shared/DoAfter/SharedDoAfterSystem.cs +++ b/Content.Shared/DoAfter/SharedDoAfterSystem.cs @@ -130,6 +130,7 @@ private void OnDoAfterHandleState(EntityUid uid, DoAfterComponent comp, ref Comp doAfterArgs.Used = EnsureEntity(doAfterArgs.NetUsed, uid); doAfterArgs.User = EnsureEntity(doAfterArgs.NetUser, uid); doAfterArgs.EventTarget = EnsureEntity(doAfterArgs.NetEventTarget, uid); + doAfterArgs.ShowTo = EnsureEntity(doAfterArgs.NetShowTo, uid); // Goobstation - Show doAfter popup to another entity } comp.NextId = state.NextId; diff --git a/Content.Shared/Inventory/InventorySystem.Helpers.cs b/Content.Shared/Inventory/InventorySystem.Helpers.cs index 7e325abe216..8bb4cf8897f 100644 --- a/Content.Shared/Inventory/InventorySystem.Helpers.cs +++ b/Content.Shared/Inventory/InventorySystem.Helpers.cs @@ -139,4 +139,17 @@ public void SpawnItemOnEntity(EntityUid entity, EntProtoId item) //Try insert into hands, or drop on the floor _handsSystem.PickupOrDrop(entity, itemToSpawn, false); } + + // Goobstation + public bool TryGetContainingEntity(Entity entity, [NotNullWhen(true)] out EntityUid? containingEntity) + { + if (!_containerSystem.TryGetContainingContainer(entity, out var container) || !HasComp(container.Owner)) + { + containingEntity = null; + return false; + } + + containingEntity = container.Owner; + return true; + } } diff --git a/Content.Shared/Item/ItemToggle/ComponentTogglerSystem.cs b/Content.Shared/Item/ItemToggle/ComponentTogglerSystem.cs index 760cefe27d4..f483b8a2ee2 100644 --- a/Content.Shared/Item/ItemToggle/ComponentTogglerSystem.cs +++ b/Content.Shared/Item/ItemToggle/ComponentTogglerSystem.cs @@ -16,11 +16,20 @@ public override void Initialize() private void OnToggled(Entity ent, ref ItemToggledEvent args) { - var target = ent.Comp.Parent ? Transform(ent).ParentUid : ent.Owner; + ToggleComponent(ent, args.Activated); + } + + // Goobstation - Make this system more flexible + public void ToggleComponent(EntityUid uid, bool activate) + { + if (!TryComp(uid, out var component)) + return; + + var target = component.Parent ? Transform(uid).ParentUid : uid; - if (args.Activated) - EntityManager.AddComponents(target, ent.Comp.Components); + if (activate) + EntityManager.AddComponents(target, component.Components); else - EntityManager.RemoveComponents(target, ent.Comp.RemoveComponents ?? ent.Comp.Components); + EntityManager.RemoveComponents(target, component.RemoveComponents ?? component.Components); } } diff --git a/Content.Shared/_Goobstation/Clothing/Components/SealableClothingComponent.cs b/Content.Shared/_Goobstation/Clothing/Components/SealableClothingComponent.cs new file mode 100644 index 00000000000..1a550d6c8bf --- /dev/null +++ b/Content.Shared/_Goobstation/Clothing/Components/SealableClothingComponent.cs @@ -0,0 +1,30 @@ +using Content.Shared._Goobstation.Clothing.Systems; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; + +namespace Content.Shared._Goobstation.Clothing.Components; + +/// Defines the clothing entity that can be sealed by +[RegisterComponent] +[NetworkedComponent, AutoGenerateComponentState] +[Access(typeof(SharedSealableClothingSystem))] +public sealed partial class SealableClothingComponent : Component +{ + [DataField, AutoNetworkedField] + public bool IsSealed = false; + + [DataField, AutoNetworkedField] + public TimeSpan SealingTime = TimeSpan.FromSeconds(1.75); + + [DataField] + public LocId SealUpPopup = "sealable-clothing-seal-up"; + + [DataField] + public LocId SealDownPopup = "sealable-clothing-seal-down"; + + [DataField] + public SoundSpecifier SealUpSound = new SoundPathSpecifier("/Audio/Mecha/mechmove03.ogg"); + + [DataField] + public SoundSpecifier SealDownSound = new SoundPathSpecifier("/Audio/Mecha/mechmove03.ogg"); +} diff --git a/Content.Shared/_Goobstation/Clothing/Components/SealableClothingControlComponent.cs b/Content.Shared/_Goobstation/Clothing/Components/SealableClothingControlComponent.cs new file mode 100644 index 00000000000..7ddb19cfbdd --- /dev/null +++ b/Content.Shared/_Goobstation/Clothing/Components/SealableClothingControlComponent.cs @@ -0,0 +1,75 @@ +using Content.Shared._Goobstation.Clothing.Systems; +using Content.Shared.Inventory; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared._Goobstation.Clothing.Components; + +/// Component used to designate contol of sealable clothing. It'll contain action to seal clothing +[RegisterComponent] +[NetworkedComponent, AutoGenerateComponentState] +[Access(typeof(SharedSealableClothingSystem))] +public sealed partial class SealableClothingControlComponent : Component +{ + /// Action that used to start sealing + [DataField, AutoNetworkedField] + public EntProtoId SealAction = "ActionClothingSeal"; + + [DataField, AutoNetworkedField] + public EntityUid? SealActionEntity; + + /// Slot required for control to show action + [DataField("requiredSlot"), AutoNetworkedField] + public SlotFlags RequiredControlSlot = SlotFlags.BACK; + + /// True if clothing in sealing/unsealing process, false if not + [DataField, AutoNetworkedField] + public bool IsInProcess = false; + + /// True if clothing is currently sealed and need to start unsealing process. False if opposite + [DataField, AutoNetworkedField] + public bool IsCurrentlySealed = false; + + /// Queue of attached parts that should be sealed/unsealed + [DataField, AutoNetworkedField] + public Queue ProcessQueue = new(); + + /// Uid of entity that currently wear seal control + [DataField, AutoNetworkedField] + public EntityUid? WearerEntity; + + /// Doafter time for other players to start sealing via stripping menu + [DataField, AutoNetworkedField] + public TimeSpan NonWearerSealingTime = TimeSpan.FromSeconds(4); + + #region Popups & Sounds + + [DataField] + public LocId ToggleFailedPopup = "sealable-clothing-equipment-not-toggled"; + + [DataField] + public LocId SealFailedPopup = "sealable-clothing-equipment-seal-failed"; + + [DataField] + public LocId SealedInProcessToggleFailPopup = "sealable-clothing-sealed-process-toggle-fail"; + + [DataField] + public LocId UnsealedInProcessToggleFailPopup = "sealable-clothing-unsealed-process-toggle-fail"; + + [DataField] + public LocId CurrentlySealedToggleFailPopup = "sealable-clothing-sealed-toggle-fail"; + + [DataField] + public LocId VerbText = "sealable-clothing-seal-verb"; + + [DataField] + public SoundSpecifier FailSound = new SoundPathSpecifier("/Audio/Machines/scanbuzz.ogg"); + + [DataField] + public SoundSpecifier SealCompleteSound = new SoundPathSpecifier("/Audio/_Goobstation/Mecha/nominal.ogg"); + + [DataField] + public SoundSpecifier UnsealCompleteSound = new SoundPathSpecifier("/Audio/_Goobstation/Machines/computer_end.ogg"); + #endregion +} diff --git a/Content.Shared/_Goobstation/Clothing/Components/SealableClothingRequiresPowerComponent.cs b/Content.Shared/_Goobstation/Clothing/Components/SealableClothingRequiresPowerComponent.cs new file mode 100644 index 00000000000..784adb9fd81 --- /dev/null +++ b/Content.Shared/_Goobstation/Clothing/Components/SealableClothingRequiresPowerComponent.cs @@ -0,0 +1,30 @@ +using Content.Shared.Alert; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared._Goobstation.Clothing.Components; + +[RegisterComponent] +[NetworkedComponent, AutoGenerateComponentState] +public sealed partial class SealableClothingRequiresPowerComponent : Component +{ + [DataField] + public LocId NotPoweredPopup = "sealable-clothing-not-powered"; + + [DataField] + public LocId OpenSealedPanelFailPopup = "sealable-clothing-open-sealed-panel-fail"; + + [DataField] + public LocId ClosePanelFirstPopup = "sealable-clothing-close-panel-first"; + + /// Movement speed when without power + [DataField] + public float MovementSpeedPenalty = 0.3f; + + [DataField, AutoNetworkedField] + public bool IsPowered = false; + + /// Alert to show for the suit's power + [DataField] + public ProtoId SuitPowerAlert = "ModsuitPower"; +} diff --git a/Content.Shared/_Goobstation/Clothing/SealableClothingVisuals.cs b/Content.Shared/_Goobstation/Clothing/SealableClothingVisuals.cs new file mode 100644 index 00000000000..ea4897b6ef3 --- /dev/null +++ b/Content.Shared/_Goobstation/Clothing/SealableClothingVisuals.cs @@ -0,0 +1,9 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared._Goobstation.Clothing; + +[Serializable, NetSerializable] +public enum SealableClothingVisuals : byte +{ + Sealed +} diff --git a/Content.Shared/_Goobstation/Clothing/Systems/SharedPoweredSealableClothingSystem.cs b/Content.Shared/_Goobstation/Clothing/Systems/SharedPoweredSealableClothingSystem.cs new file mode 100644 index 00000000000..5902688e1be --- /dev/null +++ b/Content.Shared/_Goobstation/Clothing/Systems/SharedPoweredSealableClothingSystem.cs @@ -0,0 +1,73 @@ +using Content.Shared._Goobstation.Clothing.Components; +using Content.Shared.Inventory; +using Content.Shared.Popups; +using Content.Shared.PowerCell; +using Content.Shared.Wires; + +namespace Content.Shared._Goobstation.Clothing.Systems; + +/// Used for sealable clothing that requires power to work +public abstract class SharedPoweredSealableClothingSystem : EntitySystem +{ + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly SharedPowerCellSystem _powerCellSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnRequiresPowerMapInit); + SubscribeLocalEvent(OnRequiresPowerSealAttempt); + SubscribeLocalEvent(OnRequiresPowerChangePanelAttempt); + } + + private void OnRequiresPowerMapInit(Entity entity, ref MapInitEvent args) + { + if (!TryComp(entity, out SealableClothingControlComponent? control) || !TryComp(entity, out PowerCellDrawComponent? draw)) + return; + + draw.Enabled = control.IsCurrentlySealed; + } + + /// Checks if control have enough power to seal + private void OnRequiresPowerSealAttempt(Entity entity, ref ClothingSealAttemptEvent args) + { + if (!TryComp(entity, out SealableClothingControlComponent? controlComp) + || !TryComp(entity, out PowerCellDrawComponent? cellDrawComp) + || args.Cancelled) + return; + + // Prevents sealing if wires panel is opened + if (TryComp(entity, out WiresPanelComponent? panel) && panel.Open) + { + _popupSystem.PopupClient(Loc.GetString(entity.Comp.ClosePanelFirstPopup), entity, args.User); + args.Cancel(); + return; + } + + // Control shouldn't use charge on unsealing + if (controlComp.IsCurrentlySealed) + return; + + if (!_powerCellSystem.HasDrawCharge(entity, cellDrawComp) || !_powerCellSystem.HasActivatableCharge(entity, cellDrawComp)) + { + _popupSystem.PopupClient(Loc.GetString(entity.Comp.NotPoweredPopup), entity, args.User); + args.Cancel(); + } + } + + /// Prevents wires panel from opening if clothing is sealed + private void OnRequiresPowerChangePanelAttempt(Entity entity, ref AttemptChangePanelEvent args) + { + if (args.Cancelled || !TryComp(entity, out SealableClothingControlComponent? controlComp)) + return; + + if (controlComp.IsCurrentlySealed || controlComp.IsInProcess) + { + _popupSystem.PopupClient(Loc.GetString(entity.Comp.OpenSealedPanelFailPopup), entity, args.User); + args.Cancelled = true; + } + } +} + + diff --git a/Content.Shared/_Goobstation/Clothing/Systems/SharedSealableClothingSystem.cs b/Content.Shared/_Goobstation/Clothing/Systems/SharedSealableClothingSystem.cs new file mode 100644 index 00000000000..98d18e740df --- /dev/null +++ b/Content.Shared/_Goobstation/Clothing/Systems/SharedSealableClothingSystem.cs @@ -0,0 +1,392 @@ +using Content.Shared._Goobstation.Clothing.Components; +using Content.Shared.ActionBlocker; +using Content.Shared.Actions; +using Content.Shared.Clothing; +using Content.Shared.Clothing.EntitySystems; +using Content.Shared.DoAfter; +using Content.Shared.IdentityManagement; +using Content.Shared.Interaction; +using Content.Shared.Item.ItemToggle; +using Content.Shared.Popups; +using Content.Shared.PowerCell; +using Content.Shared.Verbs; +using Content.Shared.Wires; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Network; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; + +namespace Content.Shared._Goobstation.Clothing.Systems; + +/// System used for sealable clothing +public abstract class SharedSealableClothingSystem : EntitySystem +{ + [Dependency] private readonly INetManager _netManager = default!; + [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; + [Dependency] private readonly ActionContainerSystem _actionContainerSystem = default!; + [Dependency] private readonly ComponentTogglerSystem _componentTogglerSystem = default!; + [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; + [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; + [Dependency] private readonly SharedAudioSystem _audioSystem = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly SharedPowerCellSystem _powerCellSystem = default!; + [Dependency] private readonly ToggleableClothingSystem _toggleableSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnPartSealingComplete); + + SubscribeLocalEvent(OnControlSealingComplete); + SubscribeLocalEvent(OnControlEquip); + SubscribeLocalEvent(OnControlUnequip); + SubscribeLocalEvent(OnControlRemove); + SubscribeLocalEvent(OnControlGetItemActions); + SubscribeLocalEvent>(OnEquipmentVerb); + SubscribeLocalEvent(OnControlMapInit); + SubscribeLocalEvent(OnSealClothingDoAfter); + SubscribeLocalEvent(OnControlSealEvent); + //SubscribeLocalEvent(OnStartSealingDoAfter); + SubscribeLocalEvent(OnToggleClothingAttempt); + } + + #region Events + + /// Toggles components on part when suit complete sealing process + private void OnPartSealingComplete(Entity part, ref ClothingPartSealCompleteEvent args) + => _componentTogglerSystem.ToggleComponent(part, args.IsSealed); + + /// Toggles components on control when suit complete sealing process + private void OnControlSealingComplete(Entity control, ref ClothingControlSealCompleteEvent args) + => _componentTogglerSystem.ToggleComponent(control, args.IsSealed); + + /// Add/Remove wearer on clothing equip/unequip + private void OnControlEquip(Entity control, ref ClothingGotEquippedEvent args) + { + control.Comp.WearerEntity = args.Wearer; + Dirty(control); + } + + private void OnControlUnequip(Entity control, ref ClothingGotUnequippedEvent args) + { + control.Comp.WearerEntity = null; + Dirty(control); + } + + /// Removes seal action on component remove + private void OnControlRemove(Entity control, ref ComponentRemove args) + { + var comp = control.Comp; + + _actionsSystem.RemoveAction(comp.SealActionEntity); + } + + /// Ensures seal action to wearer when it equip the seal control + private void OnControlGetItemActions(Entity control, ref GetItemActionsEvent args) + { + var (uid, comp) = control; + + if (comp.SealActionEntity == null || args.SlotFlags != comp.RequiredControlSlot) + return; + + args.AddAction(comp.SealActionEntity.Value); + } + + /// Adds unsealing verbs to sealing control allowing other users to unseal/seal clothing via stripping + private void OnEquipmentVerb(Entity control, ref GetVerbsEvent args) + { + var (uid, comp) = control; + var user = args.User; + + if (!args.CanComplexInteract + // Since sealing control in wearer's container system just won't show verb on args.CanAccess + || !_interactionSystem.InRangeUnobstructed(user, uid) + || comp.WearerEntity == null + || comp.WearerEntity != user + && _actionBlockerSystem.CanInteract(comp.WearerEntity.Value, null)) + return; + + var verbIcon = comp.IsCurrentlySealed ? + new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/unlock.svg.192dpi.png")) : + new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/lock.svg.192dpi.png")); + + var verb = new Verb() + { + Icon = verbIcon, + Priority = 5, + Text = Loc.GetString(comp.VerbText), + Act = () => TryStartSealToggleProcess(control, user) + }; + + /* This should make as do after to start unsealing of suit with verb, but, for some reason i couldn't figure out, it ends with doAfter enumerator change exception + * Would be nice if some can fix this, yet unsealing will be possible only on incapacitated wearers + if (args.User == comp.WearerEntity) + { + verb.Act = () => TryStartSealToggleProcess(control); + } + else + { + var doAfterArgs = new DoAfterArgs(EntityManager, args.User, comp.NonWearerSealingTime, new StartSealingProcessDoAfterEvent(), uid) + { + RequireCanInteract = true, + BreakOnMove = true, + BlockDuplicate = true + }; + verb.Act = () => _doAfterSystem.TryStartDoAfter(doAfterArgs); + }*/ + + args.Verbs.Add(verb); + } + + /// Ensure actionEntity on map init + private void OnControlMapInit(Entity control, ref MapInitEvent args) + { + var (uid, comp) = control; + _actionContainerSystem.EnsureAction(uid, ref comp.SealActionEntity, comp.SealAction); + } + + /* This should make as do after to start unsealing of suit with verb, but, for some reason i couldn't figure out, it ends with doAfter enumerator change exception + * Would be nice if some can fix this, yet unsealing will be possible only on incapacitated wearers + private void OnStartSealingDoAfter(Entity control, ref StartSealingProcessDoAfterEvent args) + { + if (args.Cancelled) + return; + + TryStartSealToggleProcess(control); + }*/ + + /// Trying to start sealing on action. It'll notify wearer if process already started + private void OnControlSealEvent(Entity control, ref SealClothingEvent args) + { + var (uid, comp) = control; + + if (!_actionBlockerSystem.CanInteract(args.Performer, null)) + return; + + if (comp.IsInProcess) + { + if (comp.IsCurrentlySealed) + { + _popupSystem.PopupClient(Loc.GetString(comp.SealedInProcessToggleFailPopup), uid, args.Performer); + _audioSystem.PlayPredicted(comp.FailSound, uid, args.Performer); + } + else + { + _popupSystem.PopupClient(Loc.GetString(comp.UnsealedInProcessToggleFailPopup), uid, args.Performer); + _audioSystem.PlayPredicted(comp.FailSound, uid, args.Performer); + } + + return; + } + + TryStartSealToggleProcess(control, args.Performer); + } + + /// Toggle seal on one part and starts same process on next part + private void OnSealClothingDoAfter(Entity control, ref SealClothingDoAfterEvent args) + { + var (uid, comp) = control; + + if (args.Cancelled || args.Handled || args.Target == null) + return; + + var part = args.Target; + + if (!TryComp(part, out var sealableComponent)) + return; + + sealableComponent.IsSealed = !comp.IsCurrentlySealed; + + Dirty(part.Value, sealableComponent); + + _audioSystem.PlayPvs(sealableComponent.SealUpSound, uid); + + _appearanceSystem.SetData(part.Value, SealableClothingVisuals.Sealed, sealableComponent.IsSealed); + + var ev = new ClothingPartSealCompleteEvent(sealableComponent.IsSealed); + RaiseLocalEvent(part.Value, ref ev); + + NextSealProcess(control); + } + + /// Prevents clothing from toggling if it's sealed or in sealing process + private void OnToggleClothingAttempt(Entity control, ref ToggleClothingAttemptEvent args) + { + var (uid, comp) = control; + + // Popup if currently sealing + if (comp.IsInProcess) + { + _popupSystem.PopupClient(Loc.GetString(comp.UnsealedInProcessToggleFailPopup), uid, args.User); + _audioSystem.PlayPredicted(comp.FailSound, uid, args.User); + args.Cancel(); + + return; + } + + // Popup if sealed, but not in process + if (comp.IsCurrentlySealed) + { + _popupSystem.PopupClient(Loc.GetString(comp.CurrentlySealedToggleFailPopup), uid, args.User); + _audioSystem.PlayPredicted(comp.FailSound, uid, args.User); + args.Cancel(); + + return; + } + + return; + } + #endregion + + /// Tries to start sealing process + public bool TryStartSealToggleProcess(Entity control, EntityUid? user = null) + { + var (uid, comp) = control; + + // Prevent sealing/unsealing if modsuit don't have wearer or already started process + if (comp.WearerEntity == null || comp.IsInProcess) + return false; + + if (user == null) + user = comp.WearerEntity; + + var ev = new ClothingSealAttemptEvent(user.Value); + RaiseLocalEvent(control, ev); + + if (ev.Cancelled) + return false; + + // All parts required to be toggled to perform sealing + if (_toggleableSystem.GetAttachedToggleStatus(uid) != ToggleableClothingAttachedStatus.AllToggled) + { + _popupSystem.PopupClient(Loc.GetString(comp.ToggleFailedPopup), uid, user); + _audioSystem.PlayPredicted(comp.FailSound, uid, user); + return false; + } + + // Trying to get all clothing to seal + var sealeableList = _toggleableSystem.GetAttachedClothingsList(uid); + if (sealeableList == null) + return false; + + foreach (var sealeable in sealeableList) + { + if (!HasComp(sealeable)) + { + _popupSystem.PopupEntity(Loc.GetString(comp.ToggleFailedPopup), uid); + _audioSystem.PlayPredicted(comp.FailSound, uid, user); + + comp.ProcessQueue.Clear(); + Dirty(control); + + return false; + } + + comp.ProcessQueue.Enqueue(EntityManager.GetNetEntity(sealeable)); + } + + comp.IsInProcess = true; + Dirty(control); + + NextSealProcess(control); + + return true; + } + + /// Recursively seals/unseals all parts of sealable clothing + private void NextSealProcess(Entity control) + { + var (uid, comp) = control; + + // Finish sealing process + if (comp.ProcessQueue.Count == 0) + { + comp.IsInProcess = false; + comp.IsCurrentlySealed = !comp.IsCurrentlySealed; + + _audioSystem.PlayEntity(comp.IsCurrentlySealed ? comp.SealCompleteSound : comp.UnsealCompleteSound, comp.WearerEntity!.Value, uid); + + var ev = new ClothingControlSealCompleteEvent(comp.IsCurrentlySealed); + RaiseLocalEvent(control, ref ev); + + _appearanceSystem.SetData(uid, SealableClothingVisuals.Sealed, comp.IsCurrentlySealed); + + Dirty(control); + return; + } + + var processingPart = EntityManager.GetEntity(comp.ProcessQueue.Dequeue()); + Dirty(control); + + if (!TryComp(processingPart, out var sealableComponent) || !comp.IsInProcess) + { + _popupSystem.PopupClient(Loc.GetString(comp.ToggleFailedPopup), uid, comp.WearerEntity); + _audioSystem.PlayPredicted(comp.FailSound, uid, comp.WearerEntity); + + NextSealProcess(control); + return; + } + + // If part is sealed when control trying to seal - it should just skip this part + if (sealableComponent.IsSealed != comp.IsCurrentlySealed) + { + NextSealProcess(control); + return; + } + + var doAfterArgs = new DoAfterArgs(EntityManager, uid, sealableComponent.SealingTime, new SealClothingDoAfterEvent(), uid, target: processingPart, showTo: comp.WearerEntity) + { + NeedHand = false, + RequireCanInteract = false, + }; + + // Checking for client here to skip first process popup spam that happens. Predicted popups don't work here because doafter starts on sealable control, not on player. + if (!_doAfterSystem.TryStartDoAfter(doAfterArgs) || _netManager.IsClient) + return; + + if (comp.IsCurrentlySealed) + + _popupSystem.PopupEntity(Loc.GetString(sealableComponent.SealDownPopup, + ("partName", Identity.Name(processingPart, EntityManager))), + uid, comp.WearerEntity!.Value); + else + _popupSystem.PopupEntity(Loc.GetString(sealableComponent.SealUpPopup, + ("partName", Identity.Name(processingPart, EntityManager))), + uid, comp.WearerEntity!.Value); + } +} + +[Serializable, NetSerializable] +public sealed partial class SealClothingDoAfterEvent : SimpleDoAfterEvent { } + +[Serializable, NetSerializable] +public sealed partial class StartSealingProcessDoAfterEvent : SimpleDoAfterEvent { } + +public sealed partial class SealClothingEvent : InstantActionEvent { } + +/// Raises on control when clothing finishes it's sealing or unsealing process +[ByRefEvent] +public readonly record struct ClothingControlSealCompleteEvent(bool IsSealed) +{ + public readonly bool IsSealed = IsSealed; +} + +/// Raises on part when clothing finishes it's sealing or unsealing process +[ByRefEvent] +public readonly record struct ClothingPartSealCompleteEvent(bool IsSealed) +{ + public readonly bool IsSealed = IsSealed; +} + +public sealed partial class ClothingSealAttemptEvent : CancellableEntityEventArgs +{ + public EntityUid User; + + public ClothingSealAttemptEvent(EntityUid user) + { + User = user; + } +} diff --git a/Content.Shared/_Goobstation/Wires/Components/ItemSlotsRequirePanelComponent.cs b/Content.Shared/_Goobstation/Wires/Components/ItemSlotsRequirePanelComponent.cs new file mode 100644 index 00000000000..dea1445bffb --- /dev/null +++ b/Content.Shared/_Goobstation/Wires/Components/ItemSlotsRequirePanelComponent.cs @@ -0,0 +1,14 @@ +using Content.Shared.Containers.ItemSlots; +using Robust.Shared.GameStates; + +namespace Content.Shared._Goobstation.Wires.Components; + +/// This is used for items slots that require entity to have wire panel for interactions +[RegisterComponent] +[NetworkedComponent] +public sealed partial class ItemSlotsRequirePanelComponent : Component +{ + /// For each slot: true - slot require opened panel for interaction, false - slot require closed panel for interaction + [DataField] + public Dictionary Slots = new(); +} diff --git a/Content.Shared/_Goobstation/Wires/Systems/RequirePanelSystem.cs b/Content.Shared/_Goobstation/Wires/Systems/RequirePanelSystem.cs new file mode 100644 index 00000000000..10c48f419af --- /dev/null +++ b/Content.Shared/_Goobstation/Wires/Systems/RequirePanelSystem.cs @@ -0,0 +1,37 @@ +using Content.Shared._Goobstation.Wires.Components; +using Content.Shared.Containers.ItemSlots; +using Content.Shared.Wires; + +namespace Content.Shared._Goobstation.Wires.Systems; + +public sealed partial class RequirePanelSystem : EntitySystem +{ + [Dependency] private readonly ItemSlotsSystem _itemSlots = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(ItemSlotInsertAttempt); + SubscribeLocalEvent(ItemSlotEjectAttempt); + } + + private void ItemSlotInsertAttempt(Entity entity, ref ItemSlotInsertAttemptEvent args) + => args.Cancelled = !CheckPanelStateForItemSlot(entity, args.Slot.ID); + + private void ItemSlotEjectAttempt(Entity entity, ref ItemSlotEjectAttemptEvent args) + => args.Cancelled = !CheckPanelStateForItemSlot(entity, args.Slot.ID); + + public bool CheckPanelStateForItemSlot(Entity entity, string? slot) + { + var (uid, comp) = entity; + + if (slot == null + // If slot doesn't require a wire panel - don't cancel interaction + || !comp.Slots.TryGetValue(slot, out var isRequireOpen) + || !TryComp(uid, out var wiresPanel)) + return false; + + return wiresPanel.Open == isRequireOpen; + } +} diff --git a/Resources/Audio/Machines/attributions.yml b/Resources/Audio/Machines/attributions.yml index cd257ada221..be04c55c64e 100644 --- a/Resources/Audio/Machines/attributions.yml +++ b/Resources/Audio/Machines/attributions.yml @@ -166,7 +166,7 @@ license: "CC0-1.0" copyright: "by Ko4erga" source: "https://github.com/space-wizards/space-station-14/pull/30431" - + - files: ["double_ring.ogg"] license: "CC0-1.0" copyright: "Created by fspera, converted to OGG and modified by chromiumboy." @@ -180,3 +180,8 @@ license: "CC0-1.0" copyright: "by ScarKy0" source: "https://github.com/space-wizards/space-station-14/pull/32012" + +- files: ["scanbuzz.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Taken from TG station" + source: "https://github.com/tgstation/tgstation/pull/39986" diff --git a/Resources/Audio/Machines/scanbuzz.ogg b/Resources/Audio/Machines/scanbuzz.ogg new file mode 100644 index 0000000000000000000000000000000000000000..d3422bc8f4d766d7d614bce2c9e6d637cc01c412 GIT binary patch literal 11613 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 literal 0 HcmV?d00001 diff --git a/Resources/Audio/_Goobstation/Machines/attributions.yml b/Resources/Audio/_Goobstation/Machines/attributions.yml new file mode 100644 index 00000000000..7623347dabd --- /dev/null +++ b/Resources/Audio/_Goobstation/Machines/attributions.yml @@ -0,0 +1,4 @@ +- files: ["computer_end.ogg"] + license: "CC-BY-NC-SA-3.0" + copyright: "Taken from TG station." + source: "https://github.com/tgstation/tgstation/pull/32336" diff --git a/Resources/Audio/_Goobstation/Machines/computer_end.ogg b/Resources/Audio/_Goobstation/Machines/computer_end.ogg new file mode 100644 index 0000000000000000000000000000000000000000..92944055732e421d063ca0bf8205a56777bdaab2 GIT binary patch literal 29626 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 pes-t7;=2^`vd8mR`~={C*L*)P4FBapZ++RDao@ zDLa7Tj3q&S!3+-1ZlnP@^E_P~LnNjqCn&I4>`<|CkT5=^-yq2=`&Qss*SV|WOb2u# zzj3Q3xat0GjS@KfWBOkwTMv2b4#uE!|62bx*>ouKC)F?->TQTmU^o1tUtpi`zwuDT zfxivXXMP;~>mYyR!M}r77W`*8^5gh{4f014e(i5~UC(@C)8GBq{xjcR_y7NYeuL)U z|37zfDX?AsSAN>%pi`9h6cI(cprA`ir=ByoEVr&&VN%-xbSZoTi;Wb;fe5q~{xfr(7*>577rY^4#@A4C}K{*qZt}p2Q#_`hUAftiY z?8L|av);_)IcD*x1868iq?SqZ?$om7Z{IzXd2C^4njab(H($H`o^R>msly}VVk7e%Lq)`Wij*518)-t86I2fp7H@7&B3(p$gR zTpd$8fJzwm)!A+R7@Bk>T=c>FAFKTiSnXTIki9^wL3@Al(?qfPwhqP>ywwcP8>YKe zJI@Xgw*SqPqWW9ZfpLxShH15o4QE*Av)q4KA6Hy;rcQVX!!=HzbB#~tx7YTaS?C`5 z`Cr59ou;q(Urf8{TN<39?=iW&qh*iD-)=rZLBaXoW=z?_ukTdPP~Lp=K5yE8E1&v? iG=u5`T;D*Re)5VL**jFCp<&g{tNi;EW zl9MyCv$YQi4!FZs<_1*5SQ6wH%;50sMjDXQ;_2cTqA_uAza!US10I%Eh3_^CPn_WA zc*)MSPu#pKe#1}4JMX8@%V~|_Qmww7_#w(AJWYn*A#a`0dExH|a`xTc{FTvzGb7=s z)S_HpP0{`>d!++bE55td79k?=GR#DOr|>?N6|b2?9y(mzv3vdWr77h-`~J*6qV?o( jyIPx$LP+MRQ%ZjQ-WL)1J$((nZZ!c|YqN}|Y65ts``kYKj|r&i0M@(J{der> zs5*dI-2)uIr+>aLgQh@{5vIW$TnNUPtkWz2004mhOZ7HY>kG%T&uD$sy$+{F0Hq5k zjQ~m)P#OW0E}*pF05o%JO`<<5Px0ZXzY?A8L1>DN_l1m7RRGpn*&NPYY(ZlSsZ|xg z@pip$M3{2#yV}}jzc}8mst{BGq*#L+o3>`ALCrz_I(#kDV1MtxE2;q8I()8O8Y#K@ m{JjJ3%>Et#00000n4>=dq+Rso#v_FQ0000Px%TS-JgRCt{2+COjFKo|z_hnAs;R`Mb1BumZV#Y!^q6_8kAWME-u&B9hCd%i~o zkI4sM_Eax{$Q>e?2q|(QL#$(n^~qKZ>qzu6~)>32;Pq>0+MVz9-l?y@mZ8tn5@#|Q!Zf8A4QyNXf|c? zWCdw9B{v5Q%&$=@z%<*LUP35R`~rn(ww1P5D!_5vOl$B(+seA~ zH9+|kU@aq4s3NhJ@wTVH<>i-yKlJ}p#ms~dLI@#*kmCFR4aM4zj!SX)00000NkvXX Hu0mjf{17($ literal 0 HcmV?d00001 diff --git a/Resources/Textures/_Goobstation/Clothing/Back/Modsuits/standard.rsi/meta.json b/Resources/Textures/_Goobstation/Clothing/Back/Modsuits/standard.rsi/meta.json new file mode 100644 index 00000000000..ad0319fbe8a --- /dev/null +++ b/Resources/Textures/_Goobstation/Clothing/Back/Modsuits/standard.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "From tgstation - https://github.com/tgstation/tgstation/pull/59109", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "control" + }, + { + "name": "control-sealed", + "delays": [[ 0.1, 0.1, 0.1, 0.1 ]] + }, + { + "name": "equipped-BACKPACK", + "directions": 4 + }, + { + "name": "equipped-BACKPACK-sealed", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/_Goobstation/Clothing/Hands/Modsuits/standard.rsi/equipped-HAND-sealed.png b/Resources/Textures/_Goobstation/Clothing/Hands/Modsuits/standard.rsi/equipped-HAND-sealed.png new file mode 100644 index 0000000000000000000000000000000000000000..bbe4c7db6f009aea6c342f270ebb88c1509f7002 GIT binary patch literal 145 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|yggkULn`LH zy=BPDpdi5Pu&1G`yqPmc;b&xIqSC{>e60n#tUzT93BkwDh}rrZ=~i7c>^EBfjTt1! mz;IxF-0l1Mb}S4GA9k{CnIInj)?RQMNW#Px$s!2paRCt{2+Ch$jFcb#h*NGP}=><&a!WCEW5+(5law5IKBytI_;K~Iilem6O zxj||L%|Jy>}d%RR6Jcs}Q000000RA1-b#T32hdj?|x7&4|*XD3Ig!B1K z`~5z7?Oj=xqw`-bm(H~xF~O|0Atib0`I7m28*sbbQgV-8H+TNd1~4;u?}_MVJH`aO zpXa?NW{xUCGJo$5U}iGL#N>W3#*DuBx$<{5K&b^8W5|0?&NWd1oomQ@PsSKhY7wq)`r+Y<($g03`559Ne!^p2CX&a zc^*Bjox}Y@3axeWw*-TJ0#nWYr!=&tqy|(~rI?xOx+dqE#}M+_D|bd)?4sBP=NhW( znwXiYs!AHjChq{H7NoVNEL*i6UoMxaA*1~kJIhw2wH`E%pZs6odi|O@pZvG4+_-pC)+@EQef%81=1eb&_5a7OZC@C+&+NoXmBP6u^UTx>E)=9K z-w`T5>B7QFwIg4ZZ9kY@hv(6qRhUT3C} RkOXuhgQu&X%Q~loCIDOxYkL3y literal 0 HcmV?d00001 diff --git a/Resources/Textures/_Goobstation/Clothing/Hands/Modsuits/standard.rsi/gauntlets.png b/Resources/Textures/_Goobstation/Clothing/Hands/Modsuits/standard.rsi/gauntlets.png new file mode 100644 index 0000000000000000000000000000000000000000..86570695735f6ea2366abf40960b1bd20f279833 GIT binary patch literal 270 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|Bm#UwTn`*L zU|)HVmq#)rB+@d zvM+$Y<8xpg%Ztw)@2XvU-YcmdzF4E7G+(>GMWU-SpH?XTER8tFqd7 z7I!hxcYCx8l^0C?vubCD#JxI}6;-Tms!NOda(x{blp L)z4*}Q$iB}t^sQT literal 0 HcmV?d00001 diff --git a/Resources/Textures/_Goobstation/Clothing/Hands/Modsuits/standard.rsi/meta.json b/Resources/Textures/_Goobstation/Clothing/Hands/Modsuits/standard.rsi/meta.json new file mode 100644 index 00000000000..c89900d9aa1 --- /dev/null +++ b/Resources/Textures/_Goobstation/Clothing/Hands/Modsuits/standard.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "From tgstation - https://github.com/tgstation/tgstation/pull/59109", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "gauntlets" + }, + { + "name": "gauntlets-sealed" + }, + { + "name": "equipped-HAND", + "directions": 4 + }, + { + "name": "equipped-HAND-sealed", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/_Goobstation/Clothing/Head/Modsuits/standard.rsi/equipped-HEAD-sealed.png b/Resources/Textures/_Goobstation/Clothing/Head/Modsuits/standard.rsi/equipped-HEAD-sealed.png new file mode 100644 index 0000000000000000000000000000000000000000..bfb719e9856b225f2b3b685c101ff78aaa70bfe3 GIT binary patch literal 763 zcmVPx%wMj%lRCt{2nlWqJKorOSDz%|q#DNK9F5vf6SI7tG zS1^Qj$fCt#Adtl|ScA7=V&&YMk2h|xtTGBpTExs{X2C%?ag3?5mukfrm<~X1s=z-YUOp? zwlSSfwffW&AdX{vI9Pm6d$Skc7s-4+S8c6ULmbDd&3~_h_A20cp7>b%0&%dn=p>Br z5Z@vQg2Gn=Im~U_24f7Ia}{_H1XkgzKoA7hLwpM>jMTBM;H7=w`Dw2PZ_=OfJ|I8g zY%BP6&>aB;K@bE%5ClOG1VIo4LA))DUyAGCiLs#U7r*XWw*1(7L(NXb8t~FdB`J z7eUqu4ElFSd$YnuLcTU3Z%>W@>DzcbUfaIW9KaYui4Re}zBKvyMk!xkqQr;Bsd??c zfGkLmnc1{A!vKp~0V|Av<9#D%Uc(9_p#53ZCZxR?%7F641g#N}Wf?fqe95OQ%e4B@-Uzv_E9&(+CX)#&l?w8!ffYvh{_Sg_li=CKmB`yG@gcG-gY>D} z?P}Es80`e52-Rv;O^5&r4_Y@wT(m&R=^L{^?%0>d7fWJ%%2dj6ZzpPX1WMG)3WE_XpXN z_xt#3)aC@udiU$nM{#@ByzURNC(kdeM{vsz3Hdl> z%UlH}-YJz`vsI$aajwmVN1;CoES}8Yz;lXchwr>Px#^+`lQR9J=Wlrf6JP!NW{CwqoKdTU`@QHUwMge>kUJb;B~P{d1E#s>ylA@yz@ z$R*wu%RC~AWJD|Ht6nCLng5?m1`NY^cbw+3_dcHI`*Uc&+e|-Wt}M$s=lswJ0Qg?) zLKqE5^W-8v-=BjkE7r@I%9QGIfA|%PkFyB?M}MF$8qz$WE*e}}@pIaC&fC)nxaAy( zu{2Kzqk$%+{u2mCf4GWCCw^Dm^Qbx?PNn_NKqa6q8sNTrx$_bMuRuKm;#AJAtm1HL z-KzyiF)8dIPQ4U&Wu+3J+5_;v3);`^-%h&&9uja%grF^M7=~dO0DlD2TNjd_2Uq|A N002ovPDHLkV1mv*g3JH_ literal 0 HcmV?d00001 diff --git a/Resources/Textures/_Goobstation/Clothing/Head/Modsuits/standard.rsi/helmet.png b/Resources/Textures/_Goobstation/Clothing/Head/Modsuits/standard.rsi/helmet.png new file mode 100644 index 0000000000000000000000000000000000000000..968c15541413f0ff77cce6a86f611b1ab1a2d7da GIT binary patch literal 365 zcmV-z0h0cSP)Px$CrLy>R9J=Wl(9~PFc`=GxV76YBo?ZH)L`_yVn`h9Z=&>H`uoV&0+C205{djVNl57K_TFhdqV1ZXd{x#?hzyTU4;S-S zAs;km0stKLM|UafGk(n~0v;l}O6J!BjPW1;!eM`OjPdmah%6Z6AhIB`sLJ|_L{-+# z&--i$K}~hNy>UWhFlIWtfq%ys$8NWU)+1h~=XIK}?V7}x2~y@~tMi*nnZuYV_{0SF z0Fi}K!-oX$UiJK0${b1!iyARlpzWH3$l`Q*1(Agi8Klf1Wey=S%;!R@UnJlqv>w42 zN1AS+)BvRhr0E8XaV)v4JVD{U?>~*dI|W{FJ}!!4iAy9B`KP=AmaCBPWa_v(00000 LNkvXXu0mjf0Ggwc literal 0 HcmV?d00001 diff --git a/Resources/Textures/_Goobstation/Clothing/Head/Modsuits/standard.rsi/meta.json b/Resources/Textures/_Goobstation/Clothing/Head/Modsuits/standard.rsi/meta.json new file mode 100644 index 00000000000..77303d7be47 --- /dev/null +++ b/Resources/Textures/_Goobstation/Clothing/Head/Modsuits/standard.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "From tgstation - https://github.com/tgstation/tgstation/pull/59109", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "helmet" + }, + { + "name": "helmet-sealed" + }, + { + "name": "equipped-HEAD", + "directions": 4 + }, + { + "name": "equipped-HEAD-sealed", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/_Goobstation/Clothing/OuterClothing/Modsuits/standard.rsi/chestplate-sealed.png b/Resources/Textures/_Goobstation/Clothing/OuterClothing/Modsuits/standard.rsi/chestplate-sealed.png new file mode 100644 index 0000000000000000000000000000000000000000..5f374eecd9d17a4417a47eab8bac5775d3f0361b GIT binary patch literal 110 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}8lEnWArY-_ zj~ntb7;rEc99h6*{#ifC;=u#u%>8Z*8zy}HSHi$xv6S&zDRb=yr}iG89tKZWKbLh* G2~7aPs~`yg literal 0 HcmV?d00001 diff --git a/Resources/Textures/_Goobstation/Clothing/OuterClothing/Modsuits/standard.rsi/chestplate.png b/Resources/Textures/_Goobstation/Clothing/OuterClothing/Modsuits/standard.rsi/chestplate.png new file mode 100644 index 0000000000000000000000000000000000000000..404383a236f28053553ea126ba4af3bded915cbe GIT binary patch literal 428 zcmV;d0aN~oP)Px$W=TXrR9J=Wl`)FLFc3w5mhFu}wQ!>%)!abj5^8b-ohUa*<4c6xKvfELst1ft z;H@+`*#s-F-ep0ru^5l_zrJM|Xfzs)KW4YqaJgJ$?7g1>Am@zp`7F;KkH@*|Ism}w zbdvhvaQHa`-usz|aJ^p9bscif->%kLgb>6;je(EVj)izi3D#QVoG}aohzP?lhwAOb?6!GQ#5CU$u+v@zEYYohd5Q2>Rz6StUYoGQ4z%)%V zUZZLcIOil%rIeUh@+dlv)F~+yW-!`C>5)%NRwHEPm?_H`6q9sHGr4(X}2qB0#5p8dPnUPWg zGmB{1e5JmrfwJ;P(Pe#81I!F#%-w4!Nf=|c{|d?>Px&|4BqaRCt{2nz3%9Oc2Naj=tVP+S+nMks{hRK#^$gN{Y)H@CtbVzXE=OTtRu) zB1L%v1}^A_xLBO=G{~Ilrg;AqKc}5nk6dq9{V!w%G6Y z&{|`^-`m00bq%ex^^{vd`2b*yv4iDb(=;%~0D!ant553w{@xD0X&MAUfZc9)<_R3U z{Qg#+AdL^e4pu2;@8Nk61OQ;=zrVj)p92mafc=!Kf(y-sGI=4LeyVEl2gd;bc7|4}|1C~kJ z82L^f0040u+b7RKYpoH-v9s_o#BmH`j5P$0$I5p$008TPOgj25IX7V#p2_F=7@Q1{ zW!a`5Z!#|DtObxulij6xVJJ?i@SgySAHZ)9km-5<0SrlnJs!`$&0jYOx^T0i=>HjS zfVCA^8*hNM6<8Z@fVCA^8*hNM6<8bRq9~EuRdenJ&8>rTDlFf}S%bUOoG>S{A%gO$ zk86X3QwT`GVHiSdZ8s+8=nMEv2dy>2FoZD%JZ8bvRR^aK=xL7J=Ve}kO6a|!0wkt#`HaC=4*EN(;XT1e&6(G*u&wl;fgR-5f>;=DP>)sq-omy`1yVCay-ccfb+fjrFsNzIp0h+z>>=! zku7smaefzgetx!La6BG6HYzqvvq(yu=lO>6wAMHtk5(V4+fbZ5Aj`5%k|dqX2-zF7 zM2Cn(_7AdqtiK`*!Px`2{81pc$d`H#0DeCRZqG|y#(3c9IUopv#WTP@Oo69#r;M%# zES>y(#?&^s$pw+JEEh|HB(gnb3Zp}SgI+TBC5(4jpYV7*9#?(=`ZekUD67eI00000 LNkvXXu0mjf08{`$ literal 0 HcmV?d00001 diff --git a/Resources/Textures/_Goobstation/Clothing/OuterClothing/Modsuits/standard.rsi/meta.json b/Resources/Textures/_Goobstation/Clothing/OuterClothing/Modsuits/standard.rsi/meta.json new file mode 100644 index 00000000000..0f10810f22e --- /dev/null +++ b/Resources/Textures/_Goobstation/Clothing/OuterClothing/Modsuits/standard.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "From tgstation - https://github.com/tgstation/tgstation/pull/59109", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "chestplate" + }, + { + "name": "chestplate-sealed" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-sealed", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/_Goobstation/Clothing/Shoes/Modsuits/standard.rsi/boots-sealed.png b/Resources/Textures/_Goobstation/Clothing/Shoes/Modsuits/standard.rsi/boots-sealed.png new file mode 100644 index 0000000000000000000000000000000000000000..4d141b313fbd02cd754ae2ea28796c2076d018b3 GIT binary patch literal 109 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}>Ygr+ArY-_ z&l&PE7;rEc?0Fz4J=sR)gW$X{M`Z?vX{mebYZ(~ac&BMFX)wuL4*_an@O1TaS?83{ F1OPAe8x#Nl literal 0 HcmV?d00001 diff --git a/Resources/Textures/_Goobstation/Clothing/Shoes/Modsuits/standard.rsi/boots.png b/Resources/Textures/_Goobstation/Clothing/Shoes/Modsuits/standard.rsi/boots.png new file mode 100644 index 0000000000000000000000000000000000000000..7dcd0cf6e5fc7b321a614656a0203c3b218c4525 GIT binary patch literal 307 zcmV-30nGl1P)Px#?MXyIR9J=WlQ9m$FbqXMh^1pBucT8))YJ43zGd&>Eo`(>xIq~pr6LJUVM6## z(YF+T=R1I+D9XRH%N6>*kF>7qCL)UTaU7A;0bm%0Y@b7s%}4X5X<)6LHRl{e1S#En z4A8f}2$*>SFi#{R(R8ml zujO8BfSD&_Or(dcH^$(#9$p|UU{xIf4BHJb^VGI&q=y$s)t0;PG;z*0e`_Fh;aY8( ze`ySxzF`TWab2GP0GG=ZA4LGffE!?kfX$|GrUU>0002ovPDHLk FV1kQxdy@bF literal 0 HcmV?d00001 diff --git a/Resources/Textures/_Goobstation/Clothing/Shoes/Modsuits/standard.rsi/equipped-FEET-sealed.png b/Resources/Textures/_Goobstation/Clothing/Shoes/Modsuits/standard.rsi/equipped-FEET-sealed.png new file mode 100644 index 0000000000000000000000000000000000000000..5ad67b6efa164a2c6cbab40d56f6044e941b9b09 GIT binary patch literal 131 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|Y&~5ZLn`LH zy}6K)L4k*P!{0;wdk&sVF#M6LvTW1WUZ4sPc#wN$>-#@zmdKI;Vst0C9&Y=Kufz literal 0 HcmV?d00001 diff --git a/Resources/Textures/_Goobstation/Clothing/Shoes/Modsuits/standard.rsi/equipped-FEET.png b/Resources/Textures/_Goobstation/Clothing/Shoes/Modsuits/standard.rsi/equipped-FEET.png new file mode 100644 index 0000000000000000000000000000000000000000..a95a4bb37de9cc09867b5eda3d05351c72396dae GIT binary patch literal 501 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D%z*L%7+hE&XX zduwCvVFLly3-1+ry_l}wnwz!mfvN>d_y?7Hj@}ilSKqvu#^62GM=+p3$Wl<$wO!@^ zR59OqcABd{FuEXtFZOSq@$8DxV_hvoZr?OXczqeb?e;dC^WarKzdces=P%06KQf)*W%{ITU;5Z_VvK{IFr)`|Rbbm6R;z`dKb*k$Aj= zQHd$}%p%^3S=JY-=QA#NU)>^b!K9kO;qn_nCyD%hJ<-uKx8M2m_m`pDk||GAf5Yzz`@(zmv;GDsvRtc|s8*;E(l>~e2SydOiutFP(yOy7@Jzhdd& z2=aRDw`T1c`&y>F$))E4zl#0veYTG^?LXU}P#%Zf47ubI4|WSzyf1z!cwXuk$b+dWLVEXzw?W>&P%_@5~h3KfezSa!@5+H>Bo)r0oj2J!M2RitpR!e0{1(u mb?rY8X3IFg^2+*v*R1{}#~xpLa5MxMj|`r!elF{r5}E)OUC@;P literal 0 HcmV?d00001 diff --git a/Resources/Textures/_Goobstation/Clothing/Shoes/Modsuits/standard.rsi/meta.json b/Resources/Textures/_Goobstation/Clothing/Shoes/Modsuits/standard.rsi/meta.json new file mode 100644 index 00000000000..136eede0ec6 --- /dev/null +++ b/Resources/Textures/_Goobstation/Clothing/Shoes/Modsuits/standard.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "From tgstation - https://github.com/tgstation/tgstation/pull/59109", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "boots" + }, + { + "name": "boots-sealed" + }, + { + "name": "equipped-FEET", + "directions": 4 + }, + { + "name": "equipped-FEET-sealed", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/_Goobstation/Interface/Alerts/modpower.rsi/meta.json b/Resources/Textures/_Goobstation/Interface/Alerts/modpower.rsi/meta.json new file mode 100644 index 00000000000..2687b6079d0 --- /dev/null +++ b/Resources/Textures/_Goobstation/Interface/Alerts/modpower.rsi/meta.json @@ -0,0 +1,29 @@ +{ + "version": 1, + "license": "CC0-1.0", + "copyright": "Taken from TG", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "modpower0" + }, + { + "name": "modpower1" + }, + { + "name": "modpower2" + }, + { + "name": "modpower3" + }, + { + "name": "modpower4" + }, + { + "name": "modpower5" + } + ] +} diff --git a/Resources/Textures/_Goobstation/Interface/Alerts/modpower.rsi/modpower0.png b/Resources/Textures/_Goobstation/Interface/Alerts/modpower.rsi/modpower0.png new file mode 100644 index 0000000000000000000000000000000000000000..2e738ca7e76b61f8a097ab56626d7de480058363 GIT binary patch literal 324 zcmV-K0lWT*P)Px#{z*hZR9J=WlrajzKoCX0VwWQI=bXXR#}WjH;9sE9xUt(vpau3>;Qrw2tvwao)bY3fSJ`ln)$x3`+1%R0LVQT zF|%?U2LRA)bd8tx`Ynt{lSBfTSv`t~g@{yAh)5-^pH1hwo`|%=FAe~J>$+Gj)*prD z3$EUuj%VZRi=_epU_6?j*Kg^3)_Bs;JdDF-qEanK;#dbv?01J)F^~CoTyED${FMVH zQJ+{XH@_EGC5TAbwhha&P_MOrp-(dycJwR==RoR%NDu@;NS!;6 W%$5bo=6$FD0000Px$X-PyuR9J=Wl(9?0P!PtyNSqux71~Tr{sEZ;L4un&Sa7i-E_P6GEjZW?#-Wg@ zIAqCU2Nk5!%^^@iC#TG!i#X^|aVXe93+JF06Oz1CDlWbsJo4_#y}NrCa)3l4k^FZQ z-guq|0MH$aSo14YMRzRnD^(?abTSOX005|AQFknw>-yh9w@i`egq)wA#V!&Qz{wxz z2>{Tlcd&I>LaW{p27TQxVWuJWVb(XwlvD%&V6eGD1+7TTTM?8+GY4wsvi*pN9J@mW ztr)CW2bg#5v%u*`!rjet>TRy(^TA%^$1EBPfC(JcY#f-ofj%WVG`o~f)WgZy=9R_v z&T7PtbzmHpultF`g!MG~gO`Xc>;RM2iL|0bT%346FW|l_4n9tD*X;oS%*I|sk9`7r z`1RuW5~CrYx;>0)Yf#O0Vqn#52f@dIe?uNbkx3XEM88aU1|&{Gzfx6h@i(JbB9TZW Z%m*Bm%}GBLSMUG;002ovPDHLkV1ienxjFy< literal 0 HcmV?d00001 diff --git a/Resources/Textures/_Goobstation/Interface/Alerts/modpower.rsi/modpower2.png b/Resources/Textures/_Goobstation/Interface/Alerts/modpower.rsi/modpower2.png new file mode 100644 index 0000000000000000000000000000000000000000..21d8e7f6e7a94410889b1bafb3260fd7ae4e79ac GIT binary patch literal 415 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^TwbxD{AO%m;PUTyiu-noOb8W+xNT)4E%%GtMAEoM&V z0>7L##Yn!#hYb`b%z4;<=r{*s(t(A0r*E)giPx$W=TXrR9J=Wl)p;DP!xxMNqm7$Ma2$XTta3+41qp?Tl}*MQo$z>og&3IPzoY0 zYSYQpAcW}Ft)xS`Iwe61?nxZ%O?ymgQ>&Bro9_8>&%IyZ<^UWHhx6YN*uu7L0D!LO z$*hN%T~lCv<7-yu^=O)w70_4oMBI>Oy}vMbH@E41OkjZpx}qndEFpf1NK@mXiKDH3 znzcrmC`*Xlh-8gcGgn}HcPAN_AOZ}(>XTZi3P?&xZnn6*^x0GL>ArgQ^2>|+RDvCo zD^MPkNgb@cX0m=IOfP3W{Ysebd-i)70aaTE4B+(S9LL5%nu}T2>8Le@)S@gwttk-Y z>F;N+zDaw3BH%oB5xWs$m%f*;bspjS+x!BPq>9kKwtrus$d_A|0{}424oRMQgh3}D zb=$KMtkTT#oKbcKphGjub0&(g-!Oj=4QIh$@$Dc2hGW#7V-ybn^QG7*=5RP14&wuU WO_eV;7`v4K0000Px$KuJVFR9J=Wlp#;VKoEsr3ApxhnQkotK%++v)BAfj}Ve--+x;mSq3{HAuLxX4y24 ze#c5FtP5d~FzL9j{!pf3--I(@o7brkMU#y;9DZ-LYLsf!sac#?&SKIrl&KhKP6H^I ziHLo0Q?zPCF*D=1aQQ^UD44|rEYWQ#;@7>+xmp~@qNXj+X}bUna5cWh+4+gf zS@R8aAsG!tOsPgP8iG>2wD#a=U&M_GbRmCw!S?gJYi{xH&pbZ9K1KYe3ACB&!u{RD z>J4-`xqS&2m#0E*{sMWAkVlU|xznoE+5qM9JZIKa0A|ln!P|s=FQVF1;{7)^VK&Vp kyD0jgxqJeFKp+780N4f0Jjt65JOBUy07*qoM6N<$f+zB-0{{R3 literal 0 HcmV?d00001 diff --git a/Resources/Textures/_Goobstation/Interface/Alerts/modpower.rsi/modpower5.png b/Resources/Textures/_Goobstation/Interface/Alerts/modpower.rsi/modpower5.png new file mode 100644 index 0000000000000000000000000000000000000000..d6b47c7577c5c06e5f9d5cacd669a236171cf038 GIT binary patch literal 390 zcmV;10eSw3P)Px$KuJVFR9J=Wlp#;VKoEsr3ApxhnQkotK%++v)BAfj}Ve--+x;mSq3{HAuLxX4y24 ze#c5FtP5d~FzL9j{!pf3--I(@o7brkMU#y;9DZ-LYLsf!sac#?&SKIrl&KhKP6H^I ziHLo0Q?zPCF*D=1aQQ^UD44|rEYWQ#;@7>+xmp~@qNXj+X}bUna5cWh+4+gf zS@R8aAsG!tOsPgP8iG>2wD#a=U&M_GbRmCw!S?gJYi{xH&pbZ9K1KYe3ACB&!u{RD z>J4-`xqS&2m#0E*{sMWAkVlU|xznoE+5qM9JZIKa0A|ln!P|s=FQVF1;{7)^VK&Vp kyD0jgxqJeFKp+780N4f0Jjt65JOBUy07*qoM6N<$f+zB-0{{R3 literal 0 HcmV?d00001 diff --git a/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/boots.png b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/boots.png new file mode 100644 index 0000000000000000000000000000000000000000..0b0ada9e583e1c5727e1a4bbc228edfa55079164 GIT binary patch literal 271 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfv=>VS)*8>L*gv%+oNGr<88HI!- z^72UP8VBpSW}BEeRkvt20F^P81o;IsI6S+N2IMUAba4#Pn0WTWMy^8!0&EXL3b#kb zwCeRel00!wI#1-#%^tsf4NaR>8f~`u`byo2Vfp{oZj!xw%a7G2L0fKHIdw&ZSSTf& zuld_zJ?k%z+=}~^)mjOry9+KKUfRIOc6H^ib1XSo?8_f4E8`Gu?mO@PGGj^B?vH2g z7~cKhu{3$6Sh$syb;^e&hCLGJ@*^rYNlcKqCb`8pa3XW{i36&iy6686jN_Q8El^>~ RF938cgQu&X%Q~loCIFwcW-b5# literal 0 HcmV?d00001 diff --git a/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/chestplate.png b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/chestplate.png new file mode 100644 index 0000000000000000000000000000000000000000..a499fcf230f4e79636a04eda31ba7bc48d515f37 GIT binary patch literal 367 zcmV-#0g(QQP)D@@@bC00DGTPE!Ct z=GbNc008qzL_t(2k-d-2YQj(ug(nrRkX3?xfJmU=?rP}NE{l|IUAYzquGg%jl;!~n zA$C`UaJv(PQ1T3Yg}zfmU?#e7>)D-;bLI!&it-{Do`LxJeiohh8+={8Pfh@lEr^!l zl|wW*Ed^}wxcmOVM~-0@Xeu3 zV9GOUXcL1m-w{FFBm(c2!5k)tV-5Z8cCwC)*vM05bBDa*FV>Z#M3%zO=L*@bXB?$r*)&B$}8w zZ4kQk6ez`5666=m;PC858j!QV)5S4FW8&Ef2L%r+@US#;IRrHDuVieIX4w6I7Sj>o z-*ZnY|1x;qv8JP^?dt&5* z>^s|+o~o!$xfp)M!as2bP0l+XkK Dq()$& literal 0 HcmV?d00001 diff --git a/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/helmet.png b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/helmet.png new file mode 100644 index 0000000000000000000000000000000000000000..5385dcb9fa93a494439b28f835df0128972e3a71 GIT binary patch literal 362 zcmV-w0hRuVP)1<9doH)z-c5{n=mli<=Qy|mKlQtV(nr@**ZYq07bklPI8SJB(|Qc zlkcPjFCthb_mY7uA2X2?Y_s0>lYdx{g4-p zhg%;YI&a4w*EVgaM3H!I0sj-_qz>&Q+8koqb#6Wc{?`A*FXm4$17+}ln*aa+07*qo IM6N<$f_h_*djJ3c literal 0 HcmV?d00001 diff --git a/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/meta.json b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/meta.json new file mode 100644 index 00000000000..1d59db7f795 --- /dev/null +++ b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/meta.json @@ -0,0 +1,56 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "From tgstation - https://github.com/tgstation/tgstation/pull/59109", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "chestplate" + }, + { + "name": "gauntlets" + }, + { + "name": "helmet" + }, + { + "name": "standard-plating" + }, + { + "name": "boots" + }, + { + "name": "shell" + }, + { + "name": "mod-core-standard", + "delays": [[0.1, 0.1, 0.1, 0.1]] + }, + { + "name": "shell-core", + "delays": [[0.1, 0.1, 0.1, 0.1]] + }, + { + "name": "shell-core-secured", + "delays": [[0.1, 0.1, 0.1, 0.1]] + }, + { + "name": "shell-helmet" + }, + { + "name": "shell-chestplate" + }, + { + "name": "shell-gauntlets" + }, + { + "name": "shell-boots" + }, + { + "name": "shell-secured" + } + ] +} diff --git a/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/mod-core-standard.png b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/mod-core-standard.png new file mode 100644 index 0000000000000000000000000000000000000000..437079c540f18dbb5d813795c530cc7bcca34012 GIT binary patch literal 922 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 M07*qoM6N<$f)!%RS^xk5 literal 0 HcmV?d00001 diff --git a/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell-helmet.png b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell-helmet.png new file mode 100644 index 0000000000000000000000000000000000000000..9417be1ef6692fbde307e060d55d6e1ce6f45a45 GIT binary patch literal 462 zcmV;<0WtoGP)Px$h)G02R9J=WRk3QrFciEr9Xce`azP*pin}z)WbmN=Kq3C*6uK8Y1TrP*QpZpX z9_&D}t4rm`PD&)lOTo8DPqyBj&+kbiPc;PzcN1bC0-w&tlcnnfb&2C0C7((CoaPWfM*ooJdjDUFm}So0nP)d z>{eqOZ;@p?#PPOV|LhY`>Xc$z5ulR;K$h)5se>%rK?uJRE7*uI`URdhs>GZ13ggKX zQM3f5P8sX(>VmEkP~<71=oqV_+pJfZg(2SNbBrfbc-{z6v@D5>JT($vR4nygS1qX9 zUnkrjQls1#9Z)@APx$vPnciR9J=WRy}WnFcdthj)?>emb?&@p-CpB3`m(e!e8n?BV)&Uh{V)(h?=rk z@?fZvp-V+4JBC)O0kx4TZh11;?~eCnfDt2l;`Z*oHf>)xeGg72R1+UhytkjLzT`=o zVzFEyNz;ayUlpK;JfAk6t92hC)}af6b;CI!E`+GGHBl5hYrj?z2!eG@h^vV=kPxCm z97hnM!UhuN`?lCY0!(rM5Te4K%o}_5WZsBE7ZKMso5FC`SU*2KVzFF-u^fcBdTs7) zr=_=mQgSeMUdM5yMI>p8ZI)rNTpG*LD98cUSik+i!t%i>h;`vk$ zrBZSTQK2Yyj%_Vi-vuhCjO98Vs^{bQ9QH0_80c{wPbaiiId9t`*K%hO-`PLKbVBPO uSl1UJH$@Efaq(ZWK^P-&(E=RtZ}171pWmLpNLz9M0000Px$X-PyuR9J=WmBCBHFcikWs)&aT+X|5)r17LX6?%wx_s{kpFbAPt+Mb*PDHPUC zx`PTM9wfCZV{OvyAoO=iLU`ZD%X=@tz`%cHJn|%^p&XAq*(c74skq~MAjDQj^6vZ$ zkMFORZmc5{14=wiVi01(aXnldOFUd%YiC2tF{R;7fqN8~uPv=ibbbO#Ls?5X0Dv(c z)9KsaL0)tXSE2O;jQL2DxLOMlfiWKd@a;^r#kCA9iVQ+T)n15bokd>?v<;oQ|#6ZC~?jM zIIdUK0DwvyKqap3bsbPzgNXA0Lj2Z46D6)otATQrS&r+;db^dY%u;Qc=S$3H&s*c(m+z!Xa6$k8002ovPDHLkV1m!+z6$^V literal 0 HcmV?d00001 diff --git a/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/standard-plating.png b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/standard-plating.png new file mode 100644 index 0000000000000000000000000000000000000000..ff521088cbfeff1344587dcf23cd0d111b10ccfe GIT binary patch literal 212 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfv=>VS)*8>L*NQg@+E2-Jp+J^)O zs8}T`m-XB1SgNjWv1htkAy64(NswPKgTu2MX+Tc8r;B5V#>BJdFY-1h@UR59ZOA)d z*f@okr&IUw#GS7p8ajhh3VqkR^Jy%`gr{8cKr$RdXfB`LjOwE1FdB6boFyt I=akR{02SFx%>V!Z literal 0 HcmV?d00001