Skip to content

Commit

Permalink
MODsuits (Port From Goob #1242) (#1640)
Browse files Browse the repository at this point in the history
# Description

Ports MODsuits from Goobstation PR
Goob-Station/Goob-Station#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

:cl:
- add: Modsuits have been ported from Goobstation!

---------

Signed-off-by: Eris <[email protected]>
Co-authored-by: DEATHB4DEFEAT <[email protected]>
Co-authored-by: VMSolidus <[email protected]>
  • Loading branch information
3 people authored Jan 25, 2025
1 parent f81925a commit cb06c41
Show file tree
Hide file tree
Showing 120 changed files with 2,704 additions and 184 deletions.
3 changes: 2 additions & 1 deletion Content.Client/DoAfter/DoAfterOverlay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ protected override void Draw(in OverlayDrawArgs args)
var alpha = 1f;
if (doAfter.Args.Hidden)
{
if (uid != localEnt)
// Goobstation - Show doAfter progress bar to another entity
if (uid != localEnt && localEnt != doAfter.Args.ShowTo)
continue;

// Hints to the local player that this do-after is not visible to other players.
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using Content.Shared._Goobstation.Clothing.Systems;

namespace Content.Client._Goobstation.Clothing.EntitySystems;

public sealed class PoweredSealableClothingSystem : SharedPoweredSealableClothingSystem
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using Content.Shared._Goobstation.Clothing.Systems;

namespace Content.Client._Goobstation.Clothing.EntitySystems;

public sealed partial class SealableClothingSystem : SharedSealableClothingSystem
{
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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; }
}
33 changes: 30 additions & 3 deletions Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,27 @@ public sealed class BarotraumaSystem : EntitySystem
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
[Dependency] private readonly IAdminLogManager _adminLogger= default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;

[Dependency] private readonly ILogManager _logManager = default!;

private const float UpdateTimer = 1f;

private ISawmill _sawmill = default!;
private float _timer;

public override void Initialize()
{
SubscribeLocalEvent<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)
Expand All @@ -51,6 +57,27 @@ private void OnPressureImmuneRemove(EntityUid uid, PressureImmunityComponent pre
}
}

// Goobstation - Modsuits - Update component state on toggle
private void OnPressureProtectionChanged(EntityUid uid, PressureProtectionComponent pressureProtection, EntityEventArgs args)
{
var protectionTarget = uid;
string? slotTarget = null;

if (_inventorySystem.TryGetContainingEntity(uid, out var entity) && _inventorySystem.TryGetContainingSlot(uid, out var slot))
{
protectionTarget = entity.Value;
slotTarget = slot.Name;
}

if (!TryComp<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>
Expand Down
6 changes: 6 additions & 0 deletions Content.Server/Body/Components/LungComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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?
}
42 changes: 34 additions & 8 deletions Content.Server/Body/Systems/LungSystem.cs
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
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";

public override void Initialize()
{
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);
Expand Down Expand Up @@ -50,16 +56,36 @@ private void OnGotEquipped(Entity<BreathToolComponent> ent, ref GotEquippedEvent

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
{
Expand Down
Loading

0 comments on commit cb06c41

Please sign in to comment.