# Description Ports MODsuits from Goobstation PR https://github.com/Goob-Station/Goob-Station/pull/1242. The PR author has confirmed that he is okay with me doing this. --- # TODO - [X] Port in sprites - [x] Port in YMLs - [X] Port code - [x] Port code PATCHES - [x] Update EE with required fixes --- <details><summary><h1>Media</h1></summary> <p> ## Modsuit crafting https://github.com/user-attachments/assets/8ff03d3a-0fc1-4818-b710-bfc43f0e2a68 ## Modsuit sealing https://github.com/user-attachments/assets/6671459a-7767-499b-8678-062fc1db7134 </p> </details> --- # Changelog 🆑 - add: Modsuits have been ported from Goobstation! --------- Signed-off-by: Eris <erisfiregamer1@gmail.com> Co-authored-by: DEATHB4DEFEAT <77995199+DEATHB4DEFEAT@users.noreply.github.com> Co-authored-by: VMSolidus <evilexecutive@gmail.com> (cherry picked from commit cb06c41fc07275e1f15af916babb44368c0c26c2)
@@ -112,7 +112,8 @@ public sealed class DoAfterOverlay : Overlay
|
||||
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.
|
||||
|
||||
@@ -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<string, List<PrototypeLayerData>> VisualLayers = new();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
using Content.Shared._Goobstation.Clothing.Systems;
|
||||
|
||||
namespace Content.Client._Goobstation.Clothing.EntitySystems;
|
||||
|
||||
public sealed class PoweredSealableClothingSystem : SharedPoweredSealableClothingSystem
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
using Content.Shared._Goobstation.Clothing.Systems;
|
||||
|
||||
namespace Content.Client._Goobstation.Clothing.EntitySystems;
|
||||
|
||||
public sealed partial class SealableClothingSystem : SharedSealableClothingSystem
|
||||
{
|
||||
}
|
||||
@@ -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<SealableClothingVisualsComponent>
|
||||
{
|
||||
[Dependency] private readonly SharedItemSystem _itemSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<SealableClothingVisualsComponent, GetEquipmentVisualsEvent>(OnGetEquipmentVisuals, after: new[] { typeof(ClientClothingSystem) });
|
||||
}
|
||||
|
||||
protected override void OnAppearanceChange(EntityUid uid, SealableClothingVisualsComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (!AppearanceSystem.TryGetData<bool>(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<SealableClothingVisualsComponent> sealable, ref GetEquipmentVisualsEvent args)
|
||||
{
|
||||
var (uid, comp) = sealable;
|
||||
|
||||
if (!TryComp(uid, out AppearanceComponent? appearance)
|
||||
|| !AppearanceSystem.TryGetData<bool>(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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<IEntityManager>();
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = this.CreateWindow<ToggleableClothingRadialMenu>();
|
||||
_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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<ui:RadialMenu xmlns="https://spacestation14.io"
|
||||
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
BackButtonStyleClass="RadialMenuBackButton" CloseButtonStyleClass="RadialMenuCloseButton"
|
||||
VerticalExpand="True" HorizontalExpand="True" MinSize="450 450">
|
||||
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" Radius="64" ReserveSpaceForHiddenChildren="False" />
|
||||
</ui:RadialMenu>
|
||||
@@ -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<EntityUid>? 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<RadialContainer>("Main");
|
||||
|
||||
if (!_entityManager.TryGetComponent<ToggleableClothingComponent>(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; }
|
||||
}
|
||||
@@ -18,21 +18,27 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
[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<PressureProtectionComponent, GotEquippedEvent>(OnPressureProtectionEquipped);
|
||||
SubscribeLocalEvent<PressureProtectionComponent, GotUnequippedEvent>(OnPressureProtectionUnequipped);
|
||||
SubscribeLocalEvent<PressureProtectionComponent, ComponentInit>(OnUpdateResistance);
|
||||
SubscribeLocalEvent<PressureProtectionComponent, ComponentRemove>(OnUpdateResistance);
|
||||
SubscribeLocalEvent<PressureProtectionComponent, ComponentInit>(OnPressureProtectionChanged); // Goobstation - Update component state on toggle
|
||||
SubscribeLocalEvent<PressureProtectionComponent, ComponentRemove>(OnPressureProtectionChanged); // Goobstation - Update component state on toggle
|
||||
|
||||
SubscribeLocalEvent<PressureImmunityComponent, ComponentInit>(OnPressureImmuneInit);
|
||||
SubscribeLocalEvent<PressureImmunityComponent, ComponentRemove>(OnPressureImmuneRemove);
|
||||
|
||||
// _sawmill = _logManager.GetSawmill("barotrauma");
|
||||
}
|
||||
|
||||
private void OnPressureImmuneInit(EntityUid uid, PressureImmunityComponent pressureImmunity, ComponentInit args)
|
||||
@@ -51,6 +57,27 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
}
|
||||
}
|
||||
|
||||
// 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<BarotraumaComponent>(protectionTarget, out var barotrauma))
|
||||
return;
|
||||
|
||||
if (slotTarget != null && !barotrauma.ProtectionSlots.Contains(slotTarget))
|
||||
return;
|
||||
|
||||
UpdateCachedResistances(protectionTarget, barotrauma);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generic method for updating resistance on component Lifestage events
|
||||
/// </summary>
|
||||
|
||||
@@ -34,4 +34,10 @@ public sealed partial class LungComponent : Component
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> Alert = "LowOxygen";
|
||||
|
||||
[DataField]
|
||||
public float MaxVolume = 100f;
|
||||
|
||||
[DataField]
|
||||
public bool CanReact = false; // No Dexalin lungs... right?
|
||||
}
|
||||
|
||||
@@ -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 sealed class LungSystem : EntitySystem
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<LungComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<BreathToolComponent, ComponentInit>(OnBreathToolInit); // Goobstation - Modsuits - Update on component toggle
|
||||
SubscribeLocalEvent<BreathToolComponent, GotEquippedEvent>(OnGotEquipped);
|
||||
SubscribeLocalEvent<BreathToolComponent, GotUnequippedEvent>(OnGotUnequipped);
|
||||
SubscribeLocalEvent<BreathToolComponent, ItemMaskToggledEvent>(OnMaskToggled);
|
||||
@@ -50,16 +56,36 @@ public sealed class LungSystem : EntitySystem
|
||||
|
||||
private void OnComponentInit(Entity<LungComponent> 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<BreathToolComponent> 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<BreathToolComponent> ent, ref ItemMaskToggledEvent args)
|
||||
{
|
||||
if (args.IsToggled || args.IsEquip)
|
||||
{
|
||||
_atmos.DisconnectInternals(ent.Comp);
|
||||
_atmosphereSystem.DisconnectInternals(ent);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -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<EntityUid> _queuedIdentityUpdates = new();
|
||||
|
||||
@@ -41,7 +42,11 @@ public sealed class IdentitySystem : SharedIdentitySystem
|
||||
SubscribeLocalEvent<IdentityComponent, DidUnequipEvent>((uid, _, _) => QueueIdentityUpdate(uid));
|
||||
SubscribeLocalEvent<IdentityComponent, DidUnequipHandEvent>((uid, _, _) => QueueIdentityUpdate(uid));
|
||||
SubscribeLocalEvent<IdentityComponent, WearerMaskToggledEvent>((uid, _, _) => QueueIdentityUpdate(uid));
|
||||
SubscribeLocalEvent<IdentityComponent, EntityRenamedEvent>((uid, _, _) => QueueIdentityUpdate(uid));
|
||||
SubscribeLocalEvent<IdentityComponent, MapInitEvent>(OnMapInit);
|
||||
|
||||
SubscribeLocalEvent<IdentityBlockerComponent, ComponentInit>(BlockerUpdateIdentity); // Goobstation - Update component state on component toggle
|
||||
SubscribeLocalEvent<IdentityBlockerComponent, ComponentRemove>(BlockerUpdateIdentity); // Goobstation - Update component state on component toggle
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
@@ -175,5 +180,16 @@ public sealed class IdentitySystem : SharedIdentitySystem
|
||||
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
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ using System.Diagnostics.CodeAnalysis;
|
||||
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 @@ internal sealed class ChargerSystem : EntitySystem
|
||||
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<InventoryComponent>(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<BatteryComponent>? FoundBattery { get; set; }
|
||||
}
|
||||
|
||||
@@ -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<SealableClothingRequiresPowerComponent, InventoryRelayedEvent<RefreshMovementSpeedModifiersEvent>>(OnMovementSpeedChange);
|
||||
SubscribeLocalEvent<SealableClothingRequiresPowerComponent, PowerCellChangedEvent>(OnPowerCellChanged);
|
||||
SubscribeLocalEvent<SealableClothingRequiresPowerComponent, PowerCellSlotEmptyEvent>(OnPowerCellEmpty);
|
||||
SubscribeLocalEvent<SealableClothingRequiresPowerComponent, ClothingControlSealCompleteEvent>(OnRequiresPowerSealCompleteEvent);
|
||||
SubscribeLocalEvent<SealableClothingRequiresPowerComponent, InventoryRelayedEvent<FindInventoryBatteryEvent>>(OnFindInventoryBatteryEvent);
|
||||
}
|
||||
|
||||
private void OnPowerCellChanged(Entity<SealableClothingRequiresPowerComponent> 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<SealableClothingRequiresPowerComponent> 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<SealableClothingRequiresPowerComponent> 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<SealableClothingRequiresPowerComponent> entity, ref InventoryRelayedEvent<RefreshMovementSpeedModifiersEvent> 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<SealableClothingRequiresPowerComponent> entity)
|
||||
{
|
||||
var (uid, comp) = entity;
|
||||
|
||||
if (!TryComp<SealableClothingControlComponent>(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<SealableClothingRequiresPowerComponent> entity, ref InventoryRelayedEvent<FindInventoryBatteryEvent> args)
|
||||
{
|
||||
if (args.Args.FoundBattery != null)
|
||||
return;
|
||||
|
||||
if (_powerCellSystem.TryGetBatteryFromSlot(entity, out var batteryEnt, out var battery))
|
||||
args.Args.FoundBattery = (batteryEnt.Value, battery);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
using Content.Shared._Goobstation.Clothing.Systems;
|
||||
|
||||
namespace Content.Server._Goobstation.Clothing.Systems;
|
||||
|
||||
public sealed partial class SealableClothingSystem : SharedSealableClothingSystem { }
|
||||
@@ -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
|
||||
/// <summary>
|
||||
/// Warning popup shown when priming the action.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public LocId Popup = string.Empty;
|
||||
// Goobstation - Modsuits - Removed required string
|
||||
[DataField]
|
||||
public LocId? Popup = null;
|
||||
|
||||
/// <summary>
|
||||
/// Type of warning popup - Goobstaiton - Modsuits
|
||||
/// </summary>
|
||||
[DataField("popupType")]
|
||||
public PopupType PopupFontType = PopupType.LargeCaution;
|
||||
|
||||
/// <summary>
|
||||
/// If not null, this is when the action can be confirmed at.
|
||||
|
||||
@@ -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 @@ public sealed class ConfirmableActionSystem : EntitySystem
|
||||
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<ConfirmableActionComponent> ent)
|
||||
@@ -75,6 +81,9 @@ public sealed class ConfirmableActionSystem : EntitySystem
|
||||
var (uid, comp) = ent;
|
||||
comp.NextConfirm = null;
|
||||
comp.NextUnprime = null;
|
||||
|
||||
_actions.SetToggled(ent, false); // Goobstation - Confirmable action with changed icon
|
||||
|
||||
Dirty(uid, comp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
/// <summary>
|
||||
/// Used for clothing that reduces damage when worn.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedArmorSystem))]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] // Goobstation - remove access restrictions
|
||||
public sealed partial class ArmorComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The damage reduction
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
[DataField(required: true), AutoNetworkedField]
|
||||
public DamageModifierSet Modifiers = default!;
|
||||
|
||||
/// <summary>
|
||||
/// A multiplier applied to the calculated point value
|
||||
/// to determine the monetary value of the armor
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public float PriceMultiplier = 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
/// <summary>
|
||||
/// The Id of the piece of clothing that this entity belongs to.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid AttachedUid;
|
||||
|
||||
/// <summary>
|
||||
/// Container ID for clothing that will be replaced with this one
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public string ClothingContainerId = DefaultClothingContainerId;
|
||||
|
||||
[ViewVariables, NonSerialized]
|
||||
public ContainerSlot? ClothingContainer;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ using Content.Shared.Inventory;
|
||||
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
|
||||
/// <summary>
|
||||
/// Default clothing entity prototype to spawn into the clothing container.
|
||||
/// </summary>
|
||||
[DataField(required: true), AutoNetworkedField]
|
||||
public EntProtoId ClothingPrototype = default!;
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntProtoId? ClothingPrototype;
|
||||
|
||||
/// <summary>
|
||||
/// The inventory slot that the clothing is equipped to.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField, AutoNetworkedField]
|
||||
public string Slot = "head";
|
||||
public string Slot = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary of inventory slots and entity prototypes to spawn into the clothing container.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public Dictionary<string, EntProtoId> ClothingPrototypes = new();
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary of clothing uids and slots
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public Dictionary<EntityUid, string> ClothingUids = new();
|
||||
|
||||
/// <summary>
|
||||
/// 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;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? ClothingUid;
|
||||
public Container? Container;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public string? VerbText;
|
||||
|
||||
/// <summary>
|
||||
/// If true it will block unequip of this entity until all attached clothing are removed
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool BlockUnequipWhenAttached = false;
|
||||
|
||||
/// <summary>
|
||||
/// If true all attached will replace already equipped clothing on equip attempt
|
||||
/// </summary>
|
||||
[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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,11 @@ using Robust.Shared.Network;
|
||||
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<ToggleableClothingComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<ToggleableClothingComponent, ComponentInit>(OnToggleableInit);
|
||||
SubscribeLocalEvent<ToggleableClothingComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<ToggleableClothingComponent, ToggleClothingEvent>(OnToggleClothing);
|
||||
SubscribeLocalEvent<ToggleableClothingComponent, ToggleClothingEvent>(OnToggleClothingAction);
|
||||
SubscribeLocalEvent<ToggleableClothingComponent, GetItemActionsEvent>(OnGetActions);
|
||||
SubscribeLocalEvent<ToggleableClothingComponent, ComponentRemove>(OnRemoveToggleable);
|
||||
SubscribeLocalEvent<ToggleableClothingComponent, GotUnequippedEvent>(OnToggleableUnequip);
|
||||
SubscribeLocalEvent<ToggleableClothingComponent, ToggleableClothingUiMessage>(OnToggleClothingMessage);
|
||||
SubscribeLocalEvent<ToggleableClothingComponent, BeingUnequippedAttemptEvent>(OnToggleableUnequipAttempt);
|
||||
|
||||
SubscribeLocalEvent<AttachedClothingComponent, ComponentInit>(OnAttachedInit);
|
||||
SubscribeLocalEvent<AttachedClothingComponent, InteractHandEvent>(OnInteractHand);
|
||||
SubscribeLocalEvent<AttachedClothingComponent, GotUnequippedEvent>(OnAttachedUnequip);
|
||||
SubscribeLocalEvent<AttachedClothingComponent, ComponentRemove>(OnRemoveAttached);
|
||||
@@ -51,62 +56,64 @@ public sealed class ToggleableClothingSystem : EntitySystem
|
||||
SubscribeLocalEvent<ToggleableClothingComponent, ToggleClothingDoAfterEvent>(OnDoAfterComplete);
|
||||
}
|
||||
|
||||
private void GetRelayedVerbs(EntityUid uid, ToggleableClothingComponent component, InventoryRelayedEvent<GetVerbsEvent<EquipmentVerb>> args)
|
||||
private void GetRelayedVerbs(Entity<ToggleableClothingComponent> toggleable, ref InventoryRelayedEvent<GetVerbsEvent<EquipmentVerb>> args)
|
||||
{
|
||||
OnGetVerbs(uid, component, args.Args);
|
||||
OnGetVerbs(toggleable, ref args.Args);
|
||||
}
|
||||
|
||||
private void OnGetVerbs(EntityUid uid, ToggleableClothingComponent component, GetVerbsEvent<EquipmentVerb> args)
|
||||
private void OnGetVerbs(Entity<ToggleableClothingComponent> toggleable, ref GetVerbsEvent<EquipmentVerb> 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<ToggleableClothingComponent> 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 @@ public sealed class ToggleableClothingSystem : EntitySystem
|
||||
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<EquipmentVerb> args)
|
||||
private void OnGetAttachedStripVerbsEvent(Entity<AttachedClothingComponent> attached, ref GetVerbsEvent<EquipmentVerb> args)
|
||||
{
|
||||
var comp = attached.Comp;
|
||||
|
||||
if (!TryComp<ToggleableClothingComponent>(comp.AttachedUid, out var toggleableComp))
|
||||
return;
|
||||
|
||||
// redirect to the attached entity.
|
||||
OnGetVerbs(component.AttachedUid, Comp<ToggleableClothingComponent>(component.AttachedUid), args);
|
||||
OnGetVerbs((comp.AttachedUid, toggleableComp), ref args);
|
||||
}
|
||||
|
||||
private void OnDoAfterComplete(EntityUid uid, ToggleableClothingComponent component, ToggleClothingDoAfterEvent args)
|
||||
private void OnDoAfterComplete(Entity<ToggleableClothingComponent> 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<AttachedClothingComponent> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prevents from unequipping entity if all attached not unequipped
|
||||
/// </summary>
|
||||
private void OnToggleableUnequipAttempt(Entity<ToggleableClothingComponent> 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the suit is unequipped, to ensure that the helmet also gets unequipped.
|
||||
/// </summary>
|
||||
private void OnToggleableUnequip(EntityUid uid, ToggleableClothingComponent component, GotUnequippedEvent args)
|
||||
private void OnToggleableUnequip(Entity<ToggleableClothingComponent> 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<ToggleableClothingComponent> 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;
|
||||
|
||||
if (component.ClothingUid != null && !_netMan.IsClient)
|
||||
QueueDel(component.ClothingUid.Value);
|
||||
_actionsSystem.RemoveAction(comp.ActionEntity);
|
||||
|
||||
if (comp.ClothingUids == null || _netMan.IsClient)
|
||||
return;
|
||||
|
||||
foreach (var clothing in comp.ClothingUids.Keys)
|
||||
QueueDel(clothing);
|
||||
}
|
||||
|
||||
private void OnAttachedUnequipAttempt(EntityUid uid, AttachedClothingComponent component, BeingUnequippedAttemptEvent args)
|
||||
private void OnAttachedUnequipAttempt(Entity<AttachedClothingComponent> attached, ref BeingUnequippedAttemptEvent args)
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnRemoveAttached(EntityUid uid, AttachedClothingComponent component, ComponentRemove args)
|
||||
private void OnRemoveAttached(Entity<AttachedClothingComponent> 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;
|
||||
|
||||
if (toggleComp.LifeStage > ComponentLifeStage.Running)
|
||||
var clothingUids = toggleableComp.ClothingUids;
|
||||
|
||||
if (!clothingUids.Remove(attached.Owner) || clothingUids.Count > 0)
|
||||
return;
|
||||
|
||||
_actionsSystem.RemoveAction(toggleComp.ActionEntity);
|
||||
RemComp(component.AttachedUid, toggleComp);
|
||||
// If no attached clothing left - remove component and action
|
||||
if (clothingUids.Count > 0)
|
||||
return;
|
||||
|
||||
_actionsSystem.RemoveAction(toggleableComp.ActionEntity);
|
||||
RemComp(comp.AttachedUid, toggleableComp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
private void OnAttachedUnequip(EntityUid uid, AttachedClothingComponent component, GotUnequippedEvent args)
|
||||
private void OnAttachedUnequip(Entity<AttachedClothingComponent> attached, ref GotUnequippedEvent args)
|
||||
{
|
||||
// Let containers worry about it.
|
||||
if (_timing.ApplyingState)
|
||||
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 (component.LifeStage > ComponentLifeStage.Running)
|
||||
return;
|
||||
if (toggleableComp.Container != null)
|
||||
_containerSystem.Insert(attached.Owner, toggleableComp.Container);
|
||||
}
|
||||
|
||||
if (!TryComp(component.AttachedUid, out ToggleableClothingComponent? toggleComp))
|
||||
return;
|
||||
/// <summary>
|
||||
/// Equip or unequip toggle clothing with ui message
|
||||
/// </summary>
|
||||
private void OnToggleClothingMessage(Entity<ToggleableClothingComponent> toggleable, ref ToggleableClothingUiMessage args)
|
||||
{
|
||||
var attachedUid = GetEntity(args.AttachedClothingUid);
|
||||
|
||||
if (toggleComp.LifeStage > ComponentLifeStage.Running)
|
||||
return;
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Equip or unequip the toggleable clothing.
|
||||
/// </summary>
|
||||
private void OnToggleClothing(EntityUid uid, ToggleableClothingComponent component, ToggleClothingEvent args)
|
||||
private void OnToggleClothingAction(Entity<ToggleableClothingComponent> toggleable, ref ToggleClothingEvent args)
|
||||
{
|
||||
var comp = toggleable.Comp;
|
||||
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
ToggleClothing(args.Performer, uid, component);
|
||||
}
|
||||
|
||||
private void ToggleClothing(EntityUid user, EntityUid target, ToggleableClothingComponent component)
|
||||
{
|
||||
if (component.Container == null || component.ClothingUid == null)
|
||||
if (comp.Container == null || comp.ClothingUids.Count == 0)
|
||||
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);
|
||||
}
|
||||
args.Handled = true;
|
||||
|
||||
// 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
|
||||
_inventorySystem.TryEquip(user, parent, component.ClothingUid.Value, component.Slot);
|
||||
_uiSystem.OpenUi(toggleable.Owner, ToggleClothingUiKey.Key, args.Performer);
|
||||
}
|
||||
|
||||
private void OnGetActions(EntityUid uid, ToggleableClothingComponent component, GetItemActionsEvent args)
|
||||
/// <summary>
|
||||
/// Toggle function for single clothing
|
||||
/// </summary>
|
||||
private void ToggleClothing(EntityUid user, Entity<ToggleableClothingComponent> toggleable, EntityUid attachedUid)
|
||||
{
|
||||
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 (!CanToggleClothing(user, toggleable))
|
||||
return;
|
||||
|
||||
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!);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggle function for toggling multiple clothings at once
|
||||
/// </summary>
|
||||
private void ToggleClothing(EntityUid user, Entity<ToggleableClothingComponent> 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
|
||||
foreach (var clothing in attachedClothings)
|
||||
if (!container!.Contains(clothing.Key))
|
||||
UnequipClothing(user, toggleable, clothing.Key, clothing.Value);
|
||||
}
|
||||
|
||||
private bool CanToggleClothing(EntityUid user, Entity<ToggleableClothingComponent> toggleable)
|
||||
{
|
||||
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<ToggleableClothingComponent> 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<AttachedClothingComponent>(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<ToggleableClothingComponent> 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<AttachedClothingComponent>(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<ToggleableClothingComponent> toggleable, ref GetItemActionsEvent args)
|
||||
{
|
||||
component.Container = _containerSystem.EnsureContainer<ContainerSlot>(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<ToggleableClothingComponent> toggleable, ref ComponentInit args)
|
||||
{
|
||||
var comp = toggleable.Comp;
|
||||
|
||||
comp.Container = _containerSystem.EnsureContainer<Container>(toggleable, comp.ContainerId);
|
||||
}
|
||||
|
||||
private void OnAttachedInit(Entity<AttachedClothingComponent> attached, ref ComponentInit args)
|
||||
{
|
||||
var comp = attached.Comp;
|
||||
|
||||
comp.ClothingContainer = _containerSystem.EnsureContainer<ContainerSlot>(attached, comp.ClothingContainerId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
private void OnMapInit(EntityUid uid, ToggleableClothingComponent component, MapInitEvent args)
|
||||
private void OnMapInit(Entity<ToggleableClothingComponent> 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");
|
||||
}
|
||||
else
|
||||
{
|
||||
var xform = Transform(uid);
|
||||
component.ClothingUid = Spawn(component.ClothingPrototype, xform.Coordinates);
|
||||
var attachedClothing = EnsureComp<AttachedClothingComponent>(component.ClothingUid.Value);
|
||||
attachedClothing.AttachedUid = uid;
|
||||
Dirty(component.ClothingUid.Value, attachedClothing);
|
||||
_containerSystem.Insert(component.ClothingUid.Value, component.Container, containerXform: xform);
|
||||
Dirty(uid, component);
|
||||
comp.ClothingPrototypes.Add(comp.Slot, comp.ClothingPrototype.Value);
|
||||
}
|
||||
|
||||
if (_actionContainer.EnsureAction(uid, ref component.ActionEntity, out var action, component.Action))
|
||||
_actionsSystem.SetEntityIcon(component.ActionEntity.Value, component.ClothingUid, action);
|
||||
var xform = Transform(toggleable.Owner);
|
||||
|
||||
if (comp.ClothingPrototypes == null)
|
||||
return;
|
||||
|
||||
var prototypes = comp.ClothingPrototypes;
|
||||
|
||||
foreach (var prototype in prototypes)
|
||||
{
|
||||
var spawned = Spawn(prototype.Value, xform.Coordinates);
|
||||
var attachedClothing = EnsureComp<AttachedClothingComponent>(spawned);
|
||||
attachedClothing.AttachedUid = toggleable;
|
||||
EnsureComp<ContainerManagerComponent>(spawned);
|
||||
|
||||
comp.ClothingUids.Add(spawned, prototype.Key);
|
||||
_containerSystem.Insert(spawned, comp.Container, containerXform: xform);
|
||||
|
||||
Dirty(spawned, attachedClothing);
|
||||
}
|
||||
|
||||
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<EntityUid>? GetAttachedClothingsList(EntityUid toggleable, ToggleableClothingComponent? component = null)
|
||||
{
|
||||
if (!Resolve(toggleable, ref component) || component.ClothingUids.Count == 0)
|
||||
return null;
|
||||
|
||||
var newList = new List<EntityUid>();
|
||||
|
||||
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
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raises on toggleable clothing when someone trying to toggle it
|
||||
/// </summary>
|
||||
public sealed class ToggleClothingAttemptEvent : CancellableEntityEventArgs
|
||||
{
|
||||
public EntityUid User { get; }
|
||||
public EntityUid Target { get; }
|
||||
|
||||
public ToggleClothingAttemptEvent(EntityUid user, EntityUid target)
|
||||
{
|
||||
User = user;
|
||||
Target = target;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Status of toggleable clothing attachee
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public enum ToggleableClothingAttachedStatus : byte
|
||||
{
|
||||
NoneToggled,
|
||||
PartlyToggled,
|
||||
AllToggled
|
||||
}
|
||||
|
||||
@@ -19,14 +19,14 @@ public sealed partial class DoAfterArgs
|
||||
/// <summary>
|
||||
/// How long does the do_after require to complete
|
||||
/// </summary>
|
||||
[DataField("delay", required: true)]
|
||||
[DataField(required: true)]
|
||||
public TimeSpan Delay;
|
||||
|
||||
/// <summary>
|
||||
/// Applicable target (if relevant)
|
||||
/// </summary>
|
||||
[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;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the progress bar for this DoAfter should be hidden from other players.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Hidden;
|
||||
|
||||
/// Whether the delay multiplier event should be raised
|
||||
[DataField]
|
||||
public bool MultiplyDelay = true;
|
||||
|
||||
#region Event options
|
||||
/// <summary>
|
||||
/// The event that will get raised when the DoAfter has finished. If null, this will simply raise a <see cref="SimpleDoAfterEvent"/>
|
||||
/// </summary>
|
||||
[DataField("event", required: true)]
|
||||
[DataField(required: true)]
|
||||
public DoAfterEvent Event = default!;
|
||||
|
||||
/// <summary>
|
||||
@@ -64,7 +75,7 @@ public sealed partial class DoAfterArgs
|
||||
/// Entity which will receive the directed event. If null, no directed event will be raised.
|
||||
/// </summary>
|
||||
[NonSerialized]
|
||||
[DataField("eventTarget")]
|
||||
[DataField]
|
||||
public EntityUid? EventTarget;
|
||||
|
||||
public NetEntity? NetEventTarget;
|
||||
@@ -72,7 +83,7 @@ public sealed partial class DoAfterArgs
|
||||
/// <summary>
|
||||
/// Should the DoAfter event broadcast? If this is false, then <see cref="EventTarget"/> should be a valid entity.
|
||||
/// </summary>
|
||||
[DataField("broadcast")]
|
||||
[DataField]
|
||||
public bool Broadcast;
|
||||
#endregion
|
||||
|
||||
@@ -81,16 +92,24 @@ public sealed partial class DoAfterArgs
|
||||
/// <summary>
|
||||
/// Whether or not this do after requires the user to have hands.
|
||||
/// </summary>
|
||||
[DataField("needHand")]
|
||||
[DataField]
|
||||
public bool NeedHand;
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="NeedHand"/> is false.
|
||||
/// </summary>
|
||||
[DataField("breakOnHandChange")]
|
||||
[DataField]
|
||||
public bool BreakOnHandChange = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the do-after should get interrupted if we drop the
|
||||
/// active item we started the do-after with
|
||||
/// This does nothing if <see cref="NeedHand"/> is false.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool BreakOnDropItem = true;
|
||||
|
||||
/// <summary>
|
||||
/// If do_after stops when the user or target moves
|
||||
/// </summary>
|
||||
@@ -107,31 +126,31 @@ public sealed partial class DoAfterArgs
|
||||
/// <summary>
|
||||
/// Threshold for user and target movement
|
||||
/// </summary>
|
||||
[DataField("movementThreshold")]
|
||||
[DataField]
|
||||
public float MovementThreshold = 0.3f;
|
||||
|
||||
/// <summary>
|
||||
/// Threshold for distance user from the used OR target entities.
|
||||
/// </summary>
|
||||
[DataField("distanceThreshold")]
|
||||
[DataField]
|
||||
public float? DistanceThreshold;
|
||||
|
||||
/// <summary>
|
||||
/// Whether damage will cancel the DoAfter. See also <see cref="DamageThreshold"/>.
|
||||
/// </summary>
|
||||
[DataField("breakOnDamage")]
|
||||
[DataField]
|
||||
public bool BreakOnDamage;
|
||||
|
||||
/// <summary>
|
||||
/// Threshold for user damage. This damage has to be dealt in a single event, not over time.
|
||||
/// </summary>
|
||||
[DataField("damageThreshold")]
|
||||
[DataField]
|
||||
public FixedPoint2 DamageThreshold = 1;
|
||||
|
||||
/// <summary>
|
||||
/// If true, this DoAfter will be canceled if the user can no longer interact with the target.
|
||||
/// </summary>
|
||||
[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
|
||||
/// <see cref="CancelDuplicate"/> enabled.
|
||||
/// </remarks>
|
||||
[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
|
||||
/// <see cref="DuplicateConditions"/>.
|
||||
/// </summary>
|
||||
[DataField("cancelDuplicate")]
|
||||
[DataField]
|
||||
public bool CancelDuplicate = true;
|
||||
|
||||
/// <summary>
|
||||
@@ -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.
|
||||
/// </remarks>
|
||||
[DataField("duplicateCondition")]
|
||||
[DataField]
|
||||
public DuplicateConditions DuplicateCondition = DuplicateConditions.All;
|
||||
#endregion
|
||||
|
||||
@@ -184,6 +203,7 @@ public sealed partial class DoAfterArgs
|
||||
/// <param name="eventTarget">The entity at which the event will be directed. If null, the event will not be directed.</param>
|
||||
/// <param name="target">The entity being targeted by the DoAFter. Not the same as <see cref="EventTarget"/></param>.
|
||||
/// <param name="used">The entity being used during the DoAfter. E.g., a tool</param>
|
||||
/// <param name="showTo">Goobstation - The entity that should see doafter progress bar except doAfter entity</param>
|
||||
public DoAfterArgs(
|
||||
IEntityManager entManager,
|
||||
EntityUid user,
|
||||
@@ -191,7 +211,8 @@ public sealed partial class 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 sealed partial class DoAfterArgs
|
||||
Used = used;
|
||||
EventTarget = eventTarget;
|
||||
Event = @event;
|
||||
ShowTo = showTo; // Goobstation
|
||||
|
||||
NetUser = entManager.GetNetEntity(User);
|
||||
NetTarget = entManager.GetNetEntity(Target);
|
||||
NetUsed = entManager.GetNetEntity(Used);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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!
|
||||
/// </summary>
|
||||
[Obsolete("Use the other constructors if possible.")]
|
||||
public DoAfterArgs()
|
||||
{
|
||||
NetShowTo = entManager.GetNetEntity(ShowTo); // Goobstation - Show doAfter popup to another entity
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -248,6 +263,7 @@ public sealed partial class DoAfterArgs
|
||||
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 sealed partial class DoAfterArgs
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -130,6 +130,7 @@ public abstract partial class SharedDoAfterSystem : EntitySystem
|
||||
doAfterArgs.Used = EnsureEntity<DoAfterComponent>(doAfterArgs.NetUsed, uid);
|
||||
doAfterArgs.User = EnsureEntity<DoAfterComponent>(doAfterArgs.NetUser, uid);
|
||||
doAfterArgs.EventTarget = EnsureEntity<DoAfterComponent>(doAfterArgs.NetEventTarget, uid);
|
||||
doAfterArgs.ShowTo = EnsureEntity<DoAfterComponent>(doAfterArgs.NetShowTo, uid); // Goobstation - Show doAfter popup to another entity
|
||||
}
|
||||
|
||||
comp.NextId = state.NextId;
|
||||
|
||||
@@ -139,4 +139,17 @@ public partial class InventorySystem
|
||||
//Try insert into hands, or drop on the floor
|
||||
_handsSystem.PickupOrDrop(entity, itemToSpawn, false);
|
||||
}
|
||||
|
||||
// Goobstation
|
||||
public bool TryGetContainingEntity(Entity<TransformComponent?, MetaDataComponent?> entity, [NotNullWhen(true)] out EntityUid? containingEntity)
|
||||
{
|
||||
if (!_containerSystem.TryGetContainingContainer(entity, out var container) || !HasComp<InventoryComponent>(container.Owner))
|
||||
{
|
||||
containingEntity = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
containingEntity = container.Owner;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,20 @@ public sealed class ComponentTogglerSystem : EntitySystem
|
||||
|
||||
private void OnToggled(Entity<ComponentTogglerComponent> ent, ref ItemToggledEvent args)
|
||||
{
|
||||
var target = ent.Comp.Parent ? Transform(ent).ParentUid : ent.Owner;
|
||||
ToggleComponent(ent, args.Activated);
|
||||
}
|
||||
|
||||
if (args.Activated)
|
||||
EntityManager.AddComponents(target, ent.Comp.Components);
|
||||
// Goobstation - Make this system more flexible
|
||||
public void ToggleComponent(EntityUid uid, bool activate)
|
||||
{
|
||||
if (!TryComp<ComponentTogglerComponent>(uid, out var component))
|
||||
return;
|
||||
|
||||
var target = component.Parent ? Transform(uid).ParentUid : uid;
|
||||
|
||||
if (activate)
|
||||
EntityManager.AddComponents(target, component.Components);
|
||||
else
|
||||
EntityManager.RemoveComponents(target, ent.Comp.RemoveComponents ?? ent.Comp.Components);
|
||||
EntityManager.RemoveComponents(target, component.RemoveComponents ?? component.Components);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <see cref="SealableClothingControlComponent"/>
|
||||
[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");
|
||||
}
|
||||
@@ -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<NetEntity> 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
|
||||
}
|
||||
@@ -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<AlertPrototype> SuitPowerAlert = "ModsuitPower";
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._Goobstation.Clothing;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum SealableClothingVisuals : byte
|
||||
{
|
||||
Sealed
|
||||
}
|
||||
@@ -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<SealableClothingRequiresPowerComponent, MapInitEvent>(OnRequiresPowerMapInit);
|
||||
SubscribeLocalEvent<SealableClothingRequiresPowerComponent, ClothingSealAttemptEvent>(OnRequiresPowerSealAttempt);
|
||||
SubscribeLocalEvent<SealableClothingRequiresPowerComponent, AttemptChangePanelEvent>(OnRequiresPowerChangePanelAttempt);
|
||||
}
|
||||
|
||||
private void OnRequiresPowerMapInit(Entity<SealableClothingRequiresPowerComponent> 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<SealableClothingRequiresPowerComponent> 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<SealableClothingRequiresPowerComponent> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<SealableClothingComponent, ClothingPartSealCompleteEvent>(OnPartSealingComplete);
|
||||
|
||||
SubscribeLocalEvent<SealableClothingControlComponent, ClothingControlSealCompleteEvent>(OnControlSealingComplete);
|
||||
SubscribeLocalEvent<SealableClothingControlComponent, ClothingGotEquippedEvent>(OnControlEquip);
|
||||
SubscribeLocalEvent<SealableClothingControlComponent, ClothingGotUnequippedEvent>(OnControlUnequip);
|
||||
SubscribeLocalEvent<SealableClothingControlComponent, ComponentRemove>(OnControlRemove);
|
||||
SubscribeLocalEvent<SealableClothingControlComponent, GetItemActionsEvent>(OnControlGetItemActions);
|
||||
SubscribeLocalEvent<SealableClothingControlComponent, GetVerbsEvent<Verb>>(OnEquipmentVerb);
|
||||
SubscribeLocalEvent<SealableClothingControlComponent, MapInitEvent>(OnControlMapInit);
|
||||
SubscribeLocalEvent<SealableClothingControlComponent, SealClothingDoAfterEvent>(OnSealClothingDoAfter);
|
||||
SubscribeLocalEvent<SealableClothingControlComponent, SealClothingEvent>(OnControlSealEvent);
|
||||
//SubscribeLocalEvent<SealableClothingControlComponent, StartSealingProcessDoAfterEvent>(OnStartSealingDoAfter);
|
||||
SubscribeLocalEvent<SealableClothingControlComponent, ToggleClothingAttemptEvent>(OnToggleClothingAttempt);
|
||||
}
|
||||
|
||||
#region Events
|
||||
|
||||
/// Toggles components on part when suit complete sealing process
|
||||
private void OnPartSealingComplete(Entity<SealableClothingComponent> part, ref ClothingPartSealCompleteEvent args)
|
||||
=> _componentTogglerSystem.ToggleComponent(part, args.IsSealed);
|
||||
|
||||
/// Toggles components on control when suit complete sealing process
|
||||
private void OnControlSealingComplete(Entity<SealableClothingControlComponent> control, ref ClothingControlSealCompleteEvent args)
|
||||
=> _componentTogglerSystem.ToggleComponent(control, args.IsSealed);
|
||||
|
||||
/// Add/Remove wearer on clothing equip/unequip
|
||||
private void OnControlEquip(Entity<SealableClothingControlComponent> control, ref ClothingGotEquippedEvent args)
|
||||
{
|
||||
control.Comp.WearerEntity = args.Wearer;
|
||||
Dirty(control);
|
||||
}
|
||||
|
||||
private void OnControlUnequip(Entity<SealableClothingControlComponent> control, ref ClothingGotUnequippedEvent args)
|
||||
{
|
||||
control.Comp.WearerEntity = null;
|
||||
Dirty(control);
|
||||
}
|
||||
|
||||
/// Removes seal action on component remove
|
||||
private void OnControlRemove(Entity<SealableClothingControlComponent> 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<SealableClothingControlComponent> 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<SealableClothingControlComponent> control, ref GetVerbsEvent<Verb> 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<SealableClothingControlComponent> 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<SealableClothingControlComponent> 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<SealableClothingControlComponent> 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<SealableClothingControlComponent> control, ref SealClothingDoAfterEvent args)
|
||||
{
|
||||
var (uid, comp) = control;
|
||||
|
||||
if (args.Cancelled || args.Handled || args.Target == null)
|
||||
return;
|
||||
|
||||
var part = args.Target;
|
||||
|
||||
if (!TryComp<SealableClothingComponent>(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<SealableClothingControlComponent> 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<SealableClothingControlComponent> 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<SealableClothingComponent>(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<SealableClothingControlComponent> 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<SealableClothingComponent>(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;
|
||||
}
|
||||
}
|
||||
@@ -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<string, bool> Slots = new();
|
||||
}
|
||||
@@ -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<ItemSlotsRequirePanelComponent, ItemSlotInsertAttemptEvent>(ItemSlotInsertAttempt);
|
||||
SubscribeLocalEvent<ItemSlotsRequirePanelComponent, ItemSlotEjectAttemptEvent>(ItemSlotEjectAttempt);
|
||||
}
|
||||
|
||||
private void ItemSlotInsertAttempt(Entity<ItemSlotsRequirePanelComponent> entity, ref ItemSlotInsertAttemptEvent args)
|
||||
=> args.Cancelled = !CheckPanelStateForItemSlot(entity, args.Slot.ID);
|
||||
|
||||
private void ItemSlotEjectAttempt(Entity<ItemSlotsRequirePanelComponent> entity, ref ItemSlotEjectAttemptEvent args)
|
||||
=> args.Cancelled = !CheckPanelStateForItemSlot(entity, args.Slot.ID);
|
||||
|
||||
public bool CheckPanelStateForItemSlot(Entity<ItemSlotsRequirePanelComponent> 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<WiresPanelComponent>(uid, out var wiresPanel))
|
||||
return false;
|
||||
|
||||
return wiresPanel.Open == isRequireOpen;
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
BIN
Resources/Audio/Machines/scanbuzz.ogg
Normal file
4
Resources/Audio/_Goobstation/Machines/attributions.yml
Normal file
@@ -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"
|
||||
BIN
Resources/Audio/_Goobstation/Machines/computer_end.ogg
Normal file
4
Resources/Audio/_Goobstation/Mecha/attributions.yml
Normal file
@@ -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"
|
||||
BIN
Resources/Audio/_Goobstation/Mecha/nominal.ogg
Normal file
@@ -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.
|
||||
@@ -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!
|
||||
1
Resources/Locale/en-US/_Goobstation/forensics/fibers.ftl
Normal file
@@ -0,0 +1 @@
|
||||
fibers-modular = modular
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -46151,7 +46151,7 @@ entities:
|
||||
occludes: True
|
||||
ents:
|
||||
- 6158
|
||||
toggleable-clothing: !type:ContainerSlot
|
||||
toggleable-clothing: !type:Container
|
||||
showEnts: False
|
||||
occludes: True
|
||||
ent: null
|
||||
|
||||
@@ -4,31 +4,32 @@
|
||||
# If item is not in list it will go at the bottom (ties broken by alert type enum value)
|
||||
id: BaseAlertOrder
|
||||
order:
|
||||
- category: Health
|
||||
- category: Mood
|
||||
- category: Stamina
|
||||
- alertType: SuitPower
|
||||
- category: Internals
|
||||
- alertType: Fire
|
||||
- alertType: Handcuffed
|
||||
- alertType: Ensnared
|
||||
- category: Buckled
|
||||
- alertType: Pulling
|
||||
- category: Piloting
|
||||
- alertType: Corporeal
|
||||
- alertType: Stun
|
||||
- alertType: KnockedDown # WD EDIT
|
||||
- category: Breathing # Vox gang not calling this oxygen
|
||||
- category: Pressure
|
||||
- alertType: Bleed
|
||||
- category: Temperature
|
||||
- category: Hunger
|
||||
- category: Thirst
|
||||
- alertType: Magboots
|
||||
- alertType: Pacified
|
||||
- alertType: Offer
|
||||
- alertType: RecentlyBlocked # WD EDIT
|
||||
- alertType: Deflecting
|
||||
- category: Health
|
||||
- category: Mood
|
||||
- category: Stamina
|
||||
- alertType: SuitPower
|
||||
- alertType: ModsuitPower # Goobstation - Modsuits
|
||||
- category: Internals
|
||||
- alertType: Fire
|
||||
- alertType: Handcuffed
|
||||
- alertType: Ensnared
|
||||
- category: Buckled
|
||||
- alertType: Pulling
|
||||
- category: Piloting
|
||||
- alertType: Corporeal
|
||||
- alertType: Stun
|
||||
- alertType: KnockedDown # WD EDIT
|
||||
- category: Breathing # Vox gang not calling this oxygen
|
||||
- category: Pressure
|
||||
- alertType: Bleed
|
||||
- category: Temperature
|
||||
- category: Hunger
|
||||
- category: Thirst
|
||||
- alertType: Magboots
|
||||
- alertType: Pacified
|
||||
- alertType: Offer
|
||||
- alertType: Deflecting
|
||||
- alertType: RecentlyBlocked # WD EDIT
|
||||
|
||||
- type: entity
|
||||
id: AlertSpriteView
|
||||
@@ -51,8 +52,8 @@
|
||||
id: LowNitrogen
|
||||
category: Breathing
|
||||
icons:
|
||||
- sprite: /Textures/Interface/Alerts/breathing.rsi
|
||||
state: not_enough_nitro
|
||||
- sprite: /Textures/Interface/Alerts/breathing.rsi
|
||||
state: not_enough_nitro
|
||||
name: alerts-low-nitrogen-name
|
||||
description: alerts-low-nitrogen-desc
|
||||
|
||||
@@ -508,8 +509,8 @@
|
||||
category: Mood
|
||||
onClick: !type:ShowMoodEffects { }
|
||||
icons:
|
||||
- sprite: /Textures/Interface/Alerts/mood.rsi
|
||||
state: mood_insane
|
||||
- sprite: /Textures/Interface/Alerts/mood.rsi
|
||||
state: mood_insane
|
||||
name: alerts-mood-insane-name
|
||||
description: alerts-mood-insane-desc
|
||||
|
||||
@@ -518,8 +519,8 @@
|
||||
category: Mood
|
||||
onClick: !type:ShowMoodEffects { }
|
||||
icons:
|
||||
- sprite: /Textures/Interface/Alerts/mood.rsi
|
||||
state: mood1
|
||||
- sprite: /Textures/Interface/Alerts/mood.rsi
|
||||
state: mood1
|
||||
name: alerts-mood-horrible-name
|
||||
description: alerts-mood-horrible-desc
|
||||
|
||||
@@ -528,8 +529,8 @@
|
||||
category: Mood
|
||||
onClick: !type:ShowMoodEffects { }
|
||||
icons:
|
||||
- sprite: /Textures/Interface/Alerts/mood.rsi
|
||||
state: mood2
|
||||
- sprite: /Textures/Interface/Alerts/mood.rsi
|
||||
state: mood2
|
||||
name: alerts-mood-terrible-name
|
||||
description: alerts-mood-terrible-desc
|
||||
|
||||
@@ -538,8 +539,8 @@
|
||||
category: Mood
|
||||
onClick: !type:ShowMoodEffects { }
|
||||
icons:
|
||||
- sprite: /Textures/Interface/Alerts/mood.rsi
|
||||
state: mood3
|
||||
- sprite: /Textures/Interface/Alerts/mood.rsi
|
||||
state: mood3
|
||||
name: alerts-mood-bad-name
|
||||
description: alerts-mood-bad-desc
|
||||
|
||||
@@ -548,8 +549,8 @@
|
||||
category: Mood
|
||||
onClick: !type:ShowMoodEffects { }
|
||||
icons:
|
||||
- sprite: /Textures/Interface/Alerts/mood.rsi
|
||||
state: mood4
|
||||
- sprite: /Textures/Interface/Alerts/mood.rsi
|
||||
state: mood4
|
||||
name: alerts-mood-meh-name
|
||||
description: alerts-mood-meh-desc
|
||||
|
||||
@@ -558,8 +559,8 @@
|
||||
category: Mood
|
||||
onClick: !type:ShowMoodEffects { }
|
||||
icons:
|
||||
- sprite: /Textures/Interface/Alerts/mood.rsi
|
||||
state: mood5
|
||||
- sprite: /Textures/Interface/Alerts/mood.rsi
|
||||
state: mood5
|
||||
name: alerts-mood-neutral-name
|
||||
description: alerts-mood-neutral-desc
|
||||
|
||||
@@ -568,8 +569,8 @@
|
||||
category: Mood
|
||||
onClick: !type:ShowMoodEffects { }
|
||||
icons:
|
||||
- sprite: /Textures/Interface/Alerts/mood.rsi
|
||||
state: mood6
|
||||
- sprite: /Textures/Interface/Alerts/mood.rsi
|
||||
state: mood6
|
||||
name: alerts-mood-good-name
|
||||
description: alerts-mood-good-desc
|
||||
|
||||
@@ -578,8 +579,8 @@
|
||||
category: Mood
|
||||
onClick: !type:ShowMoodEffects { }
|
||||
icons:
|
||||
- sprite: /Textures/Interface/Alerts/mood.rsi
|
||||
state: mood7
|
||||
- sprite: /Textures/Interface/Alerts/mood.rsi
|
||||
state: mood7
|
||||
name: alerts-mood-great-name
|
||||
description: alerts-mood-great-desc
|
||||
|
||||
@@ -588,8 +589,8 @@
|
||||
category: Mood
|
||||
onClick: !type:ShowMoodEffects { }
|
||||
icons:
|
||||
- sprite: /Textures/Interface/Alerts/mood.rsi
|
||||
state: mood8
|
||||
- sprite: /Textures/Interface/Alerts/mood.rsi
|
||||
state: mood8
|
||||
name: alerts-mood-exceptional-name
|
||||
description: alerts-mood-exceptional-desc
|
||||
|
||||
@@ -598,8 +599,8 @@
|
||||
category: Mood
|
||||
onClick: !type:ShowMoodEffects { }
|
||||
icons:
|
||||
- sprite: /Textures/Interface/Alerts/mood.rsi
|
||||
state: mood9
|
||||
- sprite: /Textures/Interface/Alerts/mood.rsi
|
||||
state: mood9
|
||||
name: alerts-mood-perfect-name
|
||||
description: alerts-mood-perfect-desc
|
||||
|
||||
@@ -608,8 +609,8 @@
|
||||
category: Mood
|
||||
onClick: !type:ShowMoodEffects { }
|
||||
icons:
|
||||
- sprite: /Textures/Interface/Alerts/mood.rsi
|
||||
state: mood_happiness_bad
|
||||
- sprite: /Textures/Interface/Alerts/mood.rsi
|
||||
state: mood_happiness_bad
|
||||
name: alerts-mood-dead-name
|
||||
description: alerts-mood-dead-desc
|
||||
|
||||
|
||||
@@ -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 {}
|
||||
toggleable-clothing: !type:Container {}
|
||||
|
||||
@@ -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: []
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
slot: head
|
||||
- type: ContainerContainer
|
||||
containers:
|
||||
toggleable-clothing: !type:ContainerSlot {}
|
||||
toggleable-clothing: !type:Container {}
|
||||
|
||||
#Prisoner EVA
|
||||
- type: entity
|
||||
|
||||
@@ -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
|
||||
sprite: Clothing/OuterClothing/Suits/witchrobe.rsi
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
slot: head
|
||||
- type: ContainerContainer
|
||||
containers:
|
||||
toggleable-clothing: !type:ContainerSlot {}
|
||||
toggleable-clothing: !type:Container {}
|
||||
storagebase: !type:Container
|
||||
ents: []
|
||||
|
||||
|
||||
@@ -687,6 +687,8 @@
|
||||
- TorsoBorgService
|
||||
- MechAirTank # Goobstation
|
||||
- MechThruster # Goobstation
|
||||
- PowerCageMedium # Goobstation - Powercell to exosuit fab
|
||||
- PowerCageSmall # Goobstation - Powercell to exosuit fab
|
||||
dynamicRecipes:
|
||||
- ProximitySensor
|
||||
- BorgModuleLightReplacer
|
||||
@@ -762,6 +764,14 @@
|
||||
- MechEquipmentKineticAccelerator
|
||||
- MechEquipmentHonkerBananaMortar
|
||||
- MechEquipmentHonkerMousetrapMortar
|
||||
# Goobstation - Modsuits
|
||||
- ModsuitChestplate
|
||||
- ModsuitBoots
|
||||
- ModsuitHelmet
|
||||
- ModsuitGauntlets
|
||||
- ModsuitShell
|
||||
- ModsuitPlatingExternal
|
||||
- PowerCageHigh # Goobstation - Powercell to exosuit fab
|
||||
- type: EmagLatheRecipes
|
||||
emagDynamicRecipes:
|
||||
- WeaponMechCombatImmolationGun
|
||||
|
||||
@@ -246,6 +246,7 @@
|
||||
components:
|
||||
- BorgChassis
|
||||
- Silicon # Parkstation IPCs
|
||||
- Inventory # Goobstation - Modsuits
|
||||
- type: Construction
|
||||
containers:
|
||||
- machine_parts
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
clothingPrototype: ClothingHeadHatHoodCultHoodTrue
|
||||
- type: ContainerContainer
|
||||
containers:
|
||||
toggleable-clothing: !type:ContainerSlot { }
|
||||
toggleable-clothing: !type:Container { }
|
||||
|
||||
- type: entity
|
||||
parent: ClothingHeadHatHoodCulthood
|
||||
|
||||
20
Resources/Prototypes/_Goobstation/Actions/clothing.yml
Normal file
@@ -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 {}
|
||||
19
Resources/Prototypes/_Goobstation/Alerts/alerts.yml
Normal file
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -38,3 +38,7 @@
|
||||
- type: latheCategory
|
||||
id: MechWeapons
|
||||
name: lathe-category-mechs-weapons
|
||||
|
||||
- type: latheCategory
|
||||
id: Modsuit
|
||||
name: lathe-category-modsuit
|
||||
|
||||
@@ -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
|
||||
19
Resources/Prototypes/_Goobstation/Research/experimental.yml
Normal file
@@ -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
|
||||
@@ -96,7 +96,7 @@
|
||||
|
||||
- type: Tag
|
||||
id: RipleyMkII
|
||||
|
||||
|
||||
- type: Tag
|
||||
id: Clarke
|
||||
|
||||
@@ -104,4 +104,30 @@
|
||||
id: Durand
|
||||
|
||||
- type: Tag
|
||||
id: Gygax
|
||||
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
|
||||
|
||||
|
After Width: | Height: | Size: 811 B |
BIN
Resources/Textures/_Goobstation/Actions/modsuit.rsi/activate.png
Normal file
|
After Width: | Height: | Size: 252 B |
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 390 B |
|
After Width: | Height: | Size: 238 B |
|
After Width: | Height: | Size: 392 B |
|
After Width: | Height: | Size: 673 B |
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 145 B |
|
After Width: | Height: | Size: 496 B |
|
After Width: | Height: | Size: 277 B |
|
After Width: | Height: | Size: 270 B |
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 763 B |
|
After Width: | Height: | Size: 474 B |
|
After Width: | Height: | Size: 315 B |
|
After Width: | Height: | Size: 365 B |
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 110 B |
|
After Width: | Height: | Size: 428 B |
|
After Width: | Height: | Size: 130 B |
|
After Width: | Height: | Size: 1.1 KiB |
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 109 B |
|
After Width: | Height: | Size: 307 B |
|
After Width: | Height: | Size: 131 B |
|
After Width: | Height: | Size: 501 B |
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 324 B |