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 00000000000..d3422bc8f4d Binary files /dev/null and b/Resources/Audio/Machines/scanbuzz.ogg differ 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 00000000000..92944055732 Binary files /dev/null and b/Resources/Audio/_Goobstation/Machines/computer_end.ogg differ diff --git a/Resources/Audio/_Goobstation/Mecha/attributions.yml b/Resources/Audio/_Goobstation/Mecha/attributions.yml new file mode 100644 index 00000000000..0caf2522fc7 --- /dev/null +++ b/Resources/Audio/_Goobstation/Mecha/attributions.yml @@ -0,0 +1,4 @@ +- files: ["nominal.ogg"] + license: "CC-BY-NC-SA-3.0" + copyright: "Taken from TG station." + source: "https://github.com/tgstation/tgstation/commit/3517810d119fcb5eeb9d477c87cda3eb3cd2048c" diff --git a/Resources/Audio/_Goobstation/Mecha/nominal.ogg b/Resources/Audio/_Goobstation/Mecha/nominal.ogg new file mode 100644 index 00000000000..b89bd39616a Binary files /dev/null and b/Resources/Audio/_Goobstation/Mecha/nominal.ogg differ diff --git a/Resources/Locale/en-US/_Goobstation/alert-levels/alerts.ftl b/Resources/Locale/en-US/_Goobstation/alert-levels/alerts.ftl new file mode 100644 index 00000000000..9c1b0c121fc --- /dev/null +++ b/Resources/Locale/en-US/_Goobstation/alert-levels/alerts.ftl @@ -0,0 +1,2 @@ +alerts-modsuit-power-name = [color=yellow]Modsuit Power[/color] +alerts-modsuit-power-desc = Displays the current power level of your modsuit. Low power may affect suit functionality. diff --git a/Resources/Locale/en-US/_Goobstation/clothing/sealable-clothing-component.ftl b/Resources/Locale/en-US/_Goobstation/clothing/sealable-clothing-component.ftl new file mode 100644 index 00000000000..e9c7ffe84b7 --- /dev/null +++ b/Resources/Locale/en-US/_Goobstation/clothing/sealable-clothing-component.ftl @@ -0,0 +1,23 @@ +sealable-clothing-equipment-not-toggled = Deploy all parts first! +sealable-clothing-equipment-seal-failed = Sealing failed! +sealable-clothing-seal-verb = Toggle Seals + +sealable-clothing-seal-up = The {$partName} is sealing +sealable-clothing-seal-up-helmet = The {$partName} hisses as it closes. +sealable-clothing-seal-up-gauntlets = The {$partName} tightens around your fingers and wrists. +sealable-clothing-seal-up-chestplate = The {$partName} clenches tightly around your chest. +sealable-clothing-seal-up-boots = The {$partName} seals around your feet. + +sealable-clothing-seal-down = The {$partName} is unsealing +sealable-clothing-seal-down-helmet = The {$partName} hisses open. +sealable-clothing-seal-down-gauntlets = The {$partName} become loose around your fingers. +sealable-clothing-seal-down-chestplate = The {$partName} releases your chest. +sealable-clothing-seal-down-boots= The {$partName} relaxes its grip on your legs. + +sealable-clothing-sealed-process-toggle-fail = Suit is already shutting down! +sealable-clothing-unsealed-process-toggle-fail = Suit is already starting up! +sealable-clothing-sealed-toggle-fail = Deactivate the suit first! + +sealable-clothing-not-powered = Suit is not powered! +sealable-clothing-open-sealed-panel-fail = Wiring panel is too tightly sealed! +sealable-clothing-close-panel-first = Close the wiring panel first! diff --git a/Resources/Locale/en-US/_Goobstation/forensics/fibers.ftl b/Resources/Locale/en-US/_Goobstation/forensics/fibers.ftl new file mode 100644 index 00000000000..e3cc656b7cd --- /dev/null +++ b/Resources/Locale/en-US/_Goobstation/forensics/fibers.ftl @@ -0,0 +1 @@ +fibers-modular = modular diff --git a/Resources/Locale/en-US/_Goobstation/lathe/lathe-categories.ftl b/Resources/Locale/en-US/_Goobstation/lathe/lathe-categories.ftl index c72f9b46fed..8d7f0f63b34 100644 --- a/Resources/Locale/en-US/_Goobstation/lathe/lathe-categories.ftl +++ b/Resources/Locale/en-US/_Goobstation/lathe/lathe-categories.ftl @@ -8,3 +8,4 @@ lathe-category-mechs-gygax = Gygax lathe-category-mechs-durand = Durand lathe-category-mechs-equipment = Mech equipment lathe-category-mechs-weapons = Mech weapons +lathe-category-modsuit = MOD Suits diff --git a/Resources/Locale/en-US/_Goobstation/research/technologies.ftl b/Resources/Locale/en-US/_Goobstation/research/technologies.ftl index 31a8640b241..ba68b2494c8 100644 --- a/Resources/Locale/en-US/_Goobstation/research/technologies.ftl +++ b/Resources/Locale/en-US/_Goobstation/research/technologies.ftl @@ -6,3 +6,4 @@ research-technology-gygax = Gygax research-technology-durand = Durand research-technology-explosive-mech-ammunition = Explosive Mech Ammunition research-technology-honk-weapons = Bananium Weapons +research-technology-modsuits = Modular Technologies diff --git a/Resources/Locale/en-US/clothing/components/toggleable-clothing-component.ftl b/Resources/Locale/en-US/clothing/components/toggleable-clothing-component.ftl index 746eea4a28f..a7d08623f42 100644 --- a/Resources/Locale/en-US/clothing/components/toggleable-clothing-component.ftl +++ b/Resources/Locale/en-US/clothing/components/toggleable-clothing-component.ftl @@ -1,3 +1,6 @@ toggle-clothing-verb-text = Toggle {CAPITALIZE($entity)} toggleable-clothing-remove-first = You have to unequip {$entity} first. +toggleable-clothing-remove-all-attached-first = You have to unequip all toggled clothing first. +toggleable-clothing-attach-tooltip = Equip +toggleable-clothing-unattach-tooltip = Unequip diff --git a/Resources/Maps/glacier.yml b/Resources/Maps/glacier.yml index 437e8ede164..b26763d30bd 100644 --- a/Resources/Maps/glacier.yml +++ b/Resources/Maps/glacier.yml @@ -46151,7 +46151,7 @@ entities: occludes: True ents: - 6158 - toggleable-clothing: !type:ContainerSlot + toggleable-clothing: !type:Container showEnts: False occludes: True ent: null diff --git a/Resources/Prototypes/Alerts/alerts.yml b/Resources/Prototypes/Alerts/alerts.yml index f391a3cf221..c5703820dab 100644 --- a/Resources/Prototypes/Alerts/alerts.yml +++ b/Resources/Prototypes/Alerts/alerts.yml @@ -8,6 +8,7 @@ - category: Mood - category: Stamina - alertType: SuitPower + - alertType: ModsuitPower # Goobstation - Modsuits - category: Internals - alertType: Fire - alertType: Handcuffed @@ -617,4 +618,4 @@ - sprite: /Textures/Interface/Alerts/deflecting.rsi state: deflecting0 name: alerts-deflecting-name - description: alerts-deflecting-desc \ No newline at end of file + description: alerts-deflecting-desc diff --git a/Resources/Prototypes/Entities/Clothing/Neck/cloaks.yml b/Resources/Prototypes/Entities/Clothing/Neck/cloaks.yml index f02dc068da9..84b3df45900 100644 --- a/Resources/Prototypes/Entities/Clothing/Neck/cloaks.yml +++ b/Resources/Prototypes/Entities/Clothing/Neck/cloaks.yml @@ -8,7 +8,7 @@ sprite: Clothing/Neck/Cloaks/centcomcloakformal.rsi - type: StealTarget stealGroup: HeadCloak # leaving this here because I suppose it might be interesting? - + - type: entity parent: ClothingNeckBase id: ClothingNeckCloakCap @@ -159,7 +159,7 @@ slot: head - type: ContainerContainer containers: - toggleable-clothing: !type:ContainerSlot {} + toggleable-clothing: !type:Container {} - type: entity parent: ClothingNeckBase @@ -185,7 +185,7 @@ slot: head - type: ContainerContainer containers: - toggleable-clothing: !type:ContainerSlot {} + toggleable-clothing: !type:Container {} - type: TypingIndicatorClothing proto: moth @@ -287,4 +287,4 @@ slot: head - type: ContainerContainer containers: - toggleable-clothing: !type:ContainerSlot {} \ No newline at end of file + toggleable-clothing: !type:Container {} diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/base_clothingouter.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/base_clothingouter.yml index 1d938911a1a..1b350a97674 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/base_clothingouter.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/base_clothingouter.yml @@ -120,7 +120,7 @@ slot: head - type: ContainerContainer containers: - toggleable-clothing: !type:ContainerSlot {} + toggleable-clothing: !type:Container {} storagebase: !type:Container ents: [] @@ -155,7 +155,7 @@ slot: head - type: ContainerContainer containers: - toggleable-clothing: !type:ContainerSlot {} + toggleable-clothing: !type:Container {} - type: GroupExamine - type: Tag tags: @@ -242,7 +242,7 @@ slot: head - type: ContainerContainer containers: - toggleable-clothing: !type:ContainerSlot {} + toggleable-clothing: !type:Container {} storagebase: !type:Container ents: [] diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/softsuits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/softsuits.yml index 66718d519bc..bc3a8c84ff6 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/softsuits.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/softsuits.yml @@ -58,7 +58,7 @@ slot: head - type: ContainerContainer containers: - toggleable-clothing: !type:ContainerSlot {} + toggleable-clothing: !type:Container {} #Prisoner EVA - type: entity diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml index 96caf947172..9bbd9008a25 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml @@ -144,7 +144,7 @@ slot: head - type: ContainerContainer containers: - toggleable-clothing: !type:ContainerSlot {} + toggleable-clothing: !type:Container {} - type: ClothingRequiredStepTriggerImmune slots: WITHOUT_POCKET - type: Tag @@ -251,7 +251,7 @@ sprite: Clothing/OuterClothing/Suits/monkey.rsi - type: ContainerContainer containers: - toggleable-clothing: !type:ContainerSlot {} + toggleable-clothing: !type:Container {} - type: ClothingRequiredStepTriggerImmune slots: WITHOUT_POCKET - type: Tag @@ -274,7 +274,7 @@ clothingPrototype: ClothingHeadHatHoodIan - type: ContainerContainer containers: - toggleable-clothing: !type:ContainerSlot {} + toggleable-clothing: !type:Container {} - type: Construction graph: ClothingOuterSuitIan node: suit @@ -297,7 +297,7 @@ clothingPrototype: ClothingHeadHatHoodCarp - type: ContainerContainer containers: - toggleable-clothing: !type:ContainerSlot {} + toggleable-clothing: !type:Container {} - type: entity parent: ClothingOuterSuitCarp @@ -321,4 +321,4 @@ - type: Sprite sprite: Clothing/OuterClothing/Suits/witchrobe.rsi - type: Clothing - sprite: Clothing/OuterClothing/Suits/witchrobe.rsi \ No newline at end of file + sprite: Clothing/OuterClothing/Suits/witchrobe.rsi diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml index c9ba05fcff7..e5e38a26737 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml @@ -45,7 +45,7 @@ slot: head - type: ContainerContainer containers: - toggleable-clothing: !type:ContainerSlot {} + toggleable-clothing: !type:Container {} storagebase: !type:Container ents: [] diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index 5818f182be1..566f60ea0e9 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -686,6 +686,8 @@ - TorsoBorgService - MechAirTank # Goobstation - MechThruster # Goobstation + - PowerCageMedium # Goobstation - Powercell to exosuit fab + - PowerCageSmall # Goobstation - Powercell to exosuit fab dynamicRecipes: - ProximitySensor - BorgModuleLightReplacer @@ -761,6 +763,14 @@ - MechEquipmentKineticAccelerator - MechEquipmentHonkerBananaMortar - MechEquipmentHonkerMousetrapMortar + # Goobstation - Modsuits + - ModsuitChestplate + - ModsuitBoots + - ModsuitHelmet + - ModsuitGauntlets + - ModsuitShell + - ModsuitPlatingExternal + - PowerCageHigh # Goobstation - Powercell to exosuit fab - type: EmagLatheRecipes emagDynamicRecipes: - WeaponMechCombatImmolationGun diff --git a/Resources/Prototypes/Entities/Structures/Power/chargers.yml b/Resources/Prototypes/Entities/Structures/Power/chargers.yml index ae8689bead4..3ffe6d0ac41 100644 --- a/Resources/Prototypes/Entities/Structures/Power/chargers.yml +++ b/Resources/Prototypes/Entities/Structures/Power/chargers.yml @@ -246,6 +246,7 @@ components: - BorgChassis - Silicon # Parkstation IPCs + - Inventory # Goobstation - Modsuits - type: Construction containers: - machine_parts diff --git a/Resources/Prototypes/WhiteDream/Entities/Clothing/Cult/armor.yml b/Resources/Prototypes/WhiteDream/Entities/Clothing/Cult/armor.yml index 88b578cb5ff..fdaca46eee0 100644 --- a/Resources/Prototypes/WhiteDream/Entities/Clothing/Cult/armor.yml +++ b/Resources/Prototypes/WhiteDream/Entities/Clothing/Cult/armor.yml @@ -74,7 +74,7 @@ clothingPrototype: ClothingHeadHatHoodCultHoodTrue - type: ContainerContainer containers: - toggleable-clothing: !type:ContainerSlot { } + toggleable-clothing: !type:Container { } - type: entity parent: ClothingHeadHatHoodCulthood diff --git a/Resources/Prototypes/_Goobstation/Actions/clothing.yml b/Resources/Prototypes/_Goobstation/Actions/clothing.yml new file mode 100644 index 00000000000..d82ab2718f0 --- /dev/null +++ b/Resources/Prototypes/_Goobstation/Actions/clothing.yml @@ -0,0 +1,20 @@ +- type: entity + id: ActionClothingSeal + name: Seal/Unseal Clothing + description: Seals or unseals your current clothing. + categories: [ HideSpawnMenu ] + components: + - type: ConfirmableAction + confirmDelay: 0 + primeTime: 2 + - type: InstantAction + checkCanInteract: true + checkConsciousness: true + itemIconStyle: NoItem + icon: + sprite: _Goobstation/Actions/modsuit.rsi + state: activate + iconOn: + sprite: _Goobstation/Actions/modsuit.rsi + state: activate-ready + event: !type:SealClothingEvent {} diff --git a/Resources/Prototypes/_Goobstation/Alerts/alerts.yml b/Resources/Prototypes/_Goobstation/Alerts/alerts.yml new file mode 100644 index 00000000000..efacd4a096b --- /dev/null +++ b/Resources/Prototypes/_Goobstation/Alerts/alerts.yml @@ -0,0 +1,19 @@ +- type: alert + id: ModsuitPower + icons: + - sprite: /Textures/_Goobstation/Interface/Alerts/modpower.rsi + state: modpower0 + - sprite: /Textures/_Goobstation/Interface/Alerts/modpower.rsi + state: modpower1 + - sprite: /Textures/_Goobstation/Interface/Alerts/modpower.rsi + state: modpower2 + - sprite: /Textures/_Goobstation/Interface/Alerts/modpower.rsi + state: modpower3 + - sprite: /Textures/_Goobstation/Interface/Alerts/modpower.rsi + state: modpower4 + - sprite: /Textures/_Goobstation/Interface/Alerts/modpower.rsi + state: modpower5 + name: alerts-modsuit-power-name + description: alerts-modsuit-power-desc + minSeverity: 0 + maxSeverity: 5 diff --git a/Resources/Prototypes/_Goobstation/Catalog/Cargo/cargo_science.yml b/Resources/Prototypes/_Goobstation/Catalog/Cargo/cargo_science.yml new file mode 100644 index 00000000000..56d703b1ab9 --- /dev/null +++ b/Resources/Prototypes/_Goobstation/Catalog/Cargo/cargo_science.yml @@ -0,0 +1,9 @@ +- type: cargoProduct + id: ScienceModsuitCores + icon: + sprite: _Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi + state: mod-core-standard + product: CrateScienceModsuitCoresFilled + cost: 3000 + category: cargoproduct-category-name-science + group: market diff --git a/Resources/Prototypes/_Goobstation/Catalog/Fills/Crates/science.yml b/Resources/Prototypes/_Goobstation/Catalog/Fills/Crates/science.yml new file mode 100644 index 00000000000..f6d85c39b65 --- /dev/null +++ b/Resources/Prototypes/_Goobstation/Catalog/Fills/Crates/science.yml @@ -0,0 +1,10 @@ +- type: entity + id: CrateScienceModsuitCoresFilled + parent: CrateScienceSecure + name: MOD cores crate + description: Contains three MOD cores inside. + components: + - type: StorageFill + contents: + - id: ModsuitCoreStandard + amount: 3 diff --git a/Resources/Prototypes/_Goobstation/Entities/Clothing/Back/modsuit.yml b/Resources/Prototypes/_Goobstation/Entities/Clothing/Back/modsuit.yml new file mode 100644 index 00000000000..7d0b6258399 --- /dev/null +++ b/Resources/Prototypes/_Goobstation/Entities/Clothing/Back/modsuit.yml @@ -0,0 +1,92 @@ +- type: entity + parent: [Clothing, ContentsExplosionResistanceBase] + id: ClothingModsuitStandard + name: standard modsuit control + description: A special modular suit contol containing all modular suit parts. + components: + - type: Appearance + - type: Sprite + sprite: _Goobstation/Clothing/Back/Modsuits/standard.rsi + layers: + - state: control + - state: control-sealed + visible: false + shader: unshaded + map: [ "sealed" ] + - type: Item + size: Huge + - type: Clothing + quickEquip: false + slots: + - back + - type: Storage + grid: + - 0,0,6,3 + maxItemSize: Huge + - type: ContainerContainer + containers: + storagebase: !type:Container + ents: [] + toggleable-clothing: !type:Container + cell_slot: !type:ContainerSlot + - type: UserInterface + interfaces: + enum.StorageUiKey.Key: + type: StorageBoundUserInterface + enum.ToggleClothingUiKey.Key: + type: ToggleableClothingBoundUserInterface + - type: UseDelay + delay: 0.5 + - type: ExplosionResistance + damageCoefficient: 0.9 + - type: ToggleableClothing + requiredSlot: back + blockUnequipWhenAttached: true + replaceCurrentClothing: true + clothingPrototypes: + head: ClothingModsuitHelmetStandard + gloves: ClothingModsuitGauntletsStandard + outerClothing: ClothingModsuitChestplateStandard + shoes: ClothingModsuitBootsStandard + - type: WiresPanel + - type: ItemSlots + slots: + cell_slot: + name: power-cell-slot-component-slot-name-default + whitelist: + components: + - PowerCell + - type: ItemSlotsRequirePanel + slots: + cell_slot: true + - type: PowerCellDraw + drawRate: 1 # Sealed draw rate + useRate: 5 # Draw rate for sealing + - type: PowerCellSlot + cellSlotId: cell_slot + fitsInCharger: false + - type: DoAfter + - type: SealableClothingControl + - type: SealableClothingRequiresPower + - type: SealableClothingVisuals + visualLayers: + back: + - state: equipped-BACKPACK-sealed + shader: unshaded + - type: Construction + graph: Modsuit + node: standard + +- type: entity + parent: ClothingModsuitStandard + id: ClothingModsuitStandardPowerCell + suffix: High-Capacity Battery + components: + - type: ItemSlots + slots: + cell_slot: + name: power-cell-slot-component-slot-name-default + startingItem: PowerCellHigh + whitelist: + components: + - PowerCell diff --git a/Resources/Prototypes/_Goobstation/Entities/Clothing/Hands/modsuit.yml b/Resources/Prototypes/_Goobstation/Entities/Clothing/Hands/modsuit.yml new file mode 100644 index 00000000000..a6b5eb3b0e1 --- /dev/null +++ b/Resources/Prototypes/_Goobstation/Entities/Clothing/Hands/modsuit.yml @@ -0,0 +1,32 @@ +- type: entity + parent: ClothingHandsBase + id: ClothingModsuitGauntletsStandard + name: standard modsuit gauntlets + description: A special modular suit gloves that protect wearer from electric shock. + categories: [ HideSpawnMenu ] + components: + - type: Appearance + - type: Sprite + sprite: _Goobstation/Clothing/Hands/Modsuits/standard.rsi + layers: + - state: gauntlets + - state: gauntlets-sealed + visible: false + map: [ "sealed" ] + - type: Clothing + equipSound: /Audio/Mecha/mechmove03.ogg + unequipSound: /Audio/Mecha/mechmove03.ogg + slots: [ gloves ] + - type: Insulated + - type: Fiber + fiberMaterial: fibers-modular + fiberColor: fibers-black + - type: FingerprintMask + - type: SealableClothing + sealUpPopup: sealable-clothing-seal-up-gauntlets + sealDownPopup: sealable-clothing-seal-down-gauntlets + - type: SealableClothingVisuals + visualLayers: + gloves: + - state: equipped-HAND-sealed + shader: unshaded diff --git a/Resources/Prototypes/_Goobstation/Entities/Clothing/Head/modsuit.yml b/Resources/Prototypes/_Goobstation/Entities/Clothing/Head/modsuit.yml new file mode 100644 index 00000000000..09be4262a85 --- /dev/null +++ b/Resources/Prototypes/_Goobstation/Entities/Clothing/Head/modsuit.yml @@ -0,0 +1,128 @@ +- type: entity + abstract: true + # Used to put pressureProtection, layers blocker and etc components into ComponentToggler + id: BaseClothingModsuitHelmet + name: base modsuit helmet + categories: [ HideSpawnMenu ] + components: + - type: Sprite + state: icon + - type: Clickable + - type: InteractionOutline + - type: GroupExamine + - type: Clothing + equippedPrefix: off + equipSound: /Audio/Mecha/mechmove03.ogg + unequipSound: /Audio/Mecha/mechmove03.ogg + quickEquip: false + slots: [ HEAD ] + clothingVisuals: + head: + - state: equipped-HEAD + - type: Tag + tags: + - WhitelistChameleon + +- type: entity + abstract: true + parent: BaseClothingModsuitHelmet + # Used for helmets that hide your identity even if it's not sealed + id: BaseClothingModsuitHelmetHideIdentity + name: base modsuit helmet + categories: [ HideSpawnMenu ] + components: + - type: IdentityBlocker + - type: IngestionBlocker + - type: HideLayerClothing + slots: + - Hair + - Snout + - HeadTop + - HeadSide + +- type: entity + parent: BaseClothingModsuitHelmet + id: ClothingModsuitHelmetStandard + name: standard modsuit helmet + description: A special modular suit spaceproof helmet designed for compact folding inside modular suit control. + categories: [ HideSpawnMenu ] + components: + - type: Appearance + - type: Sprite + sprite: _Goobstation/Clothing/Head/Modsuits/standard.rsi + layers: + - state: helmet + - state: helmet-sealed + visible: false + map: [ "sealed" ] + - type: HideLayerClothing # This helmet don't have sprite on unsealed state + slots: + - Snout + - type: SealableClothing + sealUpPopup: sealable-clothing-seal-up-helmet + sealDownPopup: sealable-clothing-seal-down-helmet + - type: SealableClothingVisuals + visualLayers: + head: + - state: equipped-HEAD-sealed + - type: Armor + modifiers: + coefficients: + Blunt: 0.90 + Slash: 0.90 + Piercing: 0.95 + Heat: 0.90 + Radiation: 0.75 + - type: ComponentToggler + components: + - type: BreathMask + - type: PressureProtection + highPressureMultiplier: 0.3 + lowPressureMultiplier: 1000 + - type: TemperatureProtection + coefficient: 0.1 + - type: IdentityBlocker + - type: IngestionBlocker + - type: HideLayerClothing + slots: + - Hair + - Snout + - HeadTop + - HeadSide + # This will all be replaced by modules later + - type: ToggleableLightVisuals + - type: PointLight + enabled: false + radius: 3 + energy: 2 + mask: /Textures/Effects/LightMasks/cone.png + autoRot: true + netsync: false + - type: HandheldLight + addPrefix: true + blinkingBehaviourId: blinking + radiatingBehaviourId: radiating + - type: LightBehaviour + behaviours: + - !type:FadeBehaviour + id: radiating + interpolate: Linear + maxDuration: 2.0 + startValue: 3.0 + endValue: 2.0 + isLooped: true + reverseWhenFinished: true + - !type:PulseBehaviour + id: blinking + interpolate: Nearest + maxDuration: 1.0 + minValue: 0.1 + maxValue: 2.0 + isLooped: true + - type: Battery + maxCharge: 600 # Lights drain 3/s but recharge of 2 makes this 1/s, therefore 600 is 10 minutes of light + startingCharge: 600 + - type: BatterySelfRecharger + autoRecharge: true + autoRechargeRate: 2 + diff --git a/Resources/Prototypes/_Goobstation/Entities/Clothing/OuterClothing/modsuit.yml b/Resources/Prototypes/_Goobstation/Entities/Clothing/OuterClothing/modsuit.yml new file mode 100644 index 00000000000..c8cca76bad7 --- /dev/null +++ b/Resources/Prototypes/_Goobstation/Entities/Clothing/OuterClothing/modsuit.yml @@ -0,0 +1,47 @@ +- type: entity + parent: ClothingOuterBase + id: ClothingModsuitChestplateStandard + name: standard modsuit chestplate + description: A special modular suit spaceproof cover designed for compact folding inside modular suit control. + categories: [ HideSpawnMenu ] + components: + - type: Appearance + - type: AllowSuitStorage + - type: Sprite + sprite: _Goobstation/Clothing/OuterClothing/Modsuits/standard.rsi + layers: + - state: chestplate + - state: chestplate-sealed + visible: false + map: [ "sealed" ] + - type: Clothing + equipSound: /Audio/Mecha/mechmove03.ogg + unequipSound: /Audio/Mecha/mechmove03.ogg + slots: [ outerClothing ] + - type: ClothingSpeedModifier + walkModifier: 0.9 + sprintModifier: 0.9 + - type: SealableClothing + sealUpPopup: sealable-clothing-seal-up-chestplate + sealDownPopup: sealable-clothing-seal-down-chestplate + - type: SealableClothingVisuals + visualLayers: + outerClothing: + - state: equipped-OUTERCLOTHING-sealed + shader: unshaded + - type: Armor + modifiers: + coefficients: + Blunt: 0.85 + Slash: 0.85 + Piercing: 0.85 + Heat: 0.85 + Radiation: 0.6 + - type: ComponentToggler + components: + - type: PressureProtection + highPressureMultiplier: 0.3 + lowPressureMultiplier: 1000 + - type: TemperatureProtection + coefficient: 0.1 + diff --git a/Resources/Prototypes/_Goobstation/Entities/Clothing/Shoes/modsuit.yml b/Resources/Prototypes/_Goobstation/Entities/Clothing/Shoes/modsuit.yml new file mode 100644 index 00000000000..4f192c6a239 --- /dev/null +++ b/Resources/Prototypes/_Goobstation/Entities/Clothing/Shoes/modsuit.yml @@ -0,0 +1,33 @@ +- type: entity + parent: ClothingShoesBase + id: ClothingModsuitBootsStandard + name: standard modsuit boots + description: A special modular suit boots designed for compact folding inside modular suit control. + categories: [ HideSpawnMenu ] + components: + - type: Appearance + - type: Sprite + sprite: _Goobstation/Clothing/Shoes/Modsuits/standard.rsi + layers: + - state: boots + - state: boots-sealed + visible: false + map: [ "sealed" ] + - type: Clothing + equipSound: /Audio/Mecha/mechmove03.ogg + unequipSound: /Audio/Mecha/mechmove03.ogg + slots: [ feet ] + - type: Tag + tags: + - WhitelistChameleon + - type: SealableClothing + sealUpPopup: sealable-clothing-seal-up-boots + sealDownPopup: sealable-clothing-seal-down-boots + - type: ClothingSpeedModifier + walkModifier: 0.9 + sprintModifier: 0.9 + - type: SealableClothingVisuals + visualLayers: + shoes: + - state: equipped-FEET-sealed + shader: unshaded diff --git a/Resources/Prototypes/_Goobstation/Entities/Objects/Specific/Robotics/modsuit_parts.yml b/Resources/Prototypes/_Goobstation/Entities/Objects/Specific/Robotics/modsuit_parts.yml new file mode 100644 index 00000000000..47ab0265e17 --- /dev/null +++ b/Resources/Prototypes/_Goobstation/Entities/Objects/Specific/Robotics/modsuit_parts.yml @@ -0,0 +1,162 @@ +- type: entity + id: PartModsuit + parent: BaseItem + name: MOD part + description: A part used in MOD construction. + abstract: true + components: + - type: Sprite + sprite: _Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi + - type: Icon + sprite: _Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi + - type: Item + size: Normal + - type: ContainerContainer + containers: + bodypart: !type:Container + ents: [] + #- type: GuideHelp TODO: Guide + +- type: entity + id: ModsuitChestplate + parent: PartModsuit + name: MOD chestplate + components: + - type: Sprite + state: chestplate + - type: Icon + state: chestplate + - type: PhysicalComposition + materialComposition: + Steel: 62 + - type: Tag + tags: + - ModsuitPart + - ModsuitChestplate + +- type: entity + id: ModsuitBoots + parent: PartModsuit + name: MOD boots + components: + - type: Sprite + state: boots + - type: Icon + state: boots + - type: PhysicalComposition + materialComposition: + Steel: 62 + - type: Tag + tags: + - ModsuitPart + - ModsuitBoots + +- type: entity + id: ModsuitHelmet + parent: PartModsuit + name: MOD helmet + components: + - type: Sprite + state: helmet + - type: Icon + state: helmet + - type: PhysicalComposition + materialComposition: + Steel: 62 + - type: Tag + tags: + - ModsuitPart + - ModsuitHelmet + +- type: entity + id: ModsuitGauntlets + parent: PartModsuit + name: MOD gauntlets + components: + - type: Sprite + state: gauntlets + - type: Icon + state: gauntlets + - type: PhysicalComposition + materialComposition: + Steel: 62 + - type: Tag + tags: + - ModsuitPart + - ModsuitGauntlets + +- type: entity + id: ModsuitShell + parent: PartModsuit + name: MOD shell + components: + - type: Appearance + - type: Sprite + state: shell + - type: Icon + state: shell + - type: PhysicalComposition + materialComposition: + Steel: 125 + Plasma: 62 + - type: ContainerContainer + containers: + cell_slot: !type:Container + core-container: !type:Container + - type: Construction + graph: Modsuit + node: start + defaultTarget: standard + containers: + - cell_slot + - core-container + - type: GenericVisualizer + visuals: + enum.ConstructionVisuals.Key: + enum.ConstructionVisuals.Layer: + shell-core: { state: shell-core } + shell-core-secured: { state: shell-core-secured } + shell-helmet: { state: shell-helmet } + shell-chestplate: { state: shell-chestplate } + shell-gauntlets: { state: shell-gauntlets } + shell-boots: { state: shell-boots } + shell-secured: { state: shell-secured } + +- type: entity + id: ModsuitPlatingExternal + parent: PartModsuit + name: MOD standard external plating + description: A part used in MOD construction. + components: + - type: Sprite + state: standard-plating + - type: Icon + state: standard-plating + - type: PhysicalComposition + materialComposition: + Steel: 75 + Glass: 37 + Plasma: 12 + - type: Tag + tags: + - ModsuitPart + - ModsuitPlatingExternal + +- type: entity + id: ModsuitCoreStandard + parent: PartModsuit + name: MOD standard core + description: Growing in the most lush, fertile areas of the planet Sprout, there is a crystal known as the Heartbloom. These rare, organic piezoelectric crystals are of incredible cultural significance to the artist castes of the Ethereals, owing to their appearance; which is exactly similar to that of an Ethereal's heart. Which one you have in your suit is unclear, but either way, it's been repurposed to be an internal power source for a Modular Outerwear Device. + components: + - type: Sprite + state: mod-core-standard + - type: Icon + state: mod-core-standard + - type: Tag + tags: + - ModsuitPart + - ModsuitCore + - type: PhysicalComposition + materialComposition: + Plasma: 50 + Glass: 25 diff --git a/Resources/Prototypes/_Goobstation/Recipes/Construction/Graphs/clothing/modsuit.yml b/Resources/Prototypes/_Goobstation/Recipes/Construction/Graphs/clothing/modsuit.yml new file mode 100644 index 00000000000..22357a98f7e --- /dev/null +++ b/Resources/Prototypes/_Goobstation/Recipes/Construction/Graphs/clothing/modsuit.yml @@ -0,0 +1,113 @@ +- type: constructionGraph + id: Modsuit + start: start + graph: + - node: start + entity: ModsuitShell + edges: + - to: shell-core + steps: + - tag: ModsuitCore + name: MOD core + store: core-container + completed: + - !type:PlaySound + sound: /Audio/Items/screwdriver2.ogg + + - node: shell-core + actions: + - !type:AppearanceChange + edges: + - to: start + steps: + - tool: Prying + completed: + - !type:EmptyContainer + container: core-container + - to: shell-core-secured + steps: + - tool: Screwing + doAfter: 1 + + - node: shell-core-secured + actions: + - !type:AppearanceChange + edges: + - to: shell-helmet + steps: + - tag: ModsuitHelmet + name: MOD helmet + doAfter: 1 + completed: + - !type:PlaySound + sound: /Audio/Items/screwdriver2.ogg + + - node: shell-helmet + actions: + - !type:AppearanceChange + edges: + - to: shell-chestplate + steps: + - tag: ModsuitChestplate + name: MOD chestplate + doAfter: 1 + completed: + - !type:PlaySound + sound: /Audio/Items/screwdriver2.ogg + + - node: shell-chestplate + actions: + - !type:AppearanceChange + edges: + - to: shell-gauntlets + steps: + - tag: ModsuitGauntlets + name: MOD gauntlets + doAfter: 1 + completed: + - !type:PlaySound + sound: /Audio/Items/screwdriver2.ogg + + - node: shell-gauntlets + actions: + - !type:AppearanceChange + edges: + - to: shell-boots + steps: + - tag: ModsuitBoots + name: MOD boots + doAfter: 1 + completed: + - !type:PlaySound + sound: /Audio/Items/screwdriver2.ogg + + - node: shell-boots + actions: + - !type:AppearanceChange + edges: + - to: shell-secured + steps: + - tool: Anchoring + doAfter: 1 + + - tool: Screwing + doAfter: 1 + + - node: shell-secured + actions: + - !type:AppearanceChange + edges: + - to: standard + steps: + - tag: ModsuitPlatingExternal + name: any MOD plating + doAfter: 1 + completed: + - !type:PlaySound + sound: /Audio/Items/screwdriver2.ogg + + - tool: Anchoring + doAfter: 1 + + - node: standard + entity: ClothingModsuitStandard diff --git a/Resources/Prototypes/_Goobstation/Recipes/Lathes/categories.yml b/Resources/Prototypes/_Goobstation/Recipes/Lathes/categories.yml index 42e63aaf246..a8107f0c2c5 100644 --- a/Resources/Prototypes/_Goobstation/Recipes/Lathes/categories.yml +++ b/Resources/Prototypes/_Goobstation/Recipes/Lathes/categories.yml @@ -38,3 +38,7 @@ - type: latheCategory id: MechWeapons name: lathe-category-mechs-weapons + +- type: latheCategory + id: Modsuit + name: lathe-category-modsuit diff --git a/Resources/Prototypes/_Goobstation/Recipes/Lathes/robotics.yml b/Resources/Prototypes/_Goobstation/Recipes/Lathes/robotics.yml new file mode 100644 index 00000000000..66c23c1fbca --- /dev/null +++ b/Resources/Prototypes/_Goobstation/Recipes/Lathes/robotics.yml @@ -0,0 +1,50 @@ +- type: latheRecipe + id: ModsuitChestplate + result: ModsuitChestplate + category: Modsuit + completetime: 5 + materials: + Steel: 250 + +- type: latheRecipe + id: ModsuitBoots + result: ModsuitBoots + category: Modsuit + completetime: 5 + materials: + Steel: 250 + +- type: latheRecipe + id: ModsuitHelmet + result: ModsuitHelmet + category: Modsuit + completetime: 5 + materials: + Steel: 250 + +- type: latheRecipe + id: ModsuitGauntlets + result: ModsuitGauntlets + category: Modsuit + completetime: 5 + materials: + Steel: 250 + +- type: latheRecipe + id: ModsuitShell + result: ModsuitShell + category: Modsuit + completetime: 5 + materials: + Steel: 500 + Plasma: 250 + +- type: latheRecipe + id: ModsuitPlatingExternal + result: ModsuitPlatingExternal + category: Modsuit + completetime: 5 + materials: + Steel: 300 + Glass: 150 + Plasma: 50 diff --git a/Resources/Prototypes/_Goobstation/Research/experimental.yml b/Resources/Prototypes/_Goobstation/Research/experimental.yml new file mode 100644 index 00000000000..732c3370166 --- /dev/null +++ b/Resources/Prototypes/_Goobstation/Research/experimental.yml @@ -0,0 +1,19 @@ +- type: technology + id: ModsuitsTech + name: research-technology-modsuits + icon: + sprite: _Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi + state: mod-core-standard + discipline: Experimental + tier: 3 + cost: 5000 + technologyPrerequisites: + - AdvancedTacsuits + softCapContribution: 1.5 + recipeUnlocks: + - ModsuitChestplate + - ModsuitBoots + - ModsuitHelmet + - ModsuitGauntlets + - ModsuitShell + - ModsuitPlatingExternal diff --git a/Resources/Prototypes/_Goobstation/tags.yml b/Resources/Prototypes/_Goobstation/tags.yml index 27c98e1ad88..2f3f9189671 100644 --- a/Resources/Prototypes/_Goobstation/tags.yml +++ b/Resources/Prototypes/_Goobstation/tags.yml @@ -96,7 +96,7 @@ - type: Tag id: RipleyMkII - + - type: Tag id: Clarke @@ -104,4 +104,30 @@ id: Durand - type: Tag - id: Gygax \ No newline at end of file + id: Gygax + + # MODsuits + +- type: Tag + id: ModsuitPart + +- type: Tag + id: ModsuitShell + +- type: Tag + id: ModsuitGauntlets + +- type: Tag + id: ModsuitHelmet + +- type: Tag + id: ModsuitBoots + +- type: Tag + id: ModsuitChestplate + +- type: Tag + id: ModsuitCore + +- type: Tag + id: ModsuitPlatingExternal diff --git a/Resources/Textures/_Goobstation/Actions/modsuit.rsi/activate-ready.png b/Resources/Textures/_Goobstation/Actions/modsuit.rsi/activate-ready.png new file mode 100644 index 00000000000..c604a291a5d Binary files /dev/null and b/Resources/Textures/_Goobstation/Actions/modsuit.rsi/activate-ready.png differ diff --git a/Resources/Textures/_Goobstation/Actions/modsuit.rsi/activate.png b/Resources/Textures/_Goobstation/Actions/modsuit.rsi/activate.png new file mode 100644 index 00000000000..aafa86494ea Binary files /dev/null and b/Resources/Textures/_Goobstation/Actions/modsuit.rsi/activate.png differ diff --git a/Resources/Textures/_Goobstation/Actions/modsuit.rsi/meta.json b/Resources/Textures/_Goobstation/Actions/modsuit.rsi/meta.json new file mode 100644 index 00000000000..86935425a06 --- /dev/null +++ b/Resources/Textures/_Goobstation/Actions/modsuit.rsi/meta.json @@ -0,0 +1,17 @@ +{ + "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": "activate" + }, + { + "name": "activate-ready" + } + ] +} diff --git a/Resources/Textures/_Goobstation/Clothing/Back/Modsuits/standard.rsi/control-sealed.png b/Resources/Textures/_Goobstation/Clothing/Back/Modsuits/standard.rsi/control-sealed.png new file mode 100644 index 00000000000..b6623346a2c Binary files /dev/null and b/Resources/Textures/_Goobstation/Clothing/Back/Modsuits/standard.rsi/control-sealed.png differ diff --git a/Resources/Textures/_Goobstation/Clothing/Back/Modsuits/standard.rsi/control.png b/Resources/Textures/_Goobstation/Clothing/Back/Modsuits/standard.rsi/control.png new file mode 100644 index 00000000000..3c697a64084 Binary files /dev/null and b/Resources/Textures/_Goobstation/Clothing/Back/Modsuits/standard.rsi/control.png differ diff --git a/Resources/Textures/_Goobstation/Clothing/Back/Modsuits/standard.rsi/equipped-BACKPACK-sealed.png b/Resources/Textures/_Goobstation/Clothing/Back/Modsuits/standard.rsi/equipped-BACKPACK-sealed.png new file mode 100644 index 00000000000..8c9541f32f1 Binary files /dev/null and b/Resources/Textures/_Goobstation/Clothing/Back/Modsuits/standard.rsi/equipped-BACKPACK-sealed.png differ diff --git a/Resources/Textures/_Goobstation/Clothing/Back/Modsuits/standard.rsi/equipped-BACKPACK.png b/Resources/Textures/_Goobstation/Clothing/Back/Modsuits/standard.rsi/equipped-BACKPACK.png new file mode 100644 index 00000000000..4f1b1240803 Binary files /dev/null and b/Resources/Textures/_Goobstation/Clothing/Back/Modsuits/standard.rsi/equipped-BACKPACK.png differ 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 00000000000..bbe4c7db6f0 Binary files /dev/null and b/Resources/Textures/_Goobstation/Clothing/Hands/Modsuits/standard.rsi/equipped-HAND-sealed.png differ diff --git a/Resources/Textures/_Goobstation/Clothing/Hands/Modsuits/standard.rsi/equipped-HAND.png b/Resources/Textures/_Goobstation/Clothing/Hands/Modsuits/standard.rsi/equipped-HAND.png new file mode 100644 index 00000000000..6cf77120995 Binary files /dev/null and b/Resources/Textures/_Goobstation/Clothing/Hands/Modsuits/standard.rsi/equipped-HAND.png differ diff --git a/Resources/Textures/_Goobstation/Clothing/Hands/Modsuits/standard.rsi/gauntlets-sealed.png b/Resources/Textures/_Goobstation/Clothing/Hands/Modsuits/standard.rsi/gauntlets-sealed.png new file mode 100644 index 00000000000..c118476a061 Binary files /dev/null and b/Resources/Textures/_Goobstation/Clothing/Hands/Modsuits/standard.rsi/gauntlets-sealed.png differ 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 00000000000..86570695735 Binary files /dev/null and b/Resources/Textures/_Goobstation/Clothing/Hands/Modsuits/standard.rsi/gauntlets.png differ 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 00000000000..bfb719e9856 Binary files /dev/null and b/Resources/Textures/_Goobstation/Clothing/Head/Modsuits/standard.rsi/equipped-HEAD-sealed.png differ diff --git a/Resources/Textures/_Goobstation/Clothing/Head/Modsuits/standard.rsi/equipped-HEAD.png b/Resources/Textures/_Goobstation/Clothing/Head/Modsuits/standard.rsi/equipped-HEAD.png new file mode 100644 index 00000000000..b9f66137b8f Binary files /dev/null and b/Resources/Textures/_Goobstation/Clothing/Head/Modsuits/standard.rsi/equipped-HEAD.png differ diff --git a/Resources/Textures/_Goobstation/Clothing/Head/Modsuits/standard.rsi/helmet-sealed.png b/Resources/Textures/_Goobstation/Clothing/Head/Modsuits/standard.rsi/helmet-sealed.png new file mode 100644 index 00000000000..0031e63fb36 Binary files /dev/null and b/Resources/Textures/_Goobstation/Clothing/Head/Modsuits/standard.rsi/helmet-sealed.png differ 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 00000000000..968c1554141 Binary files /dev/null and b/Resources/Textures/_Goobstation/Clothing/Head/Modsuits/standard.rsi/helmet.png differ 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 00000000000..5f374eecd9d Binary files /dev/null and b/Resources/Textures/_Goobstation/Clothing/OuterClothing/Modsuits/standard.rsi/chestplate-sealed.png differ 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 00000000000..404383a236f Binary files /dev/null and b/Resources/Textures/_Goobstation/Clothing/OuterClothing/Modsuits/standard.rsi/chestplate.png differ diff --git a/Resources/Textures/_Goobstation/Clothing/OuterClothing/Modsuits/standard.rsi/equipped-OUTERCLOTHING-sealed.png b/Resources/Textures/_Goobstation/Clothing/OuterClothing/Modsuits/standard.rsi/equipped-OUTERCLOTHING-sealed.png new file mode 100644 index 00000000000..41ec1315e41 Binary files /dev/null and b/Resources/Textures/_Goobstation/Clothing/OuterClothing/Modsuits/standard.rsi/equipped-OUTERCLOTHING-sealed.png differ diff --git a/Resources/Textures/_Goobstation/Clothing/OuterClothing/Modsuits/standard.rsi/equipped-OUTERCLOTHING.png b/Resources/Textures/_Goobstation/Clothing/OuterClothing/Modsuits/standard.rsi/equipped-OUTERCLOTHING.png new file mode 100644 index 00000000000..708d97c92ac Binary files /dev/null and b/Resources/Textures/_Goobstation/Clothing/OuterClothing/Modsuits/standard.rsi/equipped-OUTERCLOTHING.png differ 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 00000000000..4d141b313fb Binary files /dev/null and b/Resources/Textures/_Goobstation/Clothing/Shoes/Modsuits/standard.rsi/boots-sealed.png differ 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 00000000000..7dcd0cf6e5f Binary files /dev/null and b/Resources/Textures/_Goobstation/Clothing/Shoes/Modsuits/standard.rsi/boots.png differ 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 00000000000..5ad67b6efa1 Binary files /dev/null and b/Resources/Textures/_Goobstation/Clothing/Shoes/Modsuits/standard.rsi/equipped-FEET-sealed.png differ 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 00000000000..a95a4bb37de Binary files /dev/null and b/Resources/Textures/_Goobstation/Clothing/Shoes/Modsuits/standard.rsi/equipped-FEET.png differ 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 00000000000..2e738ca7e76 Binary files /dev/null and b/Resources/Textures/_Goobstation/Interface/Alerts/modpower.rsi/modpower0.png differ diff --git a/Resources/Textures/_Goobstation/Interface/Alerts/modpower.rsi/modpower1.png b/Resources/Textures/_Goobstation/Interface/Alerts/modpower.rsi/modpower1.png new file mode 100644 index 00000000000..62780521b71 Binary files /dev/null and b/Resources/Textures/_Goobstation/Interface/Alerts/modpower.rsi/modpower1.png differ 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 00000000000..21d8e7f6e7a Binary files /dev/null and b/Resources/Textures/_Goobstation/Interface/Alerts/modpower.rsi/modpower2.png differ diff --git a/Resources/Textures/_Goobstation/Interface/Alerts/modpower.rsi/modpower3.png b/Resources/Textures/_Goobstation/Interface/Alerts/modpower.rsi/modpower3.png new file mode 100644 index 00000000000..80c0d1dcfca Binary files /dev/null and b/Resources/Textures/_Goobstation/Interface/Alerts/modpower.rsi/modpower3.png differ diff --git a/Resources/Textures/_Goobstation/Interface/Alerts/modpower.rsi/modpower4.png b/Resources/Textures/_Goobstation/Interface/Alerts/modpower.rsi/modpower4.png new file mode 100644 index 00000000000..d6b47c7577c Binary files /dev/null and b/Resources/Textures/_Goobstation/Interface/Alerts/modpower.rsi/modpower4.png differ 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 00000000000..d6b47c7577c Binary files /dev/null and b/Resources/Textures/_Goobstation/Interface/Alerts/modpower.rsi/modpower5.png differ 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 00000000000..0b0ada9e583 Binary files /dev/null and b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/boots.png differ 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 00000000000..a499fcf230f Binary files /dev/null and b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/chestplate.png differ diff --git a/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/gauntlets.png b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/gauntlets.png new file mode 100644 index 00000000000..f242f4db403 Binary files /dev/null and b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/gauntlets.png differ 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 00000000000..5385dcb9fa9 Binary files /dev/null and b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/helmet.png differ 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 00000000000..437079c540f Binary files /dev/null and b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/mod-core-standard.png differ diff --git a/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell-boots.png b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell-boots.png new file mode 100644 index 00000000000..f825bb5c40b Binary files /dev/null and b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell-boots.png differ diff --git a/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell-chestplate.png b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell-chestplate.png new file mode 100644 index 00000000000..25384dabf48 Binary files /dev/null and b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell-chestplate.png differ diff --git a/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell-core-secured.png b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell-core-secured.png new file mode 100644 index 00000000000..f03323b1000 Binary files /dev/null and b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell-core-secured.png differ diff --git a/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell-core.png b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell-core.png new file mode 100644 index 00000000000..1957741c8de Binary files /dev/null and b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell-core.png differ diff --git a/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell-gauntlets.png b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell-gauntlets.png new file mode 100644 index 00000000000..02b9c2824b7 Binary files /dev/null and b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell-gauntlets.png differ 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 00000000000..9417be1ef66 Binary files /dev/null and b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell-helmet.png differ diff --git a/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell-secured.png b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell-secured.png new file mode 100644 index 00000000000..4152554390a Binary files /dev/null and b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell-secured.png differ diff --git a/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell.png b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell.png new file mode 100644 index 00000000000..0f9665700ad Binary files /dev/null and b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/shell.png differ 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 00000000000..ff521088cbf Binary files /dev/null and b/Resources/Textures/_Goobstation/Objects/Specific/Robotics/modsuit_parts.rsi/standard-plating.png differ