From de95d851a3f2c909a8d1dc1dde3f554fdae7057d Mon Sep 17 00:00:00 2001 From: gluesniffler Date: Sat, 2 Nov 2024 03:37:24 -0400 Subject: [PATCH] Added some experimental lag fixes to surgery. Might have crashes --- Content.Client/Backmen/Surgery/SurgeryBui.cs | 75 ++++++----------- .../Backmen/Surgery/SurgerySystem.cs | 14 +++- .../Backmen/Surgery/SurgeryWindow.xaml | 7 -- Content.Client/Body/Systems/BodySystem.cs | 13 +++ .../Backmen/Surgery/SurgerySystem.cs | 19 ++++- Content.Server/Body/Systems/BodySystem.cs | 5 ++ .../Body/SharedBodySystem.Integrity.cs | 4 +- .../Surgery/SharedSurgerySystem.Steps.cs | 4 +- .../Backmen/Surgery/SharedSurgerySystem.cs | 21 +++-- .../Backmen/Surgery/SurgeryUiRefreshEvent.cs | 14 ++++ .../Body/Part/BodyPartAppearanceComponent.cs | 27 ++++++ .../Body/Systems/SharedBodySystem.Body.cs | 16 +++- .../SharedBodySystem.PartAppearance.cs | 83 +++++++++++++++++++ .../Body/Systems/SharedBodySystem.Parts.cs | 11 +++ .../en-US/backmen/surgery/surgery-ui.ftl | 2 +- .../Entities/Structures/Machines/lathe.yml | 1 + .../_Backmen/Recipes/Lathes/surgery.yml | 2 +- 17 files changed, 244 insertions(+), 74 deletions(-) create mode 100644 Content.Shared/Backmen/Surgery/SurgeryUiRefreshEvent.cs create mode 100644 Content.Shared/Body/Part/BodyPartAppearanceComponent.cs create mode 100644 Content.Shared/Body/Systems/SharedBodySystem.PartAppearance.cs diff --git a/Content.Client/Backmen/Surgery/SurgeryBui.cs b/Content.Client/Backmen/Surgery/SurgeryBui.cs index f00505576b9..625bb00e0f3 100644 --- a/Content.Client/Backmen/Surgery/SurgeryBui.cs +++ b/Content.Client/Backmen/Surgery/SurgeryBui.cs @@ -3,12 +3,16 @@ using Content.Shared.Backmen.Surgery; using Content.Shared.Body.Components; using Content.Shared.Body.Part; +using Content.Shared.Rotation; +using Content.Shared.Standing; +using Content.Client.Hands.Systems; using JetBrains.Annotations; using Robust.Client.GameObjects; using Robust.Client.Player; using Robust.Shared.Prototypes; using Robust.Shared.Utility; using Robust.Shared.Timing; +using Robust.Client.Timing; using static Robust.Client.UserInterface.Control; namespace Content.Client.Backmen.Surgery; @@ -19,10 +23,11 @@ public sealed class SurgeryBui : BoundUserInterface [Dependency] private readonly IEntityManager _entities = default!; [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IClientGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _timing = default!; private readonly SurgerySystem _system; - + private readonly HandsSystem _hands; [ViewVariables] private SurgeryWindow? _window; @@ -30,23 +35,30 @@ public sealed class SurgeryBui : BoundUserInterface private bool _isBody = false; private (EntityUid Ent, EntProtoId Proto)? _surgery; private readonly List _previousSurgeries = new(); - + private DateTime _lastRefresh = DateTime.UtcNow; + private (string handName, EntityUid item) _throttling = ("", new EntityUid()); public SurgeryBui(EntityUid owner, Enum uiKey) : base(owner, uiKey) { _system = _entities.System(); + _hands = _entities.System(); + + _system.OnStep += RefreshUI; + _hands.OnPlayerItemAdded += OnPlayerItemAdded; } - protected override void Open() + private void OnPlayerItemAdded(string handName, EntityUid item) { - //Logger.Debug("Attempting to open"); - _system.OnRefresh += () => - { - UpdateDisabledPanel(); - RefreshUI(); - }; + if (_throttling.handName.Equals(handName) + && _throttling.item.Equals(item) + && DateTime.UtcNow - _lastRefresh < TimeSpan.FromSeconds(0.2) + || !_timing.IsFirstTimePredicted + || _window == null + || !_window.IsOpen) + return; - if (State is SurgeryBuiState s) - Update(s); + _throttling = (handName, item); + _lastRefresh = DateTime.UtcNow; + RefreshUI(); } protected override void UpdateState(BoundUserInterfaceState state) @@ -62,7 +74,7 @@ protected override void Dispose(bool disposing) if (disposing) _window?.Dispose(); - _system.OnRefresh -= RefreshUI; + _system.OnStep -= RefreshUI; } private void Update(SurgeryBuiState state) @@ -71,7 +83,7 @@ private void Update(SurgeryBuiState state) if (!_entities.TryGetComponent(_player.LocalEntity, out var surgeryTargetComp) || !surgeryTargetComp.CanOperate) return; - //Logger.Debug("Passed check"); + if (_window == null) { _window = new SurgeryWindow(); @@ -126,7 +138,6 @@ State is not SurgeryBuiState s || _window.Surgeries.DisposeAllChildren(); _window.Steps.DisposeAllChildren(); _window.Parts.DisposeAllChildren(); - View(ViewType.Parts); var oldSurgery = _surgery; @@ -194,16 +205,8 @@ int GetScore(BodyPartType? partType) if (!_window.IsOpen) - { //Logger.Debug("Attempting to open"); _window.OpenCentered(); - } - else - { - //Logger.Debug("Attempting to refresh"); - RefreshUI(); - UpdateDisabledPanel(); - } } private void AddStep(EntProtoId stepId, NetEntity netPart, EntProtoId surgeryId) @@ -308,7 +311,6 @@ private void OnPartPressed(NetEntity netPart, List surgeryIds) private void RefreshUI() { if (_window == null - || !_timing.IsFirstTimePredicted || !_window.IsOpen || _part == null || !_entities.HasComponent(_surgery?.Ent) @@ -317,7 +319,7 @@ private void RefreshUI() { return; } - + Logger.Debug($"Running RefreshUI on {Owner}"); var next = _system.GetNextStep(Owner, _part.Value, _surgery.Value.Ent); var i = 0; foreach (var child in _window.Steps.Children) @@ -355,9 +357,6 @@ private void RefreshUI() else { stepButton.Button.Modulate = Color.White; - // GOD THIS NEEDS A REWRITE SO BADLY, IT UPDATES ON EVERY SINGLE TICK - // THEN RUNS CANPERFORMSTEP WHICH CALLS A SHITLOAD OF EVENTS - // DID THEY NOT FUCKING PLAYTEST THIS??? if (_player.LocalEntity is { } player && status == StepStatus.Next && !_system.CanPerformStep(player, Owner, _part.Value, stepButton.Step, false, out var popup, out var reason, out _)) @@ -387,28 +386,6 @@ private void RefreshUI() stepButton.Set(stepName, texture); i++; } - - UpdateDisabledPanel(); - } - - private void UpdateDisabledPanel() - { - if (_window == null) - return; - - if (_system.IsLyingDown(Owner)) - { - _window.DisabledPanel.Visible = false; - _window.DisabledPanel.MouseFilter = MouseFilterMode.Ignore; - return; - } - - _window.DisabledPanel.Visible = true; - - var text = new FormattedMessage(); - text.AddMarkup($"[color=red][font size=16]{Loc.GetString("surgery-ui-window-steps-error-laying")}[/font][/color]"); - _window.DisabledLabel.SetMessage(text); - _window.DisabledPanel.MouseFilter = MouseFilterMode.Stop; } private void View(ViewType type) diff --git a/Content.Client/Backmen/Surgery/SurgerySystem.cs b/Content.Client/Backmen/Surgery/SurgerySystem.cs index d972ca87ad8..aeb3358dea3 100644 --- a/Content.Client/Backmen/Surgery/SurgerySystem.cs +++ b/Content.Client/Backmen/Surgery/SurgerySystem.cs @@ -1,13 +1,21 @@ using Content.Shared.Backmen.Surgery; +using Content.Shared.Medical.Surgery; namespace Content.Client.Backmen.Surgery; public sealed class SurgerySystem : SharedSurgerySystem { - public event Action? OnRefresh; + public event Action? OnStep; - public override void Update(float frameTime) + public override void Initialize() { - OnRefresh?.Invoke(); + base.Initialize(); + + SubscribeNetworkEvent(OnRefresh); + } + + private void OnRefresh(SurgeryUiRefreshEvent ev) + { + OnStep?.Invoke(); } } diff --git a/Content.Client/Backmen/Surgery/SurgeryWindow.xaml b/Content.Client/Backmen/Surgery/SurgeryWindow.xaml index 103ce02232e..b6b7ac9a2b7 100644 --- a/Content.Client/Backmen/Surgery/SurgeryWindow.xaml +++ b/Content.Client/Backmen/Surgery/SurgeryWindow.xaml @@ -20,11 +20,4 @@ - - - - - - diff --git a/Content.Client/Body/Systems/BodySystem.cs b/Content.Client/Body/Systems/BodySystem.cs index bab785525b0..52af9d93493 100644 --- a/Content.Client/Body/Systems/BodySystem.cs +++ b/Content.Client/Body/Systems/BodySystem.cs @@ -1,7 +1,20 @@ using Content.Shared.Body.Systems; +using Content.Shared.Body.Part; +using Robust.Client.GameObjects; namespace Content.Client.Body.Systems; public sealed class BodySystem : SharedBodySystem { + protected override void UpdateAppearance(EntityUid uid, BodyPartAppearanceComponent component) + { + if (TryComp(uid, out SpriteComponent? sprite)) + { + if (component.Color != null) + { + //TODO a few things need to be adjusted before this is ready to be used - also need to find a way to update the player sprite + //sprite.Color = component.Color.Value; + } + } + } } diff --git a/Content.Server/Backmen/Surgery/SurgerySystem.cs b/Content.Server/Backmen/Surgery/SurgerySystem.cs index 5aaf3bad7c4..02046afaf71 100644 --- a/Content.Server/Backmen/Surgery/SurgerySystem.cs +++ b/Content.Server/Backmen/Surgery/SurgerySystem.cs @@ -1,11 +1,15 @@ using Content.Shared.Bed.Sleep; +using Content.Shared.Body.Components; using Content.Server.Body.Systems; using Content.Shared.Body.Organ; using Content.Shared.Body.Part; using Content.Server.Chat.Systems; using Content.Server.Popups; using Content.Shared.Damage; +using Content.Shared.Damage.Prototypes; using Content.Shared.Interaction; +using Content.Shared.Inventory; +using Content.Shared.Medical.Surgery.Steps; using Content.Shared.Medical.Surgery.Conditions; using Content.Shared.Medical.Surgery.Effects.Step; using Content.Server.Atmos.Rotting; @@ -14,6 +18,7 @@ using Content.Shared.Prototypes; using Robust.Server.GameObjects; using Robust.Shared.Configuration; +using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Utility; using System.Linq; @@ -75,6 +80,18 @@ protected override void RefreshUI(EntityUid body) } //Logger.Debug($"Setting UI state with {surgeries}, {body} and {SurgeryUIKey.Key}"); _ui.SetUiState(body, SurgeryUIKey.Key, new SurgeryBuiState(surgeries)); + /* + Reason we do this is because when applying a BUI State, it rolls back the state on the entity temporarily, + which just so happens to occur right as we're checking for step completion, so we end up with the UI + not updating at all until you change tools or reopen the window. + */ + + var actors = _ui.GetActors(body, SurgeryUIKey.Key).ToArray(); + if (actors.Length == 0) + return; + + var filter = Filter.Entities(actors); + RaiseNetworkEvent(new Shared.Medical.Surgery.SurgeryUiRefreshEvent(GetNetEntity(body)), filter); } private void SetDamage(EntityUid body, @@ -174,7 +191,7 @@ private void OnStepAffixPartComplete(Entity var ev = new BodyPartEnableChangedEvent(true); RaiseLocalEvent(targetPart.Id, ref ev); // This is basically an equalizer, severing a part will badly damage it. - // and affixing it will heal it a bit if its not too badly damaged. + // and affixing it will heal it a bit if it's not too badly damaged. _body.TryChangeIntegrity(targetPart, targetPart.Component.Integrity - BodyPartComponent.IntegrityAffixPart, false, _body.GetTargetBodyPart(targetPart.Component.PartType, targetPart.Component.Symmetry), out _); } diff --git a/Content.Server/Body/Systems/BodySystem.cs b/Content.Server/Body/Systems/BodySystem.cs index c1302a72d9d..a4202deec29 100644 --- a/Content.Server/Body/Systems/BodySystem.cs +++ b/Content.Server/Body/Systems/BodySystem.cs @@ -162,4 +162,9 @@ public override HashSet GibPart( return gibs; } + + protected override void UpdateAppearance(EntityUid uid, BodyPartAppearanceComponent component) + { + return; + } } diff --git a/Content.Shared/Backmen/Surgery/Body/SharedBodySystem.Integrity.cs b/Content.Shared/Backmen/Surgery/Body/SharedBodySystem.Integrity.cs index f6d281b2c65..1bef8f86115 100644 --- a/Content.Shared/Backmen/Surgery/Body/SharedBodySystem.Integrity.cs +++ b/Content.Shared/Backmen/Surgery/Body/SharedBodySystem.Integrity.cs @@ -140,12 +140,15 @@ public void TryChangeIntegrity(Entity partEnt, out bool severed) { severed = false; + if (!_timing.IsFirstTimePredicted || !_queryTargeting.HasComp(partEnt.Comp.Body)) return; var partIdSlot = GetParentPartAndSlotOrNull(partEnt)?.Slot; var originalIntegrity = partEnt.Comp.Integrity; partEnt.Comp.Integrity = Math.Min(BodyPartComponent.MaxIntegrity, partEnt.Comp.Integrity - integrity); + + // This will also prevent the torso from being removed. if (canSever && !HasComp(partEnt) && !partEnt.Comp.Enabled @@ -153,7 +156,6 @@ public void TryChangeIntegrity(Entity partEnt, && partIdSlot is not null) severed = true; - // This will also prevent the torso from being removed. if (partEnt.Comp.Enabled && partEnt.Comp.Integrity <= BodyPartComponent.CritIntegrity) { diff --git a/Content.Shared/Backmen/Surgery/SharedSurgerySystem.Steps.cs b/Content.Shared/Backmen/Surgery/SharedSurgerySystem.Steps.cs index e7fae4a9883..4efc9459934 100644 --- a/Content.Shared/Backmen/Surgery/SharedSurgerySystem.Steps.cs +++ b/Content.Shared/Backmen/Surgery/SharedSurgerySystem.Steps.cs @@ -75,7 +75,6 @@ private void OnToolStep(Entity ent, ref SurgeryStepEvent a var compType = reg.Component.GetType(); if (HasComp(args.Part, compType)) continue; - AddComp(args.Part, _compFactory.GetComponent(compType)); } } @@ -409,7 +408,7 @@ private void OnSurgeryTargetStepChosen(Entity ent, ref S var user = args.Actor; if (GetEntity(args.Entity) is not { Valid: true } body || GetEntity(args.Part) is not { Valid: true } targetPart || - !IsSurgeryValid(body, targetPart, args.Surgery, args.Step, out var surgery, out var part, out var step)) + !IsSurgeryValid(body, targetPart, args.Surgery, args.Step, user, out var surgery, out var part, out var step)) { return; } @@ -565,7 +564,6 @@ public bool IsStepComplete(EntityUid body, EntityUid part, EntProtoId step, Enti var ev = new SurgeryStepCompleteCheckEvent(body, part, surgery); RaiseLocalEvent(stepEnt, ref ev); - return !ev.Cancelled; } diff --git a/Content.Shared/Backmen/Surgery/SharedSurgerySystem.cs b/Content.Shared/Backmen/Surgery/SharedSurgerySystem.cs index ab73ad54e1f..10ce5b6b810 100644 --- a/Content.Shared/Backmen/Surgery/SharedSurgerySystem.cs +++ b/Content.Shared/Backmen/Surgery/SharedSurgerySystem.cs @@ -71,16 +71,20 @@ private void OnRoundRestartCleanup(RoundRestartCleanupEvent ev) private void OnTargetDoAfter(Entity ent, ref SurgeryDoAfterEvent args) { + if (!_timing.IsFirstTimePredicted) + return; + if (args.Cancelled || args.Handled || args.Target is not { } target || - !IsSurgeryValid(ent, target, args.Surgery, args.Step, out var surgery, out var part, out var step) || + !IsSurgeryValid(ent, target, args.Surgery, args.Step, args.User, out var surgery, out var part, out var step) || !PreviousStepsComplete(ent, part, surgery, args.Step) || !CanPerformStep(args.User, ent, part, step, false)) { Log.Warning($"{ToPrettyString(args.User)} tried to start invalid surgery."); return; } + args.Repeat = HasComp(step); var ev = new SurgeryStepEvent(args.User, ent, part, GetTools(args.User), surgery); RaiseLocalEvent(step, ref ev); @@ -182,21 +186,22 @@ private void OnPartPresentConditionValid(Entity surgeryEnt, out EntityUid part, out EntityUid step) + EntityUid user, out Entity surgeryEnt, out EntityUid part, out EntityUid step) { surgeryEnt = default; part = default; step = default; + if (!HasComp(body) || - !IsLyingDown(body) || + !IsLyingDown(body, user) || GetSingleton(surgery) is not { } surgeryEntId || !TryComp(surgeryEntId, out SurgeryComponent? surgeryComp) || !surgeryComp.Steps.Contains(stepId) || - GetSingleton(stepId) is not { } stepEnt) + GetSingleton(stepId) is not { } stepEnt + || !HasComp(targetPart) + && !HasComp(targetPart)) return false; - if (!HasComp(targetPart) && !HasComp(targetPart)) - return false; var ev = new SurgeryValidEvent(body, targetPart); if (_timing.IsFirstTimePredicted) @@ -236,7 +241,7 @@ private List GetTools(EntityUid surgeon) return _hands.EnumerateHeld(surgeon).ToList(); } - public bool IsLyingDown(EntityUid entity) + public bool IsLyingDown(EntityUid entity, EntityUid user) { if (_standing.IsDown(entity)) return true; @@ -249,6 +254,8 @@ public bool IsLyingDown(EntityUid entity) return true; } + _popup.PopupEntity(Loc.GetString("surgery-error-laying"), user, user); + return false; } diff --git a/Content.Shared/Backmen/Surgery/SurgeryUiRefreshEvent.cs b/Content.Shared/Backmen/Surgery/SurgeryUiRefreshEvent.cs new file mode 100644 index 00000000000..9d41401d7f8 --- /dev/null +++ b/Content.Shared/Backmen/Surgery/SurgeryUiRefreshEvent.cs @@ -0,0 +1,14 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Medical.Surgery; + +[Serializable, NetSerializable] +public sealed class SurgeryUiRefreshEvent : EntityEventArgs +{ + public NetEntity Uid { get; } + + public SurgeryUiRefreshEvent(NetEntity uid) + { + Uid = uid; + } +} diff --git a/Content.Shared/Body/Part/BodyPartAppearanceComponent.cs b/Content.Shared/Body/Part/BodyPartAppearanceComponent.cs new file mode 100644 index 00000000000..5aa5d533554 --- /dev/null +++ b/Content.Shared/Body/Part/BodyPartAppearanceComponent.cs @@ -0,0 +1,27 @@ +using Content.Shared.Humanoid.Prototypes; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Body.Part; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class BodyPartAppearanceComponent : Component +{ + + /// + /// ID of this custom base layer. Must be a . + /// + [IdDataField, AutoNetworkedField] + public ProtoId? ID { get; set; } + + /// + /// Color of this custom base layer. Null implies skin colour if the corresponding is set to match skin. + /// + [DataField, AutoNetworkedField] + public Color? Color { get; set; } + + [DataField, AutoNetworkedField] + public EntityUid? OriginalBody { get; set; } + + //TODO add other custom variables such as species and markings - in case someone decides to attach a lizard arm to a human for example +} diff --git a/Content.Shared/Body/Systems/SharedBodySystem.Body.cs b/Content.Shared/Body/Systems/SharedBodySystem.Body.cs index 666c01de423..ccd705af147 100644 --- a/Content.Shared/Body/Systems/SharedBodySystem.Body.cs +++ b/Content.Shared/Body/Systems/SharedBodySystem.Body.cs @@ -10,6 +10,7 @@ using Content.Shared.Gibbing.Components; using Content.Shared.Gibbing.Events; using Content.Shared.Gibbing.Systems; +using Content.Shared.Humanoid; using Content.Shared.Inventory; using Content.Shared.Rejuvenate; using Content.Shared.Standing; @@ -19,7 +20,6 @@ using Robust.Shared.Map; using Robust.Shared.Utility; using Robust.Shared.Timing; - namespace Content.Shared.Body.Systems; public partial class SharedBodySystem @@ -221,6 +221,20 @@ private void MapInitParts(EntityUid rootPartId, BodyPrototype prototype) continue; } + if (TryComp(parentPartComponent.Body, out HumanoidAppearanceComponent? bodyAppearance)) + { + var appearance = AddComp(childPart); + appearance.OriginalBody = childPartComponent.OriginalBody; + appearance.Color = bodyAppearance.SkinColor; + + var symmetry = ((BodyPartSymmetry) childPartComponent.Symmetry).ToString(); + if (symmetry == "None") + symmetry = ""; + appearance.ID = "removed" + symmetry + ((BodyPartType) childPartComponent.PartType).ToString(); + + Dirty(childPart, appearance); + } + // Add organs SetupOrgans((childPart, childPartComponent), connectionSlot.Organs); diff --git a/Content.Shared/Body/Systems/SharedBodySystem.PartAppearance.cs b/Content.Shared/Body/Systems/SharedBodySystem.PartAppearance.cs new file mode 100644 index 00000000000..930da554379 --- /dev/null +++ b/Content.Shared/Body/Systems/SharedBodySystem.PartAppearance.cs @@ -0,0 +1,83 @@ +using Content.Shared.Body.Components; +using Content.Shared.Body.Part; +using Content.Shared.Body.Events; +using Robust.Shared.GameStates; +using Content.Shared.Humanoid; +using Content.Shared.Humanoid.Prototypes; + +namespace Content.Shared.Body.Systems; +// Code shamelessly stolen from MS14. +public partial class SharedBodySystem +{ + private void InitializePartAppearances() + { + base.Initialize(); + + SubscribeLocalEvent(OnPartAppearanceInit); + SubscribeLocalEvent(OnPartAddedToBody); + } + + private static readonly Dictionary<(BodyPartType, BodyPartSymmetry), HumanoidVisualLayers> BodyPartVisualLayers + = new Dictionary<(BodyPartType, BodyPartSymmetry), HumanoidVisualLayers> + { + { (BodyPartType.Head,BodyPartSymmetry.None), HumanoidVisualLayers.Head }, + { (BodyPartType.Tail,BodyPartSymmetry.None), HumanoidVisualLayers.Tail }, + { (BodyPartType.Torso,BodyPartSymmetry.None), HumanoidVisualLayers.Chest }, + { (BodyPartType.Arm,BodyPartSymmetry.Right), HumanoidVisualLayers.RArm }, + { (BodyPartType.Arm,BodyPartSymmetry.Left), HumanoidVisualLayers.LArm }, + { (BodyPartType.Hand,BodyPartSymmetry.Right), HumanoidVisualLayers.RHand }, + { (BodyPartType.Hand,BodyPartSymmetry.Left), HumanoidVisualLayers.LHand }, + { (BodyPartType.Leg,BodyPartSymmetry.Right), HumanoidVisualLayers.RLeg }, + { (BodyPartType.Leg,BodyPartSymmetry.Left), HumanoidVisualLayers.LLeg }, + { (BodyPartType.Foot,BodyPartSymmetry.Right), HumanoidVisualLayers.RLeg }, + { (BodyPartType.Foot,BodyPartSymmetry.Left), HumanoidVisualLayers.LLeg } + }; + + private void OnPartAppearanceInit(EntityUid uid, BodyPartAppearanceComponent component, ComponentInit args) + { + + if (TryComp(uid, out BodyPartComponent? part) && part.OriginalBody != null && + TryComp(part.OriginalBody.Value, out HumanoidAppearanceComponent? bodyAppearance)) + { + var customLayers = bodyAppearance.CustomBaseLayers; + var spriteLayers = bodyAppearance.BaseLayers; + var visualLayer = BodyPartVisualLayers[(part.PartType, part.Symmetry)]; + + component.OriginalBody = part.OriginalBody.Value; + + if (customLayers.ContainsKey(visualLayer)) + { + component.ID = customLayers[visualLayer].Id; + component.Color = customLayers[visualLayer].Color; + } + else if (spriteLayers.ContainsKey(visualLayer)) + { + component.ID = spriteLayers[visualLayer].ID; + component.Color = bodyAppearance.SkinColor; + } + else + { + var symmetry = ((BodyPartSymmetry) part.Symmetry).ToString(); + if (symmetry == "None") + symmetry = ""; + component.ID = "removed" + symmetry + ((BodyPartType) part.PartType).ToString(); + component.Color = bodyAppearance.SkinColor; + } + } + Dirty(uid, component); + UpdateAppearance(uid, component); + } + + public void OnPartAddedToBody(EntityUid uid, BodyPartAppearanceComponent component, ref BodyPartAddedEvent args) + { + if (TryComp(uid, out HumanoidAppearanceComponent? bodyAppearance)) + { + var part = args.Part; + var customLayers = bodyAppearance.CustomBaseLayers; + var visualLayer = BodyPartVisualLayers[(part.Comp.PartType, part.Comp.Symmetry)]; + customLayers[visualLayer] = new CustomBaseLayerInfo(component.ID, customLayers[visualLayer].Color); + } + } + + protected abstract void UpdateAppearance(EntityUid uid, BodyPartAppearanceComponent component); +} diff --git a/Content.Shared/Body/Systems/SharedBodySystem.Parts.cs b/Content.Shared/Body/Systems/SharedBodySystem.Parts.cs index 7f59a2a45c5..32316043409 100644 --- a/Content.Shared/Body/Systems/SharedBodySystem.Parts.cs +++ b/Content.Shared/Body/Systems/SharedBodySystem.Parts.cs @@ -5,6 +5,7 @@ using Content.Shared.Damage; using Content.Shared.Damage.Prototypes; using Content.Shared.Hands.EntitySystems; +using Content.Shared.Humanoid; using Content.Shared.Inventory; using Content.Shared.Movement.Components; using Content.Shared.Random; @@ -607,6 +608,16 @@ public bool AttachPart( } part.ParentSlot = slot; + + if (TryComp(part.Body, out HumanoidAppearanceComponent? bodyAppearance) + && !HasComp(partId)) + { + var appearance = AddComp(partId); + appearance.OriginalBody = part.Body; + appearance.Color = bodyAppearance.SkinColor; + UpdateAppearance(partId, appearance); + } + return Containers.Insert(partId, container); } diff --git a/Resources/Locale/en-US/backmen/surgery/surgery-ui.ftl b/Resources/Locale/en-US/backmen/surgery/surgery-ui.ftl index 51fd9392c45..fd1c45fcb47 100644 --- a/Resources/Locale/en-US/backmen/surgery/surgery-ui.ftl +++ b/Resources/Locale/en-US/backmen/surgery/surgery-ui.ftl @@ -7,5 +7,5 @@ surgery-ui-window-steps-error-skills = You have no surgical skills. surgery-ui-window-steps-error-table = You need an operating table for this. surgery-ui-window-steps-error-armor = You need to remove their armor! surgery-ui-window-steps-error-tools = You're missing tools for this surgery. -surgery-ui-window-steps-error-laying = They need to be laying down! +surgery-error-laying = They need to be laying down! surgery-error-self-surgery = You can't perform surgery on yourself! diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index b5b3f770e57..2831b257690 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -934,6 +934,7 @@ - MedkitRadiation - MedkitCombat - Scalpel + - BoneGel - Retractor - Cautery - Drill diff --git a/Resources/Prototypes/_Backmen/Recipes/Lathes/surgery.yml b/Resources/Prototypes/_Backmen/Recipes/Lathes/surgery.yml index 3195e494ede..75def0ca802 100644 --- a/Resources/Prototypes/_Backmen/Recipes/Lathes/surgery.yml +++ b/Resources/Prototypes/_Backmen/Recipes/Lathes/surgery.yml @@ -1,7 +1,7 @@ - type: latheRecipe id: BoneGel result: BoneGel - completetime: 10 + completetime: 2 materials: Plastic: 200 Plasma: 200