Procházet zdrojové kódy

Goobmed (#127)

Adds goobmed system with limb targeting and surgery.
Taislin před 7 měsíci
rodič
revize
a36c666d33
100 změnil soubory, kde provedl 5865 přidání a 297 odebrání
  1. 79 1
      Content.Client/Body/Systems/BodySystem.cs
  2. 64 7
      Content.Client/Hands/Systems/HandsSystem.cs
  3. 17 1
      Content.Client/HealthAnalyzer/UI/HealthAnalyzerBoundUserInterface.cs
  4. 219 25
      Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml
  5. 115 8
      Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs
  6. 30 68
      Content.Client/Humanoid/HumanoidAppearanceSystem.cs
  7. 12 0
      Content.Client/Input/ContentContexts.cs
  8. 18 1
      Content.Client/MedBook/UI/MedBookBoundUserInterface.cs
  9. 172 24
      Content.Client/MedBook/UI/MedBookWindow.xaml
  10. 113 8
      Content.Client/MedBook/UI/MedBookWindow.xaml.cs
  11. 13 0
      Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs
  12. 54 0
      Content.Client/Stylesheets/StyleNano.cs
  13. 28 10
      Content.Client/UserInterface/Screens/DefaultGameScreen.xaml
  14. 1 1
      Content.Client/UserInterface/Screens/DefaultGameScreen.xaml.cs
  15. 42 14
      Content.Client/UserInterface/Screens/SeparatedChatGameScreen.xaml
  16. 16 3
      Content.Client/UserInterface/Systems/Alerts/Widgets/AlertsUI.xaml
  17. 2 2
      Content.Client/UserInterface/Systems/Inventory/Controls/ItemStatusPanel.xaml.cs
  18. 22 0
      Content.Client/_Shitmed/Autodoc/AddStepWindow.xaml
  19. 144 0
      Content.Client/_Shitmed/Autodoc/AddStepWindow.xaml.cs
  20. 49 0
      Content.Client/_Shitmed/Autodoc/AutodocBoundUserInterface.cs
  21. 29 0
      Content.Client/_Shitmed/Autodoc/AutodocProgramWindow.xaml
  22. 210 0
      Content.Client/_Shitmed/Autodoc/AutodocProgramWindow.xaml.cs
  23. 24 0
      Content.Client/_Shitmed/Autodoc/AutodocWindow.xaml
  24. 231 0
      Content.Client/_Shitmed/Autodoc/AutodocWindow.xaml.cs
  25. 28 0
      Content.Client/_Shitmed/Autodoc/PickSurgeryWindow.xaml
  26. 154 0
      Content.Client/_Shitmed/Autodoc/PickSurgeryWindow.xaml.cs
  27. 11 0
      Content.Client/_Shitmed/Autodoc/Systems/AutodocSystem.cs
  28. 8 0
      Content.Client/_Shitmed/Body/Components/BrainComponent.cs
  29. 8 0
      Content.Client/_Shitmed/Body/Components/LungComponent.cs
  30. 8 0
      Content.Client/_Shitmed/Body/Components/StomachComponent.cs
  31. 25 0
      Content.Client/_Shitmed/Choice/UI/ChoiceControl.xaml
  32. 32 0
      Content.Client/_Shitmed/Choice/UI/ChoiceControl.xaml.cs
  33. 362 0
      Content.Client/_Shitmed/Medical/Surgery/SurgeryBui.cs
  34. 11 0
      Content.Client/_Shitmed/Medical/Surgery/SurgeryStepButton.xaml
  35. 22 0
      Content.Client/_Shitmed/Medical/Surgery/SurgeryStepButton.xaml.cs
  36. 16 0
      Content.Client/_Shitmed/Medical/Surgery/SurgerySystem.cs
  37. 30 0
      Content.Client/_Shitmed/Medical/Surgery/SurgeryWindow.xaml
  38. 19 0
      Content.Client/_Shitmed/Medical/Surgery/SurgeryWindow.xaml.cs
  39. 107 0
      Content.Client/_Shitmed/Targeting/TargetingSystem.cs
  40. 87 0
      Content.Client/_Shitmed/UserInterface/Systems/PartStatus/PartStatusUIController.cs
  41. 64 0
      Content.Client/_Shitmed/UserInterface/Systems/PartStatus/Widgets/PartStatusControl.xaml
  42. 54 0
      Content.Client/_Shitmed/UserInterface/Systems/PartStatus/Widgets/PartStatusControl.xaml.cs
  43. 87 0
      Content.Client/_Shitmed/UserInterface/Systems/Targeting/TargetingUIController.cs
  44. 223 0
      Content.Client/_Shitmed/UserInterface/Systems/Targeting/Widgets/TargetingControl.xaml
  45. 63 0
      Content.Client/_Shitmed/UserInterface/Systems/Targeting/Widgets/TargetingControl.xaml.cs
  46. 4 4
      Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs
  47. 5 1
      Content.Server/Body/Commands/AddHandCommand.cs
  48. 8 1
      Content.Server/Body/Commands/AttachBodyPartCommand.cs
  49. 5 0
      Content.Server/Body/Components/BrainComponent.cs
  50. 85 7
      Content.Server/Body/Systems/BodySystem.cs
  51. 61 3
      Content.Server/Body/Systems/BrainSystem.cs
  52. 11 1
      Content.Server/Body/Systems/MetabolizerSystem.cs
  53. 6 5
      Content.Server/Body/Systems/RespiratorSystem.cs
  54. 15 0
      Content.Server/Damage/Components/DamageUserOnTriggerComponent.cs
  55. 2 2
      Content.Server/Damage/Systems/DamageUserOnTriggerSystem.cs
  56. 3 1
      Content.Server/Destructible/Thresholds/Behaviors/BurnBodyBehavior.cs
  57. 5 1
      Content.Server/Destructible/Thresholds/Behaviors/GibBehavior.cs
  58. 9 1
      Content.Server/EntityEffects/Effects/HealthChange.cs
  59. 2 2
      Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs
  60. 39 12
      Content.Server/Hands/Systems/HandsSystem.cs
  61. 17 0
      Content.Server/Kitchen/Components/SharpComponent.cs
  62. 5 0
      Content.Server/Medical/Components/HealthAnalyzerComponent.cs
  63. 5 0
      Content.Server/Medical/Components/MedBookComponent.cs
  64. 9 3
      Content.Server/Medical/CryoPodSystem.cs
  65. 24 6
      Content.Server/Medical/HealingSystem.cs
  66. 65 7
      Content.Server/Medical/HealthAnalyzerSystem.cs
  67. 5 1
      Content.Server/Medical/InsideCryoPodSystem.cs
  68. 62 8
      Content.Server/Medical/MedBookSystem.cs
  69. 7 1
      Content.Server/Spawners/EntitySystems/SpawnerSystem.cs
  70. 7 7
      Content.Server/Wires/WiresSystem.cs
  71. 43 0
      Content.Server/_Shitmed/Autodoc/AutodocSafetyWireAction.cs
  72. 57 0
      Content.Server/_Shitmed/Autodoc/Systems/AutodocSystem.cs
  73. 57 0
      Content.Server/_Shitmed/Body/BodyEffects/Subsystems/RandomStatusActivationSystem.cs
  74. 43 0
      Content.Server/_Shitmed/Body/Organ/HeartSystem.cs
  75. 32 0
      Content.Server/_Shitmed/Body/Organ/StatusEffectOrganComponent.cs
  76. 39 0
      Content.Server/_Shitmed/Body/Organ/StatusEffectOrganSystem.cs
  77. 31 0
      Content.Server/_Shitmed/Body/Systems/BodySystem.VitalOrgans.cs
  78. 67 0
      Content.Server/_Shitmed/Body/Systems/DebrainedSystem.cs
  79. 91 0
      Content.Server/_Shitmed/Body/Systems/EyesSystem.cs
  80. 60 0
      Content.Server/_Shitmed/Cybernetics/CyberneticsSystem.cs
  81. 21 0
      Content.Server/_Shitmed/DelayedDeath/DelayedDeathComponent.cs
  82. 55 0
      Content.Server/_Shitmed/DelayedDeath/DelayedDeathSystem.cs
  83. 25 0
      Content.Server/_Shitmed/Destructible/Thresholds/Behaviors/GibPartBehavior.cs
  84. 71 0
      Content.Server/_Shitmed/Medical/Surgery/GhettoSurgerySystem.cs
  85. 172 0
      Content.Server/_Shitmed/Medical/Surgery/SurgerySystem.cs
  86. 15 0
      Content.Server/_Shitmed/Objectives/Components/RoleplayObjectiveComponent.cs
  87. 28 0
      Content.Server/_Shitmed/Objectives/Systems/RoleplayObjectiveSystem.cs
  88. 42 0
      Content.Server/_Shitmed/OnHit/OnHitSystem.cs
  89. 28 0
      Content.Server/_Shitmed/StatusEffects/ActivateArtifactEffectSystem.cs
  90. 33 0
      Content.Server/_Shitmed/StatusEffects/ExpelGasSystem.cs
  91. 53 0
      Content.Server/_Shitmed/StatusEffects/ScrambleDnaEffectSystem.cs
  92. 53 0
      Content.Server/_Shitmed/StatusEffects/SpawnEntityEffectSystem.cs
  93. 60 0
      Content.Server/_Shitmed/Targeting/TargetingSystem.cs
  94. 7 2
      Content.Shared/Bed/Sleep/SleepingSystem.cs
  95. 59 2
      Content.Shared/Body/Organ/OrganComponent.cs
  96. 165 5
      Content.Shared/Body/Part/BodyPartComponent.cs
  97. 206 9
      Content.Shared/Body/Systems/SharedBodySystem.Body.cs
  98. 189 1
      Content.Shared/Body/Systems/SharedBodySystem.Organs.cs
  99. 450 30
      Content.Shared/Body/Systems/SharedBodySystem.Parts.cs
  100. 24 1
      Content.Shared/Body/Systems/SharedBodySystem.cs

+ 79 - 1
Content.Client/Body/Systems/BodySystem.cs

@@ -1,7 +1,85 @@
-using Content.Shared.Body.Systems;
+// SPDX-FileCopyrightText: 2022 DrSmugleaf <DrSmugleaf@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2022 Jezithyr <Jezithyr@gmail.com>
+// SPDX-FileCopyrightText: 2022 metalgearsloth <metalgearsloth@gmail.com>
+// SPDX-FileCopyrightText: 2023 metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Shared.Body.Systems;
+// Shitmed Change Start
+using Content.Shared._Shitmed.Body.Part;
+using Content.Shared.Humanoid;
+using Content.Shared.Humanoid.Markings;
+using Robust.Client.GameObjects;
+using Robust.Shared.Utility;
+// Shitmed Change End
 
 namespace Content.Client.Body.Systems;
 
 public sealed class BodySystem : SharedBodySystem
 {
+    // Shitmed Change Start
+    [Dependency] private readonly MarkingManager _markingManager = default!;
+
+    private void ApplyMarkingToPart(MarkingPrototype markingPrototype,
+        IReadOnlyList<Color>? colors,
+        bool visible,
+        SpriteComponent sprite)
+    {
+        for (var j = 0; j < markingPrototype.Sprites.Count; j++)
+        {
+            var markingSprite = markingPrototype.Sprites[j];
+
+            if (markingSprite is not SpriteSpecifier.Rsi rsi)
+                continue;
+
+            var layerId = $"{markingPrototype.ID}-{rsi.RsiState}";
+
+            if (!sprite.LayerMapTryGet(layerId, out _))
+            {
+                var layer = sprite.AddLayer(markingSprite, j + 1);
+                sprite.LayerMapSet(layerId, layer);
+                sprite.LayerSetSprite(layerId, rsi);
+            }
+
+            sprite.LayerSetVisible(layerId, visible);
+
+            if (!visible)
+                continue;
+
+            // Okay so if the marking prototype is modified but we load old marking data this may no longer be valid
+            // and we need to check the index is correct. So if that happens just default to white?
+            if (colors != null && j < colors.Count)
+                sprite.LayerSetColor(layerId, colors[j]);
+            else
+                sprite.LayerSetColor(layerId, Color.White);
+        }
+    }
+
+    protected override void ApplyPartMarkings(EntityUid target, BodyPartAppearanceComponent component)
+    {
+        if (!TryComp(target, out SpriteComponent? sprite))
+            return;
+
+        if (component.Color != null)
+            sprite.Color = component.Color.Value;
+
+        foreach (var (visualLayer, markingList) in component.Markings)
+            foreach (var marking in markingList)
+            {
+                if (!_markingManager.TryGetMarking(marking, out var markingPrototype))
+                    continue;
+
+                ApplyMarkingToPart(markingPrototype, marking.MarkingColors, marking.Visible, sprite);
+            }
+    }
+
+    protected override void RemoveBodyMarkings(EntityUid target, BodyPartAppearanceComponent partAppearance, HumanoidAppearanceComponent bodyAppearance)
+    {
+        return;
+    }
+    // Shitmed Change End
 }

+ 64 - 7
Content.Client/Hands/Systems/HandsSystem.cs

@@ -1,9 +1,41 @@
+// SPDX-FileCopyrightText: 2021 Swept <sweptwastaken@protonmail.com>
+// SPDX-FileCopyrightText: 2021 Vera Aguilera Puerto <gradientvera@outlook.com>
+// SPDX-FileCopyrightText: 2022 Flipp Syder <76629141+vulppine@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2022 Jacob Tong <10494922+ShadowCommander@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2022 Jezithyr <Jezithyr.@gmail.com>
+// SPDX-FileCopyrightText: 2022 Jezithyr <Jezithyr@gmail.com>
+// SPDX-FileCopyrightText: 2022 Jezithyr <jmaster9999@gmail.com>
+// SPDX-FileCopyrightText: 2022 Paul <ritter.paul1@googlemail.com>
+// SPDX-FileCopyrightText: 2022 Paul Ritter <ritter.paul1@googlemail.com>
+// SPDX-FileCopyrightText: 2022 ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2022 Visne <39844191+Visne@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2022 wrexbe <81056464+wrexbe@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2022 wrexbe <wrexbe@protonmail.com>
+// SPDX-FileCopyrightText: 2023 DrSmugleaf <DrSmugleaf@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2023 Kara <lunarautomaton6@gmail.com>
+// SPDX-FileCopyrightText: 2023 Menshin <Menshin@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2023 Nemanja <98561806+EmoGarbage404@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2023 metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 0x6273 <0x40@keemail.me>
+// SPDX-FileCopyrightText: 2024 AJCM-git <60196617+AJCM-git@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Ed <96445749+TheShuEd@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Leon Friedrich <60421075+ElectroJr@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
+// SPDX-FileCopyrightText: 2024 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 metalgearsloth <comedian_vs_clown@hotmail.com>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
+using Content.Shared._Shitmed.Body.Events; // Shitmed Change
 using Content.Client.DisplacementMap;
 using Content.Client.Examine;
 using Content.Client.Strip;
 using Content.Client.Verbs.UI;
+using Content.Shared.Body.Part; // Shitmed Change
 using Content.Shared.Hands;
 using Content.Shared.Hands.Components;
 using Content.Shared.Hands.EntitySystems;
@@ -16,7 +48,6 @@
 using Robust.Shared.Containers;
 using Robust.Shared.GameStates;
 using Robust.Shared.Player;
-using Robust.Shared.Timing;
 
 namespace Content.Client.Hands.Systems
 {
@@ -51,6 +82,8 @@ public override void Initialize()
             SubscribeLocalEvent<HandsComponent, ComponentShutdown>(OnHandsShutdown);
             SubscribeLocalEvent<HandsComponent, ComponentHandleState>(HandleComponentState);
             SubscribeLocalEvent<HandsComponent, VisualsChangedEvent>(OnVisualsChanged);
+            SubscribeLocalEvent<HandsComponent, BodyPartRemovedEvent>(HandleBodyPartRemoved); // Shitmed Change
+            SubscribeLocalEvent<HandsComponent, BodyPartDisabledEvent>(HandleBodyPartDisabled); // Shitmed Change
 
             OnHandSetActive += OnHandActivated;
         }
@@ -240,6 +273,35 @@ public void UIHandAltActivateItem(string handName)
 
         #region visuals
 
+        // Shitmed Change Start
+        private void HideLayers(EntityUid uid, HandsComponent component, Entity<BodyPartComponent> part, SpriteComponent? sprite = null)
+        {
+            if (part.Comp.PartType != BodyPartType.Hand || !Resolve(uid, ref sprite, logMissing: false))
+                return;
+
+            var location = part.Comp.Symmetry switch
+            {
+                BodyPartSymmetry.None => HandLocation.Middle,
+                BodyPartSymmetry.Left => HandLocation.Left,
+                BodyPartSymmetry.Right => HandLocation.Right,
+                _ => throw new ArgumentOutOfRangeException(nameof(part.Comp.Symmetry))
+            };
+
+            if (component.RevealedLayers.TryGetValue(location, out var revealedLayers))
+            {
+                foreach (var key in revealedLayers)
+                    sprite.RemoveLayer(key);
+
+                revealedLayers.Clear();
+            }
+        }
+
+        private void HandleBodyPartRemoved(EntityUid uid, HandsComponent component, ref BodyPartRemovedEvent args) => HideLayers(uid, component, args.Part);
+
+        private void HandleBodyPartDisabled(EntityUid uid, HandsComponent component, ref BodyPartDisabledEvent args) => HideLayers(uid, component, args.Part);
+
+        // Shitmed Change End
+
         protected override void HandleEntityInserted(EntityUid uid, HandsComponent hands, EntInsertedIntoContainerMessage args)
         {
             base.HandleEntityInserted(uid, hands, args);
@@ -349,12 +411,7 @@ private void UpdateHandVisuals(EntityUid uid, EntityUid held, Hand hand, HandsCo
                 sprite.LayerSetData(index, layerData);
 
                 //Add displacement maps
-                if (hand.Location == HandLocation.Left && handComp.LeftHandDisplacement is not null)
-                    _displacement.TryAddDisplacement(handComp.LeftHandDisplacement, sprite, index, key, revealedLayers);
-                else if (hand.Location == HandLocation.Right && handComp.RightHandDisplacement is not null)
-                    _displacement.TryAddDisplacement(handComp.RightHandDisplacement, sprite, index, key, revealedLayers);
-                //Fallback to default displacement map
-                else if (handComp.HandDisplacement is not null)
+                if (handComp.HandDisplacement is not null)
                     _displacement.TryAddDisplacement(handComp.HandDisplacement, sprite, index, key, revealedLayers);
             }
 

+ 17 - 1
Content.Client/HealthAnalyzer/UI/HealthAnalyzerBoundUserInterface.cs

@@ -1,6 +1,7 @@
 using Content.Shared.MedicalScanner;
 using JetBrains.Annotations;
 using Robust.Client.UserInterface;
+using Content.Shared._Shitmed.Targeting; // Shitmed Change
 
 namespace Content.Client.HealthAnalyzer.UI
 {
@@ -19,7 +20,7 @@ protected override void Open()
             base.Open();
 
             _window = this.CreateWindow<HealthAnalyzerWindow>();
-
+            _window.OnBodyPartSelected += SendBodyPartMessage; // Shitmed Change
             _window.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
         }
 
@@ -33,5 +34,20 @@ protected override void ReceiveMessage(BoundUserInterfaceMessage message)
 
             _window.Populate(cast);
         }
+        // Shitmed Change Start
+        private void SendBodyPartMessage(TargetBodyPart? part, EntityUid target) => SendMessage(new HealthAnalyzerPartMessage(EntMan.GetNetEntity(target), part ?? null));
+        protected override void Dispose(bool disposing)
+        {
+            base.Dispose(disposing);
+            if (!disposing)
+                return;
+
+            if (_window != null)
+                _window.OnBodyPartSelected -= SendBodyPartMessage;
+
+            _window?.Dispose();
+        }
+
+        // Shitmed Change End
     }
 }

+ 219 - 25
Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml

@@ -1,8 +1,29 @@
+<!--
+SPDX-FileCopyrightText: 2021 Visne <39844191+Visne@users.noreply.github.com>
+SPDX-FileCopyrightText: 2022 Fishfish458 <47410468+Fishfish458@users.noreply.github.com>
+SPDX-FileCopyrightText: 2022 Paul Ritter <ritter.paul1@googlemail.com>
+SPDX-FileCopyrightText: 2022 fishfish458 <fishfish458>
+SPDX-FileCopyrightText: 2022 wrexbe <81056464+wrexbe@users.noreply.github.com>
+SPDX-FileCopyrightText: 2023 Artjom <artjombebenin@gmail.com>
+SPDX-FileCopyrightText: 2024 Piras314 <p1r4s@proton.me>
+SPDX-FileCopyrightText: 2024 Rainfey <rainfey0+github@gmail.com>
+SPDX-FileCopyrightText: 2024 Saphire Lattice <lattice@saphi.re>
+SPDX-FileCopyrightText: 2024 Thomas <87614336+Aeshus@users.noreply.github.com>
+SPDX-FileCopyrightText: 2024 Whisper <121047731+QuietlyWhisper@users.noreply.github.com>
+SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+SPDX-FileCopyrightText: 2024 goet <6637097+goet@users.noreply.github.com>
+SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+SPDX-FileCopyrightText: 2025 Tobias Berger <toby@tobot.dev>
+
+SPDX-License-Identifier: AGPL-3.0-or-later
+-->
+
 <controls:FancyWindow
     xmlns="https://spacestation14.io"
     xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
     MaxHeight="525"
-    MinWidth="300">
+    MinWidth="350">
+    <!-- Shitmed Change -->
     <ScrollContainer
         Margin="5 5 5 5"
         ReturnMeasure="True"
@@ -13,45 +34,218 @@
             Orientation="Vertical">
             <Label
                 Name="NoPatientDataText"
-                Text="{Loc health-analyzer-window-no-patient-data-text}" />
-
+                Text="{Loc health-analyzer-window-no-patient-data-text}"/>
+            <!-- Shitmed Change Start -->
+            <Button Name="ReturnButton"
+                    Text="{Loc 'health-analyzer-window-return-button-text'}"
+                    Margin="0 0 0 10"
+                    HorizontalExpand="False"/>
+            <!-- Shitmed Change End -->
             <BoxContainer
                 Name="PatientDataContainer"
                 Margin="0 0 0 5"
                 Orientation="Vertical">
-                <BoxContainer Orientation="Horizontal" Margin="0 0 0 5">
-                    <SpriteView OverrideDirection="South" Scale="2 2" Name="SpriteView" Access="Public" SetSize="64 64" />
-                    <TextureRect Name="NoDataTex" Access="Public" SetSize="64 64" Visible="false" Stretch="KeepAspectCentered" TexturePath="/Textures/Interface/Misc/health_analyzer_out_of_range.png"/>
-                    <BoxContainer Margin="5 0 0 0" Orientation="Vertical" VerticalAlignment="Top">
-                        <RichTextLabel Name="NameLabel" SetWidth="150" />
-                        <Label Name="SpeciesLabel" VerticalAlignment="Top" StyleClasses="LabelSubText" />
+                <!-- Shitmed Change Start -->
+                <BoxContainer Orientation="Horizontal"
+                              Margin="0 0 0 5">
+                    <PanelContainer>
+                        <SpriteView OverrideDirection="South"
+                                    Name="SpriteView"
+                                    Access="Public"
+                                    SetSize="96 96"/>
+                        <PanelContainer
+                            Name="PartView"
+                            SetSize="57 96"
+                            Margin="18 0 0 0"
+                            VerticalAlignment="Center"
+                            HorizontalAlignment="Left">
+                            <PanelContainer
+                                SetSize="15 33"
+                                Margin="4 0 0 0"
+                                HorizontalAlignment="Left">
+                                <TextureButton
+                                    Name="RightArmButton"
+                                    MinSize="15 25"
+                                    StyleClasses="TargetDollButtonRightArm"
+                                    VerticalAlignment="Top">
+                                </TextureButton>
+                                <TextureButton
+                                    Name="RightHandButton"
+                                    MinSize="15 15"
+                                    VerticalAlignment="Bottom"
+                                    StyleClasses="TargetDollButtonRightHand">
+                                </TextureButton>
+                            </PanelContainer>
+                            <PanelContainer
+                                SetSize="43 75"
+                                Margin="0 0 0 0"
+                                VerticalAlignment="Center"
+                                HorizontalAlignment="Center">
+                                <TextureButton
+                                    Name="HeadButton"
+                                    MinSize="28 23"
+                                    VerticalAlignment="Top"
+                                    HorizontalAlignment="Center"
+                                    StyleClasses="TargetDollButtonHead">
+                                    <!--<PanelContainer
+                            SetSize="15 15"
+                            Margin="0 9 0 0"
+                            HorizontalAlignment="Center">
+                            <TextureButton
+                                Name="EyesButton"
+                                MinSize="15 9"
+                                VerticalAlignment="Top"
+                                StyleClasses="TargetDollButtonEyes">
+                                <TextureRect
+                                    TexturePath="/Textures/Interface/Targeting/Doll/eyes.png"
+                                    Stretch="KeepAspectCentered"
+                                    SetSize="15 9"/>
+                            </TextureButton>
+                            <TextureButton
+                                Name="MouthButton"
+                                SetSize="9 6"
+                                VerticalAlignment="Bottom"
+                                StyleClasses="TargetDollButtonMouth">
+                                <TextureRect
+                                    TexturePath="/Textures/Interface/Targeting/Doll/mouth.png"
+                                    Stretch="KeepAspectCentered"
+                                    SetSize="9 6"/>
+                            </TextureButton>
+                        </PanelContainer>-->
+                                </TextureButton>
+                                <TextureButton
+                                    Name="ChestButton"
+                                    SetSize="28 30"
+                                    Margin="0 18 0 0"
+                                    VerticalAlignment="Top"
+                                    HorizontalAlignment="Center"
+                                    StyleClasses="TargetDollButtonChest">
+                                </TextureButton>
+                                <PanelContainer
+                                    MinSize="38 35"
+                                    VerticalAlignment="Bottom"
+                                    HorizontalAlignment="Center">
+                                    <TextureButton
+                                        Name="GroinButton"
+                                        MinSize="28 15"
+                                        VerticalAlignment="Top"
+                                        HorizontalAlignment="Center"
+                                        StyleClasses="TargetDollButtonGroin">
+                                    </TextureButton>
+                                    <PanelContainer
+                                        MinSize="20 30"
+                                        VerticalAlignment="Bottom"
+                                        HorizontalAlignment="Right">
+                                        <TextureButton
+                                            Name="LeftLegButton"
+                                            MinSize="15 28"
+                                            VerticalAlignment="Top"
+                                            HorizontalAlignment="Left"
+                                            StyleClasses="TargetDollButtonLeftLeg">
+                                        </TextureButton>
+                                        <TextureButton
+                                            Name="LeftFootButton"
+                                            MinSize="20 10"
+                                            VerticalAlignment="Bottom"
+                                            StyleClasses="TargetDollButtonLeftFoot">
+                                        </TextureButton>
+                                    </PanelContainer>
+                                    <PanelContainer
+                                        MinSize="20 30"
+                                        VerticalAlignment="Bottom"
+                                        HorizontalAlignment="Left">
+                                        <TextureButton
+                                            Name="RightLegButton"
+                                            MinSize="15 28"
+                                            VerticalAlignment="Top"
+                                            HorizontalAlignment="Right"
+                                            StyleClasses="TargetDollButtonRightLeg">
+                                        </TextureButton>
+                                        <TextureButton
+                                            Name="RightFootButton"
+                                            MinSize="20 10"
+                                            VerticalAlignment="Bottom"
+                                            HorizontalAlignment="Center"
+                                            StyleClasses="TargetDollButtonRightFoot">
+                                        </TextureButton>
+                                    </PanelContainer>
+                                </PanelContainer>
+                            </PanelContainer>
+                            <PanelContainer
+                                SetSize="15 33"
+                                Margin="0 0 4 0"
+                                HorizontalAlignment="Right">
+                                <TextureButton
+                                    Name="LeftArmButton"
+                                    MinSize="15 25"
+                                    StyleClasses="TargetDollButtonLeftArm"
+                                    VerticalAlignment="Top">
+                                </TextureButton>
+                                <TextureButton
+                                    Name="LeftHandButton"
+                                    MinSize="15 15"
+                                    VerticalAlignment="Bottom"
+                                    StyleClasses="TargetDollButtonLeftHand">
+                                </TextureButton>
+                            </PanelContainer>
+                        </PanelContainer>
+                    </PanelContainer>
+                    <TextureRect Name="NoDataTex"
+                                 Access="Public"
+                                 SetSize="64 64"
+                                 Visible="false"
+                                 Stretch="KeepAspectCentered"
+                                 TexturePath="/Textures/Interface/Misc/health_analyzer_out_of_range.png"/>
+                    <BoxContainer Margin="5 0 0 0"
+                                  Orientation="Vertical"
+                                  VerticalAlignment="Top">
+                        <RichTextLabel Name="NameLabel"
+                                       SetWidth="150"/>
+                        <Label Name="SpeciesLabel"
+                               VerticalAlignment="Top"
+                               StyleClasses="LabelSubText"/>
+                        <Label Name="PartNameLabel"
+                               VerticalAlignment="Bottom"
+                               StyleClasses="LabelSubText"/>
                     </BoxContainer>
-                    <Label Margin="0 0 5 0" HorizontalExpand="True" HorizontalAlignment="Right" VerticalExpand="True"
-                           VerticalAlignment="Top" Name="ScanModeLabel"
-                           Text="{Loc 'health-analyzer-window-entity-unknown-text'}" />
+                    <Label Margin="0 0 5 0"
+                           HorizontalExpand="True"
+                           HorizontalAlignment="Right"
+                           VerticalExpand="True"
+                           VerticalAlignment="Top"
+                           Name="ScanModeLabel"
+                           Text="{Loc 'health-analyzer-window-entity-unknown-text'}"/>
                 </BoxContainer>
 
-                <PanelContainer StyleClasses="LowDivider" />
+                <PanelContainer StyleClasses="LowDivider"/>
 
-                <GridContainer Margin="0 5 0 0" Columns="2">
-                    <Label Text="{Loc 'health-analyzer-window-entity-status-text'}" />
-                    <Label Name="StatusLabel" />
-                    <Label Text="{Loc 'health-analyzer-window-entity-temperature-text'}" />
-                    <Label Name="TemperatureLabel" />
-                    <Label Text="{Loc 'health-analyzer-window-entity-blood-level-text'}" />
-                    <Label Name="BloodLabel" />
-                    <Label Text="{Loc 'health-analyzer-window-entity-damage-total-text'}" />
-                    <Label Name="DamageLabel" />
+                <GridContainer Margin="0 5 0 0"
+                               Columns="2">
+                    <Label Text="{Loc 'health-analyzer-window-entity-status-text'}"/>
+                    <Label Name="StatusLabel"/>
+                    <Label Text="{Loc 'health-analyzer-window-entity-temperature-text'}"/>
+                    <Label Name="TemperatureLabel"/>
+                    <Label Text="{Loc 'health-analyzer-window-entity-blood-level-text'}"/>
+                    <Label Name="BloodLabel"/>
+                    <Label Text="{Loc 'health-analyzer-window-entity-damage-total-text'}"/>
+                    <Label Name="DamageLabel"/>
                 </GridContainer>
             </BoxContainer>
 
-            <PanelContainer Name="AlertsDivider" Visible="False" StyleClasses="LowDivider" />
+            <PanelContainer Name="AlertsDivider"
+                            Visible="False"
+                            StyleClasses="LowDivider"/>
 
-            <BoxContainer Name="AlertsContainer" Visible="False" Margin="0 5" Orientation="Vertical" HorizontalAlignment="Center">
+            <BoxContainer Name="AlertsContainer"
+                          Visible="False"
+                          Margin="0 5"
+                          Orientation="Vertical"
+                          HorizontalAlignment="Center">
 
             </BoxContainer>
 
-            <PanelContainer StyleClasses="LowDivider" />
+            <PanelContainer StyleClasses="LowDivider"/>
+            <!-- Shitmed Change End -->
 
             <BoxContainer
                 Name="GroupsContainer"

+ 115 - 8
Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs

@@ -4,6 +4,7 @@
 using Content.Shared.Atmos;
 using Content.Client.UserInterface.Controls;
 using Content.Shared.Alert;
+using Content.Shared._Shitmed.Targeting; // Shitmed
 using Content.Shared.Damage;
 using Content.Shared.Damage.Prototypes;
 using Content.Shared.FixedPoint;
@@ -35,7 +36,16 @@ public sealed partial class HealthAnalyzerWindow : FancyWindow
         private readonly SpriteSystem _spriteSystem;
         private readonly IPrototypeManager _prototypes;
         private readonly IResourceCache _cache;
+        // Shitmed Change Start
+        public event Action<TargetBodyPart?, EntityUid>? OnBodyPartSelected;
+        private EntityUid _spriteViewEntity;
 
+        [ValidatePrototypeId<EntityPrototype>]
+        private readonly EntProtoId _bodyView = "AlertSpriteView";
+
+        private readonly Dictionary<TargetBodyPart, TextureButton> _bodyPartControls;
+        private EntityUid? _target;
+        // Shitmed Change End
         public HealthAnalyzerWindow()
         {
             RobustXamlLoader.Load(this);
@@ -45,19 +55,79 @@ public HealthAnalyzerWindow()
             _spriteSystem = _entityManager.System<SpriteSystem>();
             _prototypes = dependencies.Resolve<IPrototypeManager>();
             _cache = dependencies.Resolve<IResourceCache>();
+            // Shitmed Change Start
+            _bodyPartControls = new Dictionary<TargetBodyPart, TextureButton>
+            {
+                { TargetBodyPart.Head, HeadButton },
+                { TargetBodyPart.Torso, ChestButton },
+                { TargetBodyPart.Groin, GroinButton },
+                { TargetBodyPart.LeftArm, LeftArmButton },
+                { TargetBodyPart.LeftHand, LeftHandButton },
+                { TargetBodyPart.RightArm, RightArmButton },
+                { TargetBodyPart.RightHand, RightHandButton },
+                { TargetBodyPart.LeftLeg, LeftLegButton },
+                { TargetBodyPart.LeftFoot, LeftFootButton },
+                { TargetBodyPart.RightLeg, RightLegButton },
+                { TargetBodyPart.RightFoot, RightFootButton },
+            };
+
+            foreach (var bodyPartButton in _bodyPartControls)
+            {
+                bodyPartButton.Value.MouseFilter = MouseFilterMode.Stop;
+                bodyPartButton.Value.OnPressed += _ => SetActiveBodyPart(bodyPartButton.Key, bodyPartButton.Value);
+            }
+            ReturnButton.OnPressed += _ => ResetBodyPart();
+            // Shitmed Change End
+        }
+        // Shitmed Change Start
+        public void SetActiveBodyPart(TargetBodyPart part, TextureButton button)
+        {
+            if (_target == null)
+                return;
+
+            // Bit of the ole shitcode until we have Groins in the prototypes.
+            OnBodyPartSelected?.Invoke(part == TargetBodyPart.Groin ? TargetBodyPart.Torso : part, _target.Value);
+        }
+
+        public void ResetBodyPart()
+        {
+            if (_target == null)
+                return;
+
+            OnBodyPartSelected?.Invoke(null, _target.Value);
         }
 
+        public void SetActiveButtons(bool isHumanoid)
+        {
+            foreach (var button in _bodyPartControls)
+                button.Value.Visible = isHumanoid;
+        }
+
+        // Not all of this function got messed with, but it was spread enough to warrant being covered entirely by a Shitmed Change
         public void Populate(HealthAnalyzerScannedUserMessage msg)
         {
-            var target = _entityManager.GetEntity(msg.TargetEntity);
+            // Start-Shitmed
+            _target = _entityManager.GetEntity(msg.TargetEntity);
+            EntityUid? part = msg.Part != null ? _entityManager.GetEntity(msg.Part.Value) : null;
+            var isPart = part != null;
 
-            if (target == null
-                || !_entityManager.TryGetComponent<DamageableComponent>(target, out var damageable))
+            if (_target == null
+                || !_entityManager.TryGetComponent<DamageableComponent>(isPart ? part : _target, out var damageable))
             {
                 NoPatientDataText.Visible = true;
                 return;
             }
 
+            SetActiveButtons(_entityManager.HasComponent<TargetingComponent>(_target.Value));
+
+            ReturnButton.Visible = isPart;
+            PartNameLabel.Visible = isPart;
+
+            if (part != null)
+                PartNameLabel.Text = _entityManager.HasComponent<MetaDataComponent>(part)
+                    ? Identity.Name(part.Value, _entityManager)
+                    : Loc.GetString("health-analyzer-window-entity-unknown-value-text");
+
             NoPatientDataText.Visible = false;
 
             // Scan Mode
@@ -72,19 +142,20 @@ public void Populate(HealthAnalyzerScannedUserMessage msg)
 
             // Patient Information
 
-            SpriteView.SetEntity(target.Value);
+            SpriteView.SetEntity(SetupIcon(msg.Body) ?? _target.Value);
             SpriteView.Visible = msg.ScanMode.HasValue && msg.ScanMode.Value;
+            PartView.Visible = SpriteView.Visible;
             NoDataTex.Visible = !SpriteView.Visible;
 
             var name = new FormattedMessage();
             name.PushColor(Color.White);
-            name.AddText(_entityManager.HasComponent<MetaDataComponent>(target.Value)
-                ? Identity.Name(target.Value, _entityManager)
+            name.AddText(_entityManager.HasComponent<MetaDataComponent>(_target.Value)
+                ? Identity.Name(_target.Value, _entityManager)
                 : Loc.GetString("health-analyzer-window-entity-unknown-text"));
             NameLabel.SetMessage(name);
 
             SpeciesLabel.Text =
-                _entityManager.TryGetComponent<HumanoidAppearanceComponent>(target.Value,
+                _entityManager.TryGetComponent<HumanoidAppearanceComponent>(_target.Value,
                     out var humanoidAppearanceComponent)
                     ? Loc.GetString(_prototypes.Index<SpeciesPrototype>(humanoidAppearanceComponent.Species).Name)
                     : Loc.GetString("health-analyzer-window-entity-unknown-species-text");
@@ -100,7 +171,7 @@ public void Populate(HealthAnalyzerScannedUserMessage msg)
                 : Loc.GetString("health-analyzer-window-entity-unknown-value-text");
 
             StatusLabel.Text =
-                _entityManager.TryGetComponent<MobStateComponent>(target.Value, out var mobStateComponent)
+                _entityManager.TryGetComponent<MobStateComponent>(_target.Value, out var mobStateComponent)
                     ? GetStatus(mobStateComponent.CurrentState)
                     : Loc.GetString("health-analyzer-window-entity-unknown-text");
 
@@ -144,6 +215,7 @@ public void Populate(HealthAnalyzerScannedUserMessage msg)
 
             DrawDiagnosticGroups(damageSortedGroups, damagePerType);
         }
+        // Shitmed Change End
 
         private static string GetStatus(MobState mobState)
         {
@@ -243,5 +315,40 @@ private BoxContainer CreateDiagnosticGroupTitle(string text, string id)
 
             return rootContainer;
         }
+        // Shitmed Change Start
+        /// <summary>
+        /// Sets up the Body Doll using Alert Entity to use in Health Analyzer.
+        /// </summary>
+        private EntityUid? SetupIcon(Dictionary<TargetBodyPart, TargetIntegrity>? body)
+        {
+            if (body is null)
+                return null;
+
+            if (!_entityManager.Deleted(_spriteViewEntity))
+                _entityManager.QueueDeleteEntity(_spriteViewEntity);
+
+            _spriteViewEntity = _entityManager.Spawn(_bodyView);
+
+            if (!_entityManager.TryGetComponent<SpriteComponent>(_spriteViewEntity, out var sprite))
+                return null;
+
+            int layer = 0;
+            foreach (var (bodyPart, integrity) in body)
+            {
+                // TODO: PartStatusUIController and make it use layers instead of TextureRects when EE refactors alerts.
+                string enumName = Enum.GetName(typeof(TargetBodyPart), bodyPart) ?? "Unknown";
+                int enumValue = (int)integrity;
+                var rsi = new SpriteSpecifier.Rsi(new ResPath($"/Textures/_Shitmed/Interface/Targeting/Status/{enumName.ToLowerInvariant()}.rsi"), $"{enumName.ToLowerInvariant()}_{enumValue}");
+                // Shitcode with love from Russia :)
+                if (!sprite.TryGetLayer(layer, out _))
+                    sprite.AddLayer(_spriteSystem.Frame0(rsi));
+                else
+                    sprite.LayerSetTexture(layer, _spriteSystem.Frame0(rsi));
+                sprite.LayerSetScale(layer, new Vector2(3f, 3f));
+                layer++;
+            }
+            return _spriteViewEntity;
+        }
+        // Shitmed Change End
     }
 }

+ 30 - 68
Content.Client/Humanoid/HumanoidAppearanceSystem.cs

@@ -1,11 +1,21 @@
-using Content.Shared.CCVar;
+// SPDX-FileCopyrightText: 2023 DrSmugleaf <DrSmugleaf@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2023 Flipp Syder <76629141+vulppine@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2023 Leon Friedrich <60421075+ElectroJr@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2023 Morb <14136326+Morb0@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2023 csqrb <56765288+CaptainSqrBeard@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2023 metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Nemanja <98561806+EmoGarbage404@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
 using Content.Shared.Humanoid;
 using Content.Shared.Humanoid.Markings;
 using Content.Shared.Humanoid.Prototypes;
-using Content.Shared.Inventory;
 using Content.Shared.Preferences;
+using Content.Shared.Inventory;
 using Robust.Client.GameObjects;
-using Robust.Shared.Configuration;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Utility;
 
@@ -15,15 +25,12 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
 {
     [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
     [Dependency] private readonly MarkingManager _markingManager = default!;
-    [Dependency] private readonly IConfigurationManager _configurationManager = default!;
 
     public override void Initialize()
     {
         base.Initialize();
 
         SubscribeLocalEvent<HumanoidAppearanceComponent, AfterAutoHandleStateEvent>(OnHandleState);
-        Subs.CVar(_configurationManager, CCVars.AccessibilityClientCensorNudity, OnCvarChanged, true);
-        Subs.CVar(_configurationManager, CCVars.AccessibilityServerCensorNudity, OnCvarChanged, true);
     }
 
     private void OnHandleState(EntityUid uid, HumanoidAppearanceComponent component, ref AfterAutoHandleStateEvent args)
@@ -31,15 +38,6 @@ private void OnHandleState(EntityUid uid, HumanoidAppearanceComponent component,
         UpdateSprite(component, Comp<SpriteComponent>(uid));
     }
 
-    private void OnCvarChanged(bool value)
-    {
-        var humanoidQuery = EntityManager.AllEntityQueryEnumerator<HumanoidAppearanceComponent, SpriteComponent>();
-        while (humanoidQuery.MoveNext(out var _, out var humanoidComp, out var spriteComp))
-        {
-            UpdateSprite(humanoidComp, spriteComp);
-        }
-    }
-
     private void UpdateSprite(HumanoidAppearanceComponent component, SpriteComponent sprite)
     {
         UpdateLayers(component, sprite);
@@ -49,7 +47,7 @@ private void UpdateSprite(HumanoidAppearanceComponent component, SpriteComponent
     }
 
     private static bool IsHidden(HumanoidAppearanceComponent humanoid, HumanoidVisualLayers layer)
-        => humanoid.HiddenLayers.ContainsKey(layer) || humanoid.PermanentlyHidden.Contains(layer);
+        => humanoid.PermanentlyHidden.Contains(layer);
 
     private void UpdateLayers(HumanoidAppearanceComponent component, SpriteComponent sprite)
     {
@@ -70,7 +68,8 @@ private void UpdateLayers(HumanoidAppearanceComponent component, SpriteComponent
         foreach (var (key, info) in component.CustomBaseLayers)
         {
             oldLayers.Remove(key);
-            SetLayerData(component, sprite, key, info.Id, sexMorph: false, color: info.Color);
+            // Shitmed Change: For whatever reason these weren't actually ignoring the skin color as advertised.
+            SetLayerData(component, sprite, key, info.Id, sexMorph: false, color: info.Color, overrideSkin: true);
         }
 
         // hide old layers
@@ -88,7 +87,8 @@ private void UpdateLayers(HumanoidAppearanceComponent component, SpriteComponent
         HumanoidVisualLayers key,
         string? protoId,
         bool sexMorph = false,
-        Color? color = null)
+        Color? color = null,
+        bool overrideSkin = false) // Shitmed Change
     {
         var layerIndex = sprite.LayerMapReserveBlank(key);
         var layer = sprite[layerIndex];
@@ -106,7 +106,7 @@ private void UpdateLayers(HumanoidAppearanceComponent component, SpriteComponent
         var proto = _prototypeManager.Index<HumanoidSpeciesSpriteLayer>(protoId);
         component.BaseLayers[key] = proto;
 
-        if (proto.MatchSkin)
+        if (proto.MatchSkin && !overrideSkin) // Shitmed Change
             layer.Color = component.SkinColor.WithAlpha(proto.LayerAlpha);
 
         if (proto.BaseSprite != null)
@@ -204,7 +204,7 @@ public override void LoadProfile(EntityUid uid, HumanoidCharacterProfile? profil
 
         humanoid.MarkingSet = markings;
         humanoid.PermanentlyHidden = new HashSet<HumanoidVisualLayers>();
-        humanoid.HiddenLayers = new Dictionary<HumanoidVisualLayers, SlotFlags>();
+        humanoid.HiddenLayers = new HashSet<HumanoidVisualLayers>();
         humanoid.CustomBaseLayers = customBaseLayers;
         humanoid.Sex = profile.Sex;
         humanoid.Gender = profile.Gender;
@@ -222,30 +222,16 @@ private void ApplyMarkingSet(HumanoidAppearanceComponent humanoid, SpriteCompone
         // Really, markings should probably be a separate component altogether.
         ClearAllMarkings(humanoid, sprite);
 
-        var censorNudity = _configurationManager.GetCVar(CCVars.AccessibilityClientCensorNudity) ||
-                           _configurationManager.GetCVar(CCVars.AccessibilityServerCensorNudity);
-        // The reason we're splitting this up is in case the character already has undergarment equipped in that slot.
-        var applyUndergarmentTop = censorNudity;
-        var applyUndergarmentBottom = censorNudity;
-
         foreach (var markingList in humanoid.MarkingSet.Markings.Values)
         {
             foreach (var marking in markingList)
             {
                 if (_markingManager.TryGetMarking(marking, out var markingPrototype))
-                {
                     ApplyMarking(markingPrototype, marking.MarkingColors, marking.Visible, humanoid, sprite);
-                    if (markingPrototype.BodyPart == HumanoidVisualLayers.UndergarmentTop)
-                        applyUndergarmentTop = false;
-                    else if (markingPrototype.BodyPart == HumanoidVisualLayers.UndergarmentBottom)
-                        applyUndergarmentBottom = false;
-                }
             }
         }
 
         humanoid.ClientOldMarkings = new MarkingSet(humanoid.MarkingSet);
-
-        AddUndergarments(humanoid, sprite, applyUndergarmentTop, applyUndergarmentBottom);
     }
 
     private void ClearAllMarkings(HumanoidAppearanceComponent humanoid, SpriteComponent sprite)
@@ -293,31 +279,6 @@ private void RemoveMarking(Marking marking, SpriteComponent spriteComp)
             spriteComp.RemoveLayer(index);
         }
     }
-
-    private void AddUndergarments(HumanoidAppearanceComponent humanoid, SpriteComponent sprite, bool undergarmentTop, bool undergarmentBottom)
-    {
-        if (undergarmentTop && humanoid.UndergarmentTop != null)
-        {
-            var marking = new Marking(humanoid.UndergarmentTop, new List<Color> { new Color() });
-            if (_markingManager.TryGetMarking(marking, out var prototype))
-            {
-                // Markings are added to ClientOldMarkings because otherwise it causes issues when toggling the feature on/off.
-                humanoid.ClientOldMarkings.Markings.Add(MarkingCategories.UndergarmentTop, new List<Marking>{ marking });
-                ApplyMarking(prototype, null, true, humanoid, sprite);
-            }
-        }
-
-        if (undergarmentBottom && humanoid.UndergarmentBottom != null)
-        {
-            var marking = new Marking(humanoid.UndergarmentBottom, new List<Color> { new Color() });
-            if (_markingManager.TryGetMarking(marking, out var prototype))
-            {
-                humanoid.ClientOldMarkings.Markings.Add(MarkingCategories.UndergarmentBottom, new List<Marking>{ marking });
-                ApplyMarking(prototype, null, true, humanoid, sprite);
-            }
-        }
-    }
-
     private void ApplyMarking(MarkingPrototype markingPrototype,
         IReadOnlyList<Color>? colors,
         bool visible,
@@ -392,21 +353,23 @@ public override void SetSkinColor(EntityUid uid, Color skinColor, bool sync = tr
         }
     }
 
-    public override void SetLayerVisibility(
-        Entity<HumanoidAppearanceComponent> ent,
+    protected override void SetLayerVisibility(
+        EntityUid uid,
+        HumanoidAppearanceComponent humanoid,
         HumanoidVisualLayers layer,
         bool visible,
-        SlotFlags? slot,
+        bool permanent,
         ref bool dirty)
     {
-        base.SetLayerVisibility(ent, layer, visible, slot, ref dirty);
+        base.SetLayerVisibility(uid, humanoid, layer, visible, permanent, ref dirty);
 
-        var sprite = Comp<SpriteComponent>(ent);
+        var sprite = Comp<SpriteComponent>(uid);
         if (!sprite.LayerMapTryGet(layer, out var index))
         {
             if (!visible)
                 return;
-            index = sprite.LayerMapReserveBlank(layer);
+            else
+                index = sprite.LayerMapReserveBlank(layer);
         }
 
         var spriteLayer = sprite[index];
@@ -416,14 +379,13 @@ public override void SetSkinColor(EntityUid uid, Color skinColor, bool sync = tr
         spriteLayer.Visible = visible;
 
         // I fucking hate this. I'll get around to refactoring sprite layers eventually I swear
-        // Just a week away...
 
-        foreach (var markingList in ent.Comp.MarkingSet.Markings.Values)
+        foreach (var markingList in humanoid.MarkingSet.Markings.Values)
         {
             foreach (var marking in markingList)
             {
                 if (_markingManager.TryGetMarking(marking, out var markingPrototype) && markingPrototype.BodyPart == layer)
-                    ApplyMarking(markingPrototype, marking.MarkingColors, marking.Visible, ent, sprite);
+                    ApplyMarking(markingPrototype, marking.MarkingColors, marking.Visible, humanoid, sprite);
             }
         }
     }

+ 12 - 0
Content.Client/Input/ContentContexts.cs

@@ -84,6 +84,18 @@ public static void SetupContexts(IInputContextContainer contexts)
             human.AddFunction(ContentKeyFunctions.Arcade1);
             human.AddFunction(ContentKeyFunctions.Arcade2);
             human.AddFunction(ContentKeyFunctions.Arcade3);
+            // Shitmed Change Start - TODO: Add hands, feet and groin targeting.
+            human.AddFunction(ContentKeyFunctions.TargetHead);
+            human.AddFunction(ContentKeyFunctions.TargetTorso);
+            human.AddFunction(ContentKeyFunctions.TargetLeftArm);
+            human.AddFunction(ContentKeyFunctions.TargetLeftHand);
+            human.AddFunction(ContentKeyFunctions.TargetRightArm);
+            human.AddFunction(ContentKeyFunctions.TargetRightHand);
+            human.AddFunction(ContentKeyFunctions.TargetLeftLeg);
+            human.AddFunction(ContentKeyFunctions.TargetLeftFoot);
+            human.AddFunction(ContentKeyFunctions.TargetRightLeg);
+            human.AddFunction(ContentKeyFunctions.TargetRightFoot);
+            // Shitmed Change End
             human.AddFunction(ContentKeyFunctions.Lay); // Stalker-Changes-UI
             // actions should be common (for ghosts, mobs, etc)
             common.AddFunction(ContentKeyFunctions.OpenActionsMenu);

+ 18 - 1
Content.Client/MedBook/UI/MedBookBoundUserInterface.cs

@@ -1,10 +1,12 @@
 using Content.Shared.MedicalScanner;
 using JetBrains.Annotations;
 using Robust.Client.UserInterface;
+using Content.Shared._Shitmed.Targeting; // Shitmed Change
 
 namespace Content.Client.MedBook.UI
 {
     [UsedImplicitly]
+
     public sealed class MedBookBoundUserInterface : BoundUserInterface
     {
         [ViewVariables]
@@ -19,7 +21,7 @@ protected override void Open()
             base.Open();
 
             _window = this.CreateWindow<MedBookWindow>();
-
+            _window.OnBodyPartSelected += SendBodyPartMessage; // Shitmed Change
             _window.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
         }
 
@@ -33,5 +35,20 @@ protected override void ReceiveMessage(BoundUserInterfaceMessage message)
 
             _window.Populate(cast);
         }
+        // Shitmed Change Start
+        private void SendBodyPartMessage(TargetBodyPart? part, EntityUid target) => SendMessage(new MedBookPartMessage(EntMan.GetNetEntity(target), part ?? null));
+        protected override void Dispose(bool disposing)
+        {
+            base.Dispose(disposing);
+            if (!disposing)
+                return;
+
+            if (_window != null)
+                _window.OnBodyPartSelected -= SendBodyPartMessage;
+
+            _window?.Dispose();
+        }
+
+        // Shitmed Change End
     }
 }

+ 172 - 24
Content.Client/MedBook/UI/MedBookWindow.xaml

@@ -2,7 +2,7 @@
     xmlns="https://spacestation14.io"
     xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
     MaxHeight="525"
-    MinWidth="300">
+    MinWidth="350">
     <ScrollContainer
         Margin="5 5 5 5"
         ReturnMeasure="True"
@@ -14,39 +14,186 @@
             <Label
                 Name="NoPatientDataText"
                 Text="{Loc health-analyzer-window-no-patient-data-text}"/>
+            <!-- Shitmed Change Start -->
+            <Button Name="ReturnButton"
+                    Text="{Loc 'health-analyzer-window-return-button-text'}"
+                    Margin="0 0 0 10"
+                    HorizontalExpand="False"/>
+            <!-- Shitmed Change End -->
 
             <BoxContainer
                 Name="PatientDataContainer"
                 Margin="0 0 0 5"
                 Orientation="Vertical">
+                <!-- Shitmed Change Start -->
                 <BoxContainer Orientation="Horizontal"
-                        Margin="0 0 0 5">
-                    <SpriteView OverrideDirection="South"
-                            Scale="2 2"
-                            Name="SpriteView"
-                            Access="Public"
-                            SetSize="64 64"/>
+                              Margin="0 0 0 5">
+                    <PanelContainer>
+                        <SpriteView OverrideDirection="South"
+                                    Name="SpriteView"
+                                    Access="Public"
+                                    SetSize="96 96"/>
+                        <PanelContainer
+                            Name="PartView"
+                            SetSize="57 96"
+                            Margin="18 0 0 0"
+                            VerticalAlignment="Center"
+                            HorizontalAlignment="Left">
+                            <PanelContainer
+                                SetSize="15 33"
+                                Margin="4 0 0 0"
+                                HorizontalAlignment="Left">
+                                <TextureButton
+                                    Name="RightArmButton"
+                                    MinSize="15 25"
+                                    StyleClasses="TargetDollButtonRightArm"
+                                    VerticalAlignment="Top">
+                                </TextureButton>
+                                <TextureButton
+                                    Name="RightHandButton"
+                                    MinSize="15 15"
+                                    VerticalAlignment="Bottom"
+                                    StyleClasses="TargetDollButtonRightHand">
+                                </TextureButton>
+                            </PanelContainer>
+                            <PanelContainer
+                                SetSize="43 75"
+                                Margin="0 0 0 0"
+                                VerticalAlignment="Center"
+                                HorizontalAlignment="Center">
+                                <TextureButton
+                                    Name="HeadButton"
+                                    MinSize="28 23"
+                                    VerticalAlignment="Top"
+                                    HorizontalAlignment="Center"
+                                    StyleClasses="TargetDollButtonHead">
+                                    <!--<PanelContainer
+                            SetSize="15 15"
+                            Margin="0 9 0 0"
+                            HorizontalAlignment="Center">
+                            <TextureButton
+                                Name="EyesButton"
+                                MinSize="15 9"
+                                VerticalAlignment="Top"
+                                StyleClasses="TargetDollButtonEyes">
+                                <TextureRect
+                                    TexturePath="/Textures/Interface/Targeting/Doll/eyes.png"
+                                    Stretch="KeepAspectCentered"
+                                    SetSize="15 9"/>
+                            </TextureButton>
+                            <TextureButton
+                                Name="MouthButton"
+                                SetSize="9 6"
+                                VerticalAlignment="Bottom"
+                                StyleClasses="TargetDollButtonMouth">
+                                <TextureRect
+                                    TexturePath="/Textures/Interface/Targeting/Doll/mouth.png"
+                                    Stretch="KeepAspectCentered"
+                                    SetSize="9 6"/>
+                            </TextureButton>
+                        </PanelContainer>-->
+                                </TextureButton>
+                                <TextureButton
+                                    Name="ChestButton"
+                                    SetSize="28 30"
+                                    Margin="0 18 0 0"
+                                    VerticalAlignment="Top"
+                                    HorizontalAlignment="Center"
+                                    StyleClasses="TargetDollButtonChest">
+                                </TextureButton>
+                                <PanelContainer
+                                    MinSize="38 35"
+                                    VerticalAlignment="Bottom"
+                                    HorizontalAlignment="Center">
+                                    <TextureButton
+                                        Name="GroinButton"
+                                        MinSize="28 15"
+                                        VerticalAlignment="Top"
+                                        HorizontalAlignment="Center"
+                                        StyleClasses="TargetDollButtonGroin">
+                                    </TextureButton>
+                                    <PanelContainer
+                                        MinSize="20 30"
+                                        VerticalAlignment="Bottom"
+                                        HorizontalAlignment="Right">
+                                        <TextureButton
+                                            Name="LeftLegButton"
+                                            MinSize="15 28"
+                                            VerticalAlignment="Top"
+                                            HorizontalAlignment="Left"
+                                            StyleClasses="TargetDollButtonLeftLeg">
+                                        </TextureButton>
+                                        <TextureButton
+                                            Name="LeftFootButton"
+                                            MinSize="20 10"
+                                            VerticalAlignment="Bottom"
+                                            StyleClasses="TargetDollButtonLeftFoot">
+                                        </TextureButton>
+                                    </PanelContainer>
+                                    <PanelContainer
+                                        MinSize="20 30"
+                                        VerticalAlignment="Bottom"
+                                        HorizontalAlignment="Left">
+                                        <TextureButton
+                                            Name="RightLegButton"
+                                            MinSize="15 28"
+                                            VerticalAlignment="Top"
+                                            HorizontalAlignment="Right"
+                                            StyleClasses="TargetDollButtonRightLeg">
+                                        </TextureButton>
+                                        <TextureButton
+                                            Name="RightFootButton"
+                                            MinSize="20 10"
+                                            VerticalAlignment="Bottom"
+                                            HorizontalAlignment="Center"
+                                            StyleClasses="TargetDollButtonRightFoot">
+                                        </TextureButton>
+                                    </PanelContainer>
+                                </PanelContainer>
+                            </PanelContainer>
+                            <PanelContainer
+                                SetSize="15 33"
+                                Margin="0 0 4 0"
+                                HorizontalAlignment="Right">
+                                <TextureButton
+                                    Name="LeftArmButton"
+                                    MinSize="15 25"
+                                    StyleClasses="TargetDollButtonLeftArm"
+                                    VerticalAlignment="Top">
+                                </TextureButton>
+                                <TextureButton
+                                    Name="LeftHandButton"
+                                    MinSize="15 15"
+                                    VerticalAlignment="Bottom"
+                                    StyleClasses="TargetDollButtonLeftHand">
+                                </TextureButton>
+                            </PanelContainer>
+                        </PanelContainer>
+                    </PanelContainer>
                     <TextureRect Name="NoDataTex"
-                            Access="Public"
-                            SetSize="64 64"
-                            Visible="false"
-                            Stretch="KeepAspectCentered"
-                            TexturePath="/Textures/Interface/Misc/health_analyzer_out_of_range.png"/>
+                                 Access="Public"
+                                 SetSize="64 64"
+                                 Visible="false"
+                                 Stretch="KeepAspectCentered"
+                                 TexturePath="/Textures/Interface/Misc/health_analyzer_out_of_range.png"/>
                     <BoxContainer Margin="5 0 0 0"
-                            Orientation="Vertical"
-                            VerticalAlignment="Top">
+                                  Orientation="Vertical"
+                                  VerticalAlignment="Top">
                         <RichTextLabel Name="NameLabel"
-                                SetWidth="150"/>
+                                       SetWidth="150"/>
                         <Label Name="SpeciesLabel"
-                                VerticalAlignment="Top"
-                                StyleClasses="LabelSubText"/>
+                               VerticalAlignment="Top"
+                               StyleClasses="LabelSubText"/>
+                        <Label Name="PartNameLabel"
+                               VerticalAlignment="Bottom"
+                               StyleClasses="LabelSubText"/>
                     </BoxContainer>
                 </BoxContainer>
 
                 <PanelContainer StyleClasses="LowDivider"/>
 
                 <GridContainer Margin="0 5 0 0"
-                        Columns="2">
+                               Columns="2">
                     <Label Text="{Loc 'health-analyzer-window-entity-status-text'}"/>
                     <Label Name="StatusLabel"/>
                     <Label Text="{Loc 'health-analyzer-window-entity-blood-level-text'}"/>
@@ -55,18 +202,19 @@
             </BoxContainer>
 
             <PanelContainer Name="AlertsDivider"
-                    Visible="False"
-                    StyleClasses="LowDivider"/>
+                            Visible="False"
+                            StyleClasses="LowDivider"/>
 
             <BoxContainer Name="AlertsContainer"
-                    Visible="False"
-                    Margin="0 5"
-                    Orientation="Vertical"
-                    HorizontalAlignment="Center">
+                          Visible="False"
+                          Margin="0 5"
+                          Orientation="Vertical"
+                          HorizontalAlignment="Center">
 
             </BoxContainer>
 
             <PanelContainer StyleClasses="LowDivider"/>
+            <!-- Shitmed Change End -->
 
             <BoxContainer
                 Name="GroupsContainer"

+ 113 - 8
Content.Client/MedBook/UI/MedBookWindow.xaml.cs

@@ -25,6 +25,7 @@
 using Robust.Client.UserInterface;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Utility;
+using Content.Shared._Shitmed.Targeting; // Shitmed
 
 namespace Content.Client.MedBook.UI
 {
@@ -35,7 +36,16 @@ public sealed partial class MedBookWindow : FancyWindow
         private readonly SpriteSystem _spriteSystem;
         private readonly IPrototypeManager _prototypes;
         private readonly IResourceCache _cache;
+        // Shitmed Change Start
+        public event Action<TargetBodyPart?, EntityUid>? OnBodyPartSelected;
+        private EntityUid _spriteViewEntity;
 
+        [ValidatePrototypeId<EntityPrototype>]
+        private readonly EntProtoId _bodyView = "AlertSpriteView";
+
+        private readonly Dictionary<TargetBodyPart, TextureButton> _bodyPartControls;
+        private EntityUid? _target;
+        // Shitmed Change End
         public MedBookWindow()
         {
             RobustXamlLoader.Load(this);
@@ -45,36 +55,94 @@ public MedBookWindow()
             _spriteSystem = _entityManager.System<SpriteSystem>();
             _prototypes = dependencies.Resolve<IPrototypeManager>();
             _cache = dependencies.Resolve<IResourceCache>();
+            // Shitmed Change Start
+            _bodyPartControls = new Dictionary<TargetBodyPart, TextureButton>
+            {
+                { TargetBodyPart.Head, HeadButton },
+                { TargetBodyPart.Torso, ChestButton },
+                { TargetBodyPart.Groin, GroinButton },
+                { TargetBodyPart.LeftArm, LeftArmButton },
+                { TargetBodyPart.LeftHand, LeftHandButton },
+                { TargetBodyPart.RightArm, RightArmButton },
+                { TargetBodyPart.RightHand, RightHandButton },
+                { TargetBodyPart.LeftLeg, LeftLegButton },
+                { TargetBodyPart.LeftFoot, LeftFootButton },
+                { TargetBodyPart.RightLeg, RightLegButton },
+                { TargetBodyPart.RightFoot, RightFootButton },
+            };
+
+            foreach (var bodyPartButton in _bodyPartControls)
+            {
+                bodyPartButton.Value.MouseFilter = MouseFilterMode.Stop;
+                bodyPartButton.Value.OnPressed += _ => SetActiveBodyPart(bodyPartButton.Key, bodyPartButton.Value);
+            }
+            ReturnButton.OnPressed += _ => ResetBodyPart();
+            // Shitmed Change End
+        }
+        // Shitmed Change Start
+        public void SetActiveBodyPart(TargetBodyPart part, TextureButton button)
+        {
+            if (_target == null)
+                return;
+
+            // Bit of the ole shitcode until we have Groins in the prototypes.
+            OnBodyPartSelected?.Invoke(part == TargetBodyPart.Groin ? TargetBodyPart.Torso : part, _target.Value);
+        }
+
+        public void ResetBodyPart()
+        {
+            if (_target == null)
+                return;
+
+            OnBodyPartSelected?.Invoke(null, _target.Value);
         }
 
+        public void SetActiveButtons(bool isHumanoid)
+        {
+            foreach (var button in _bodyPartControls)
+                button.Value.Visible = isHumanoid;
+        }
         public void Populate(MedBookScannedUserMessage msg)
         {
-            var target = _entityManager.GetEntity(msg.TargetEntity);
+            _target = _entityManager.GetEntity(msg.TargetEntity);
+            EntityUid? part = msg.Part != null ? _entityManager.GetEntity(msg.Part.Value) : null;
+            var isPart = part != null;
 
-            if (target == null
-                || !_entityManager.TryGetComponent<DamageableComponent>(target, out var damageable))
+            if (_target == null
+                || !_entityManager.TryGetComponent<DamageableComponent>(isPart ? part : _target, out var damageable))
             {
                 NoPatientDataText.Visible = true;
                 return;
             }
 
+            SetActiveButtons(_entityManager.HasComponent<TargetingComponent>(_target.Value));
+
+            ReturnButton.Visible = isPart;
+            PartNameLabel.Visible = isPart;
+
+            if (part != null)
+                PartNameLabel.Text = _entityManager.HasComponent<MetaDataComponent>(part)
+                    ? Identity.Name(part.Value, _entityManager)
+                    : Loc.GetString("health-analyzer-window-entity-unknown-value-text");
+
             NoPatientDataText.Visible = false;
 
             // Patient Information
 
-            SpriteView.SetEntity(target.Value);
+            SpriteView.SetEntity(SetupIcon(msg.Body) ?? _target.Value);
             SpriteView.Visible = msg.ScanMode.HasValue && msg.ScanMode.Value;
+            PartView.Visible = SpriteView.Visible;
             NoDataTex.Visible = !SpriteView.Visible;
 
             var name = new FormattedMessage();
             name.PushColor(Color.White);
-            name.AddText(_entityManager.HasComponent<MetaDataComponent>(target.Value)
-                ? Identity.Name(target.Value, _entityManager)
+            name.AddText(_entityManager.HasComponent<MetaDataComponent>(_target.Value)
+                ? Identity.Name(_target.Value, _entityManager)
                 : Loc.GetString("health-analyzer-window-entity-unknown-text"));
             NameLabel.SetMessage(name);
 
             SpeciesLabel.Text =
-                _entityManager.TryGetComponent<HumanoidAppearanceComponent>(target.Value,
+                _entityManager.TryGetComponent<HumanoidAppearanceComponent>(_target.Value,
                     out var humanoidAppearanceComponent)
                     ? Loc.GetString(_prototypes.Index<SpeciesPrototype>(humanoidAppearanceComponent.Species).Name)
                     : Loc.GetString("health-analyzer-window-entity-unknown-species-text");
@@ -98,7 +166,7 @@ public void Populate(MedBookScannedUserMessage msg)
                 : Loc.GetString("health-analyzer-window-entity-unknown-value-text");
             StatusLabel.Text = Loc.GetString("health-analyzer-window-entity-alive-text");
             StatusLabel.FontColorOverride = Color.Green;
-            if (_entityManager.TryGetComponent<MobStateComponent>(target.Value, out var mobStateComponent))
+            if (_entityManager.TryGetComponent<MobStateComponent>(_target.Value, out var mobStateComponent))
             {
                 if (mobStateComponent.CurrentState == MobState.Critical)
                 {
@@ -149,6 +217,8 @@ public void Populate(MedBookScannedUserMessage msg)
             DrawDiagnosticGroups(damageSortedGroups, damagePerType);
         }
 
+
+        // Not all of this function got messed with, but it was spread enough to warrant being covered entirely by a Shitmed Change
         private static string GetStatus(MobState mobState)
         {
             return mobState switch
@@ -253,5 +323,40 @@ private BoxContainer CreateDiagnosticGroupTitle(string text, string id, Color co
 
             return rootContainer;
         }
+        // Shitmed Change Start
+        /// <summary>
+        /// Sets up the Body Doll using Alert Entity to use in Health Analyzer.
+        /// </summary>
+        private EntityUid? SetupIcon(Dictionary<TargetBodyPart, TargetIntegrity>? body)
+        {
+            if (body is null)
+                return null;
+
+            if (!_entityManager.Deleted(_spriteViewEntity))
+                _entityManager.QueueDeleteEntity(_spriteViewEntity);
+
+            _spriteViewEntity = _entityManager.Spawn(_bodyView);
+
+            if (!_entityManager.TryGetComponent<SpriteComponent>(_spriteViewEntity, out var sprite))
+                return null;
+
+            int layer = 0;
+            foreach (var (bodyPart, integrity) in body)
+            {
+                // TODO: PartStatusUIController and make it use layers instead of TextureRects when EE refactors alerts.
+                string enumName = Enum.GetName(typeof(TargetBodyPart), bodyPart) ?? "Unknown";
+                int enumValue = (int)integrity;
+                var rsi = new SpriteSpecifier.Rsi(new ResPath($"/Textures/_Shitmed/Interface/Targeting/Status/{enumName.ToLowerInvariant()}.rsi"), $"{enumName.ToLowerInvariant()}_{enumValue}");
+                // Shitcode with love from Russia :)
+                if (!sprite.TryGetLayer(layer, out _))
+                    sprite.AddLayer(_spriteSystem.Frame0(rsi));
+                else
+                    sprite.LayerSetTexture(layer, _spriteSystem.Frame0(rsi));
+                sprite.LayerSetScale(layer, new Vector2(3f, 3f));
+                layer++;
+            }
+            return _spriteViewEntity;
+        }
+        // Shitmed Change End
     }
 }

+ 13 - 0
Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs

@@ -231,6 +231,19 @@ void AddCheckBox(string checkBoxName, bool currentState, Action<BaseButton.Butto
             AddButton(EngineKeyFunctions.EscapeMenu);
             AddButton(ContentKeyFunctions.EscapeContext);
 
+            // Shitmed Change Start - TODO: Add hands, feet and groin targeting.
+            AddHeader("ui-options-header-targeting");
+            AddButton(ContentKeyFunctions.TargetHead);
+            AddButton(ContentKeyFunctions.TargetTorso);
+            AddButton(ContentKeyFunctions.TargetLeftArm);
+            AddButton(ContentKeyFunctions.TargetLeftHand);
+            AddButton(ContentKeyFunctions.TargetRightArm);
+            AddButton(ContentKeyFunctions.TargetRightHand);
+            AddButton(ContentKeyFunctions.TargetLeftLeg);
+            AddButton(ContentKeyFunctions.TargetLeftFoot);
+            AddButton(ContentKeyFunctions.TargetRightLeg);
+            AddButton(ContentKeyFunctions.TargetRightFoot);
+            // Shitmed Change End
             AddHeader("ui-options-header-misc");
             AddButton(ContentKeyFunctions.TakeScreenshot);
             AddButton(ContentKeyFunctions.TakeScreenshotNoUI);

+ 54 - 0
Content.Client/Stylesheets/StyleNano.cs

@@ -1642,6 +1642,60 @@ public StyleNano(IResourceCache resCache) : base(resCache)
                         BackgroundColor = FancyTreeSelectedRowColor,
                     }),
 
+                // Shitmed Change Start
+                Element<TextureButton>().Class("TargetDollButtonHead")
+                    .Pseudo(TextureButton.StylePseudoClassHover)
+                    .Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/_Shitmed/Interface/Targeting/Doll/head_hover.png")),
+
+                Element<TextureButton>().Class("TargetDollButtonChest")
+                    .Pseudo(TextureButton.StylePseudoClassHover)
+                    .Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/_Shitmed/Interface/Targeting/Doll/torso_hover.png")),
+
+                Element<TextureButton>().Class("TargetDollButtonGroin")
+                    .Pseudo(TextureButton.StylePseudoClassHover)
+                    .Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/_Shitmed/Interface/Targeting/Doll/groin_hover.png")),
+
+                Element<TextureButton>().Class("TargetDollButtonLeftArm")
+                    .Pseudo(TextureButton.StylePseudoClassHover)
+                    .Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/_Shitmed/Interface/Targeting/Doll/leftarm_hover.png")),
+
+                Element<TextureButton>().Class("TargetDollButtonLeftHand")
+                    .Pseudo(TextureButton.StylePseudoClassHover)
+                    .Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/_Shitmed/Interface/Targeting/Doll/lefthand_hover.png")),
+
+                Element<TextureButton>().Class("TargetDollButtonRightArm")
+                    .Pseudo(TextureButton.StylePseudoClassHover)
+                    .Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/_Shitmed/Interface/Targeting/Doll/rightarm_hover.png")),
+
+                Element<TextureButton>().Class("TargetDollButtonRightHand")
+                    .Pseudo(TextureButton.StylePseudoClassHover)
+                    .Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/_Shitmed/Interface/Targeting/Doll/righthand_hover.png")),
+
+                Element<TextureButton>().Class("TargetDollButtonLeftLeg")
+                    .Pseudo(TextureButton.StylePseudoClassHover)
+                    .Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/_Shitmed/Interface/Targeting/Doll/leftleg_hover.png")),
+
+                Element<TextureButton>().Class("TargetDollButtonLeftFoot")
+                    .Pseudo(TextureButton.StylePseudoClassHover)
+                    .Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/_Shitmed/Interface/Targeting/Doll/leftfoot_hover.png")),
+
+                Element<TextureButton>().Class("TargetDollButtonRightLeg")
+                    .Pseudo(TextureButton.StylePseudoClassHover)
+                    .Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/_Shitmed/Interface/Targeting/Doll/rightleg_hover.png")),
+
+                Element<TextureButton>().Class("TargetDollButtonRightFoot")
+                    .Pseudo(TextureButton.StylePseudoClassHover)
+                    .Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/_Shitmed/Interface/Targeting/Doll/rightfoot_hover.png")),
+
+                Element<TextureButton>().Class("TargetDollButtonEyes")
+                    .Pseudo(TextureButton.StylePseudoClassHover)
+                    .Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/_Shitmed/Interface/Targeting/Doll/eyes_hover.png")),
+
+                Element<TextureButton>().Class("TargetDollButtonMouth")
+                    .Pseudo(TextureButton.StylePseudoClassHover)
+                    .Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/_Shitmed/Interface/Targeting/Doll/mouth_hover.png")),
+                // Shitmed Change End
+
                 // Silicon law edit ui
                 Element<Label>().Class(SiliconLawContainer.StyleClassSiliconLawPositionLabel)
                     .Prop(Label.StylePropertyFontColor, NanoGold),

+ 28 - 10
Content.Client/UserInterface/Screens/DefaultGameScreen.xaml

@@ -9,25 +9,43 @@
     xmlns:hotbar="clr-namespace:Content.Client.UserInterface.Systems.Hotbar.Widgets"
     xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
     xmlns:inventory="clr-namespace:Content.Client.UserInterface.Systems.Inventory.Widgets"
+    xmlns:targeting="clr-namespace:Content.Client._Shitmed.UserInterface.Systems.Targeting.Widgets"
     Name="DefaultHud"
     VerticalExpand="False"
     VerticalAlignment="Bottom"
     HorizontalAlignment="Center">
-    <LayoutContainer Name="ViewportContainer" HorizontalExpand="True" VerticalExpand="True">
+    <LayoutContainer Name="ViewportContainer"
+                     HorizontalExpand="True"
+                     VerticalExpand="True">
         <controls:MainViewport Name="MainViewport"/>
     </LayoutContainer>
-    <BoxContainer Name="TopLeft" Access="Protected" Orientation="Vertical">
+    <BoxContainer Name="TopLeft"
+                  Access="Protected"
+                  Orientation="Vertical">
         <BoxContainer Orientation="Horizontal">
-            <menuBar:GameTopMenuBar Name="TopBar" Access="Protected" />
+            <menuBar:GameTopMenuBar Name="TopBar"
+                                    Access="Protected"/>
             <!-- Buffer so big votes don't skew it -->
             <Control/>
         </BoxContainer>
-        <BoxContainer Name="VoteMenu" Access="Public" Margin="0 10 0 10" Orientation="Vertical"/>
-        <actions:ActionsBar Name="Actions" Access="Protected" />
+        <BoxContainer Name="VoteMenu"
+                      Access="Public"
+                      Margin="0 10 0 10"
+                      Orientation="Vertical"/>
+        <actions:ActionsBar Name="Actions"
+                            Access="Protected"/>
     </BoxContainer>
-    <widgets:GhostGui Name="Ghost" Access="Protected" />
-    <inventory:InventoryGui Name="Inventory" Access="Protected" />
-    <hotbar:HotbarGui Name="Hotbar" Access="Protected" />
-    <chat:ResizableChatBox Name="Chat" Access="Protected" />
-    <alerts:AlertsUI Name="Alerts" Access="Protected" />
+    <widgets:GhostGui Name="Ghost"
+                      Access="Protected"/>
+    <inventory:InventoryGui Name="Inventory"
+                            Access="Protected"/>
+    <hotbar:HotbarGui Name="Hotbar"
+                      Access="Protected"/>
+    <targeting:TargetingControl Name="Targeting"
+            Access="Protected"/>
+    <!-- Shitmed Change -->
+    <chat:ResizableChatBox Name="Chat"
+                           Access="Protected"/>
+    <alerts:AlertsUI Name="Alerts"
+                     Access="Protected"/>
 </screens:DefaultGameScreen>

+ 1 - 1
Content.Client/UserInterface/Screens/DefaultGameScreen.xaml.cs

@@ -22,7 +22,7 @@ public DefaultGameScreen()
         SetAnchorAndMarginPreset(Hotbar, LayoutPreset.BottomWide, margin: 5);
         SetAnchorAndMarginPreset(Chat, LayoutPreset.TopRight, margin: 10);
         SetAnchorAndMarginPreset(Alerts, LayoutPreset.TopRight, margin: 10);
-
+        SetAnchorAndMarginPreset(Targeting, LayoutPreset.BottomRight, margin: 5); // Shitmed Change
         Chat.OnResized += ChatOnResized;
         Chat.OnChatResizeFinish += ChatOnResizeFinish;
 

+ 42 - 14
Content.Client/UserInterface/Screens/SeparatedChatGameScreen.xaml

@@ -10,30 +10,58 @@
     xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
     xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
     xmlns:inventory="clr-namespace:Content.Client.UserInterface.Systems.Inventory.Widgets"
+    xmlns:targeting="clr-namespace:Content.Client._Shitmed.UserInterface.Systems.Targeting.Widgets"
     Name="SeparatedChatHud"
     VerticalExpand="False"
     VerticalAlignment="Bottom"
     HorizontalAlignment="Center">
-    <SplitContainer Name="ScreenContainer" HorizontalExpand="True" VerticalExpand="True" SplitWidth="0" StretchDirection="TopLeft">
-        <LayoutContainer Name="ViewportContainer" HorizontalExpand="True" VerticalExpand="True">
+    <SplitContainer Name="ScreenContainer"
+                    HorizontalExpand="True"
+                    VerticalExpand="True"
+                    SplitWidth="0"
+                    StretchDirection="TopLeft">
+        <LayoutContainer Name="ViewportContainer"
+                         HorizontalExpand="True"
+                         VerticalExpand="True">
             <controls:MainViewport Name="MainViewport"/>
-            <widgets:GhostGui Name="Ghost" Access="Protected" />
-            <inventory:InventoryGui Name="Inventory" Access="Protected"/>
-            <hotbar:HotbarGui Name="Hotbar" Access="Protected"/>
-            <BoxContainer Name="TopLeftContainer" Orientation="Vertical">
-                <actions:ActionsBar Name="Actions" Access="Protected" />
-                <BoxContainer Name="VoteMenu" Access="Public" Orientation="Vertical"/>
+            <widgets:GhostGui Name="Ghost"
+                              Access="Protected"/>
+            <inventory:InventoryGui Name="Inventory"
+                                    Access="Protected"/>
+            <hotbar:HotbarGui Name="Hotbar"
+                              Access="Protected"/>
+            <targeting:TargetingControl Name="Targeting"
+                    Access="Protected"/>
+            <!-- Shitmed Change -->
+            <BoxContainer Name="TopLeftContainer"
+                          Orientation="Vertical">
+                <actions:ActionsBar Name="Actions"
+                                    Access="Protected"/>
+                <BoxContainer Name="VoteMenu"
+                              Access="Public"
+                              Orientation="Vertical"/>
             </BoxContainer>
-            <alerts:AlertsUI Name="Alerts" Access="Protected" />
+            <alerts:AlertsUI Name="Alerts"
+                             Access="Protected"/>
         </LayoutContainer>
-        <PanelContainer Name="SeparatedChatPanel" MinWidth="300">
+        <PanelContainer Name="SeparatedChatPanel"
+                        MinWidth="300">
             <PanelContainer.PanelOverride>
-                <graphics:StyleBoxFlat BackgroundColor="#2B2C3B" />
+                <graphics:StyleBoxFlat BackgroundColor="#2B2C3B"/>
             </PanelContainer.PanelOverride>
 
-            <BoxContainer Orientation="Vertical" HorizontalExpand="True" SeparationOverride="10" Margin="10">
-                <menuBar:GameTopMenuBar Name="TopBar" HorizontalExpand="True" Access="Protected" />
-                <chat:ChatBox VerticalExpand="True" HorizontalExpand="True" Name="Chat" Access="Protected" MinSize="0 0"/>
+            <BoxContainer Orientation="Vertical"
+                          HorizontalExpand="True"
+                          SeparationOverride="10"
+                          Margin="10">
+                <menuBar:GameTopMenuBar Name="TopBar"
+                                        HorizontalExpand="True"
+                                        Access="Protected"/>
+                <chat:ChatBox VerticalExpand="True"
+                              HorizontalExpand="True"
+                              Name="Chat"
+                              Access="Protected"
+                              MinSize="0 0"/>
             </BoxContainer>
         </PanelContainer>
     </SplitContainer>

+ 16 - 3
Content.Client/UserInterface/Systems/Alerts/Widgets/AlertsUI.xaml

@@ -1,7 +1,20 @@
 <widgets:AlertsUI xmlns="https://spacestation14.io"
                   xmlns:widgets="clr-namespace:Content.Client.UserInterface.Systems.Alerts.Widgets"
+                  xmlns:partStatus="clr-namespace:Content.Client._Shitmed.UserInterface.Systems.PartStatus.Widgets"
                   MinSize="64 64">
-    <PanelContainer HorizontalAlignment="Right" VerticalAlignment="Top">
-        <BoxContainer Name="AlertContainer" Access="Public" Orientation="Vertical" />
-    </PanelContainer>
+    <!-- Shitmed Change Start - TODO: Refactor this godforsaken abomination -->
+    <GridContainer Columns="1"
+            HorizontalAlignment="Right"
+            VerticalAlignment="Top">
+        <PanelContainer>
+            <GridContainer Name="AlertContainer"
+                    Columns="1"
+                    HorizontalAlignment="Right"
+                    VerticalAlignment="Center"
+                    Access="Public"/>
+        </PanelContainer>
+        <partStatus:PartStatusControl Name="PartStatus"
+                Access="Protected"/>
+    </GridContainer>
+    <!-- Shitmed Change End -->
 </widgets:AlertsUI>

+ 2 - 2
Content.Client/UserInterface/Systems/Inventory/Controls/ItemStatusPanel.xaml.cs

@@ -61,12 +61,12 @@ public void SetSide(HandUILocation location)
 
         Contents.Margin = contentMargin;
 
-        var panel = (StyleBoxTexture) Panel.PanelOverride!;
+        var panel = (StyleBoxTexture)Panel.PanelOverride!;
         panel.Texture = texture;
         panel.SetPatchMargin(flat, 4);
         panel.SetPatchMargin(cutOut, 7);
 
-        var panelHighlight = (StyleBoxTexture) HighlightPanel.PanelOverride!;
+        var panelHighlight = (StyleBoxTexture)HighlightPanel.PanelOverride!;
         panelHighlight.Texture = textureHighlight;
         panelHighlight.SetPatchMargin(flat, 4);
         panelHighlight.SetPatchMargin(cutOut, 7);

+ 22 - 0
Content.Client/_Shitmed/Autodoc/AddStepWindow.xaml

@@ -0,0 +1,22 @@
+<!--
+SPDX-FileCopyrightText: 2024 deltanedas <39013340+deltanedas@users.noreply.github.com>
+SPDX-FileCopyrightText: 2024 deltanedas <@deltanedas:kde.org>
+SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+
+SPDX-License-Identifier: AGPL-3.0-or-later
+-->
+
+<controls:FancyWindow
+        xmlns="https://spacestation14.io"
+        xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
+        Title="{Loc 'autodoc-add-step'}">
+    <BoxContainer Orientation="Vertical">
+        <Button Name="SurgeryButton" Text="{Loc 'autodoc-add-step-surgery'}"/>
+        <Button Name="GrabItemButton" Text="{Loc 'autodoc-add-step-grab-item'}"/>
+        <Button Name="GrabOrganButton" Text="{Loc 'autodoc-add-step-grab-organ'}"/>
+        <Button Name="GrabPartButton" Text="{Loc 'autodoc-add-step-grab-part'}"/>
+        <Button Name="StoreItemButton" Text="{Loc 'autodoc-add-step-store-item'}"/>
+        <Button Name="SetLabelButton" Text="{Loc 'autodoc-add-step-set-label'}"/>
+        <Button Name="WaitButton" Text="{Loc 'autodoc-add-step-wait'}"/>
+    </BoxContainer>
+</controls:FancyWindow>

+ 144 - 0
Content.Client/_Shitmed/Autodoc/AddStepWindow.xaml.cs

@@ -0,0 +1,144 @@
+// SPDX-FileCopyrightText: 2024 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2024 deltanedas <39013340+deltanedas@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 deltanedas <@deltanedas:kde.org>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Administration;
+using Content.Shared._Shitmed.Autodoc;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client._Shitmed.Autodoc;
+
+[GenerateTypedNameReferences]
+public sealed partial class AddStepWindow : FancyWindow
+{
+    public event Action<IAutodocStep>? OnAddStep;
+
+    private PickSurgeryWindow? _surgery;
+    private DialogWindow? _grab;
+    private DialogWindow? _label;
+    private DialogWindow? _wait;
+
+    public AddStepWindow()
+    {
+        RobustXamlLoader.Load(this);
+
+        // close the window once any step is added
+        OnAddStep += _ => Close();
+
+        OnClose += () =>
+        {
+            _surgery?.Close();
+            _grab?.Close();
+            _label?.Close();
+            _wait?.Close();
+        };
+
+        // dedicated ui to pick the enum values and surgery type
+        SurgeryButton.OnPressed += _ =>
+        {
+            if (_surgery is {} window)
+            {
+                window.MoveToFront();
+                return;
+            }
+
+            _surgery = new PickSurgeryWindow();
+            _surgery.OnAddStep += step => OnAddStep?.Invoke(step);
+            _surgery.OnClose += () => _surgery = null;
+            _surgery.OpenCentered();
+        };
+
+        // just picking a string, use a dialog
+        GrabItemButton.OnPressed += _ =>
+        {
+            if (_grab is {} dialog)
+            {
+                dialog.MoveToFront();
+                return;
+            }
+
+            var field = "name";
+            var prompt = Loc.GetString("autodoc-add-step-grab-item-prompt");
+            var placeholder = Loc.GetString("autodoc-add-step-grab-item-placeholder");
+            var entry = new QuickDialogEntry(field, QuickDialogEntryType.ShortText, prompt, placeholder);
+            var entries = new List<QuickDialogEntry> { entry };
+            _grab = new DialogWindow(GrabItemButton.Text!, entries);
+            _grab.OnConfirmed += responses =>
+            {
+                var name = responses[field].Trim();
+                if (name.Length < 1 || name.Length > 100)
+                    return;
+
+                OnAddStep?.Invoke(new GrabItemAutodocStep()
+                {
+                    Name = name
+                });
+            };
+            _grab.OnClose += () => _grab = null;
+        };
+
+        // no arguments so these are trivial
+        GrabOrganButton.OnPressed += _ => OnAddStep?.Invoke(new GrabAnyOrganAutodocStep());
+        GrabPartButton.OnPressed += _ => OnAddStep?.Invoke(new GrabAnyBodyPartAutodocStep());
+        StoreItemButton.OnPressed += _ => OnAddStep?.Invoke(new StoreItemAutodocStep());
+
+        SetLabelButton.OnPressed += _ =>
+        {
+            if (_label is {} dialog)
+            {
+                dialog.MoveToFront();
+                return;
+            }
+
+            var field = "label";
+            var prompt = Loc.GetString("autodoc-add-step-set-label-prompt");
+            var entry = new QuickDialogEntry(field, QuickDialogEntryType.ShortText, prompt);
+            var entries = new List<QuickDialogEntry> { entry };
+            _label = new DialogWindow(SetLabelButton.Text!, entries);
+            _label.OnConfirmed += responses =>
+            {
+                var label = responses[field].Trim();
+                if (label.Length < 1 || label.Length > 20)
+                    return;
+
+                OnAddStep?.Invoke(new SetLabelAutodocStep()
+                {
+                    Label = label
+                });
+            };
+            _label.OnClose += () => _label = null;
+        };
+
+        WaitButton.OnPressed += _ =>
+        {
+            if (_wait is {} dialog)
+            {
+                dialog.MoveToFront();
+                return;
+            }
+
+            var field = "length";
+            var prompt = Loc.GetString("autodoc-add-step-wait-prompt");
+            var entry = new QuickDialogEntry(field, QuickDialogEntryType.Integer, prompt);
+            var entries = new List<QuickDialogEntry> { entry };
+            _wait = new DialogWindow(WaitButton.Text!, entries);
+            _wait.OnConfirmed += responses =>
+            {
+                var length = int.Parse(responses[field].Trim());
+                if (length < 1 || length > 30)
+                    return;
+
+                OnAddStep?.Invoke(new WaitAutodocStep()
+                {
+                    Length = length
+                });
+            };
+            _wait.OnClose += () => _wait = null;
+        };
+    }
+}

+ 49 - 0
Content.Client/_Shitmed/Autodoc/AutodocBoundUserInterface.cs

@@ -0,0 +1,49 @@
+// SPDX-FileCopyrightText: 2024 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2024 deltanedas <39013340+deltanedas@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 deltanedas <@deltanedas:kde.org>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 JohnOakman <sremy2012@hotmail.fr>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Shared._Shitmed.Autodoc;
+using Robust.Client.Player;
+
+namespace Content.Client._Shitmed.Autodoc;
+
+public sealed class AutodocBoundUserInterface : BoundUserInterface
+{
+    [Dependency] private readonly IEntityManager _entMan = default!;
+    [Dependency] private readonly IPlayerManager _player = default!;
+
+    [ViewVariables]
+    private AutodocWindow? _window;
+
+    public AutodocBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+    {
+        _window = new AutodocWindow(owner, _entMan, _player);
+
+        _window.OnCreateProgram += title => SendMessage(new AutodocCreateProgramMessage(title));
+        _window.OnToggleProgramSafety += program => SendMessage(new AutodocToggleProgramSafetyMessage(program));
+        _window.OnRemoveProgram += program => SendMessage(new AutodocRemoveProgramMessage(program));
+
+        _window.OnAddStep += (program, step, index) => SendMessage(new AutodocAddStepMessage(program, step, index));
+        _window.OnRemoveStep += (program, stepIndex) => SendMessage(new AutodocRemoveStepMessage(program, stepIndex));
+
+        _window.OnImportProgram += (program) => SendMessage(new AutodocImportProgramMessage(program));
+
+        _window.OnStart += program => SendMessage(new AutodocStartMessage(program));
+        _window.OnStop += () => SendMessage(new AutodocStopMessage());
+
+        _window.OnClose += () => Close();
+
+        _window.OpenCentered();
+    }
+
+    protected override void Dispose(bool disposing)
+    {
+        base.Dispose(disposing);
+        if (disposing)
+            _window?.Dispose();
+    }
+}

+ 29 - 0
Content.Client/_Shitmed/Autodoc/AutodocProgramWindow.xaml

@@ -0,0 +1,29 @@
+<!--
+SPDX-FileCopyrightText: 2024 deltanedas <39013340+deltanedas@users.noreply.github.com>
+SPDX-FileCopyrightText: 2024 deltanedas <@deltanedas:kde.org>
+SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+SPDX-FileCopyrightText: 2025 JohnOakman <sremy2012@hotmail.fr>
+
+SPDX-License-Identifier: AGPL-3.0-or-later
+-->
+
+<controls:FancyWindow
+        xmlns="https://spacestation14.io"
+        xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
+        Title="{Loc 'autodoc-view-program-title'}"
+        SetSize="600 500">
+    <BoxContainer Orientation="Horizontal">
+        <BoxContainer Orientation="Vertical" Margin="5 5 5 5">
+            <Label Name="ProgramTitle"/> <!-- Set to Program.Title -->
+            <Button Name="SafetyButton" Text="{Loc 'autodoc-safety-enabled'}"/>
+            <Button Name="RemoveButton" StyleClasses="Caution" Text="{Loc 'autodoc-remove-program'}"/>
+            <Button Name="AddStepButton" Text="{Loc 'autodoc-add-step'}"/>
+            <Button Name="RemoveStepButton" StyleClasses="Caution" Text="{Loc 'autodoc-remove-step'}" Disabled="True"/>
+            <Button Name="StartButton" StyleClasses="Caution" Text="{Loc 'autodoc-start-program'}"/>
+            <Button Name="ExportProgramButton" Text="{Loc 'autodoc-export-program'}"/>
+        </BoxContainer>
+        <ScrollContainer HorizontalExpand="True" VerticalExpand="True">
+              <ItemList Name="Steps"/> <!-- Set to Program.Steps -->
+        </ScrollContainer>
+    </BoxContainer>
+</controls:FancyWindow>

+ 210 - 0
Content.Client/_Shitmed/Autodoc/AutodocProgramWindow.xaml.cs

@@ -0,0 +1,210 @@
+// SPDX-FileCopyrightText: 2024 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 JohnOakman <sremy2012@hotmail.fr>
+// SPDX-FileCopyrightText: 2025 deltanedas <39013340+deltanedas@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 deltanedas <@deltanedas:kde.org>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Client.UserInterface.Controls;
+using Content.Shared._Shitmed.Autodoc;
+using Content.Shared._Shitmed.Autodoc.Components;
+using Content.Shared._Shitmed.Autodoc.Systems;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Serialization.Manager;
+using Robust.Shared.Timing;
+using System.IO;
+
+namespace Content.Client._Shitmed.Autodoc;
+
+[GenerateTypedNameReferences]
+public sealed partial class AutodocProgramWindow : FancyWindow
+{
+    [Dependency] private readonly IEntityManager _entMan = default!;
+    [Dependency] private readonly IFileDialogManager _dialogManager = default!;
+    [Dependency] private readonly ILogManager _logMan = default!;
+    [Dependency] private readonly ISerializationManager _serMan = default!;
+    private SharedAutodocSystem _autodoc = default!;
+
+    public event Action? OnToggleSafety;
+    public event Action? OnRemoveProgram;
+    public event Action<IAutodocStep, int>? OnAddStep;
+    public event Action<int>? OnRemoveStep;
+    public event Action? OnStart;
+
+    private EntityUid _owner;
+    private AutodocProgram _program;
+    private int _steps;
+    private bool _safety = true;
+    private ISawmill _sawmill;
+
+    private int? _selected;
+    private AddStepWindow? _addStep;
+
+    public AutodocProgramWindow(EntityUid owner, AutodocProgram program)
+    {
+        RobustXamlLoader.Load(this);
+        IoCManager.InjectDependencies(this);
+
+        _autodoc = _entMan.System<SharedAutodocSystem>();
+
+        _owner = owner;
+        _program = program;
+        _sawmill = _logMan.GetSawmill("autodoc-ui");
+
+        OnClose += () => _addStep?.Close();
+
+        SafetyButton.OnPressed += _ =>
+        {
+            OnToggleSafety?.Invoke();
+            program.SkipFailed ^= true;
+            UpdateSafety();
+        };
+        UpdateSafety();
+
+        RemoveButton.OnPressed += _ =>
+        {
+            OnRemoveProgram?.Invoke();
+            Close();
+        };
+
+        AddStepButton.OnPressed += _ =>
+        {
+            if (_addStep is {} window)
+            {
+                window.MoveToFront();
+                return;
+            }
+
+            _addStep = new AddStepWindow();
+            _addStep.OnAddStep += step =>
+            {
+                // if nothing is selected add it to the end
+                // if something is selected, insert just before it
+                var index = _selected ?? program.Steps.Count;
+                OnAddStep?.Invoke(step, index);
+                _selected = null;
+                RemoveButton.Disabled = true;
+                program.Steps.Insert(index, step);
+                UpdateSteps();
+            };
+            _addStep.OnClose += () => _addStep = null;
+            _addStep.OpenCentered();
+        };
+
+        RemoveStepButton.OnPressed += _ =>
+        {
+            if (_selected is not {} index)
+                return;
+
+            _selected = null;
+            RemoveStepButton.Disabled = true;
+            OnRemoveStep?.Invoke(index);
+
+            // Steps.RemoveChild throws for no fucking reason so rebuild it
+            program.Steps.RemoveAt(index);
+            UpdateSteps();
+        };
+
+        StartButton.OnPressed += _ =>
+        {
+            OnStart?.Invoke();
+            Close();
+        };
+
+        ExportProgramButton.OnPressed += _ =>
+        {
+            ExportProgram();
+        };
+
+        Steps.OnItemSelected += args =>
+        {
+            _selected = args.ItemIndex;
+            RemoveStepButton.Disabled = false;
+        };
+        Steps.OnItemDeselected += _ =>
+        {
+            _selected = null;
+            RemoveStepButton.Disabled = true;
+        };
+
+        UpdateSteps();
+        UpdateSafety();
+    }
+
+    private async void ExportProgram()
+    {
+        if (await _dialogManager.SaveFile(new FileDialogFilters(new FileDialogFilters.Group("yml"))) is not {} file)
+            return;
+
+        try
+        {
+            var node = _serMan.WriteValue(_program.GetType(), _program);
+            await using var writer = new StreamWriter(file.fileStream);
+            node.Write(writer);
+        }
+        catch (Exception e)
+        {
+            _sawmill.Error($"Error when exporting program: {e}");
+        }
+        finally
+        {
+            await file.fileStream.DisposeAsync();
+        }
+    }
+
+    private void UpdateSafety()
+    {
+        var safety = !_program.SkipFailed;
+        if (safety == _safety)
+            return;
+
+        _safety = safety;
+
+        SafetyButton.Text = Loc.GetString("autodoc-safety-" + (safety ? "enabled" : "disabled"));
+        if (safety)
+            SafetyButton.RemoveStyleClass("Caution");
+        else
+            SafetyButton.AddStyleClass("Caution");
+    }
+
+    private void UpdateSteps()
+    {
+        var count = _program.Steps.Count;
+        if (_steps == count)
+            return;
+
+        _steps = count;
+
+        Steps.Clear();
+
+        for (int i = 0; i < count; i++)
+        {
+            Steps.AddItem(_program.Steps[i].Title);
+        }
+
+        if (!_entMan.TryGetComponent<AutodocComponent>(_owner, out var comp))
+            return;
+
+        AddStepButton.Disabled = count >= comp.MaxProgramSteps;
+    }
+
+    private void UpdateStart()
+    {
+        if (!_entMan.TryGetComponent<AutodocComponent>(_owner, out var comp))
+            return;
+
+        StartButton.Disabled = _autodoc.GetPatient((_owner, comp)) == null;
+    }
+
+    protected override void FrameUpdate(FrameEventArgs args)
+    {
+        base.FrameUpdate(args);
+
+        UpdateSteps();
+        UpdateSafety();
+        UpdateStart();
+    }
+}

+ 24 - 0
Content.Client/_Shitmed/Autodoc/AutodocWindow.xaml

@@ -0,0 +1,24 @@
+<!--
+SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+SPDX-FileCopyrightText: 2025 JohnOakman <sremy2012@hotmail.fr>
+SPDX-FileCopyrightText: 2025 deltanedas <39013340+deltanedas@users.noreply.github.com>
+SPDX-FileCopyrightText: 2025 deltanedas <@deltanedas:kde.org>
+
+SPDX-License-Identifier: AGPL-3.0-or-later
+-->
+
+<controls:FancyWindow
+        xmlns="https://spacestation14.io"
+        xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
+        Title="{Loc 'autodoc-title'}">
+    <BoxContainer Orientation="Vertical">
+        <ScrollContainer MinWidth="600" MinHeight="400" VerticalExpand="True">
+            <BoxContainer Name="Programs" Orientation="Vertical"/> <!-- Populated at runtime -->
+        </ScrollContainer>
+        <BoxContainer Orientation="Horizontal" Margin="5 5 5 5" Align="Center">
+            <Button Name="CreateProgramButton" StyleClasses="OpenRight" Text="{Loc 'autodoc-create-program'}"/>
+            <Button Name="ImportProgramButton" StyleClasses="OpenLeft" Text="{Loc 'autodoc-import-program'}"/>
+        </BoxContainer>
+        <Button Name="AbortButton" MaxWidth="350" Text="{Loc 'autodoc-abort-program'}" Disabled="True"/>
+    </BoxContainer>
+</controls:FancyWindow>

+ 231 - 0
Content.Client/_Shitmed/Autodoc/AutodocWindow.xaml.cs

@@ -0,0 +1,231 @@
+// SPDX-FileCopyrightText: 2024 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 JohnOakman <sremy2012@hotmail.fr>
+// SPDX-FileCopyrightText: 2025 deltanedas <39013340+deltanedas@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 deltanedas <@deltanedas:kde.org>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Administration;
+using Content.Shared._Shitmed.Autodoc;
+using Content.Shared._Shitmed.Autodoc.Components;
+using Content.Shared._Shitmed.Autodoc.Systems;
+using Robust.Client.AutoGenerated;
+using Robust.Client.Player;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Timing;
+using Robust.Client.UserInterface;
+using Robust.Shared.Serialization.Manager;
+using Robust.Shared.Utility;
+using System.IO;
+using YamlDotNet.RepresentationModel;
+using Robust.Shared.Serialization.Markdown;
+
+namespace Content.Client._Shitmed.Autodoc;
+
+[GenerateTypedNameReferences]
+public sealed partial class AutodocWindow : FancyWindow
+{
+    [Dependency] private readonly IEntityManager _entMan = default!;
+    [Dependency] private readonly IFileDialogManager _dialogMan = default!;
+    [Dependency] private readonly IPlayerManager _player = default!;
+    [Dependency] private readonly ISerializationManager _serMan = default!;
+    [Dependency] private readonly ILogManager _logMan = default!;
+    private SharedAutodocSystem _autodoc;
+
+    private EntityUid _owner;
+    private bool _active;
+    private int _programCount = 0;
+    private ISawmill _sawmill;
+
+    public event Action<string>? OnCreateProgram;
+    public event Action<int>? OnToggleProgramSafety;
+    public event Action<int>? OnRemoveProgram;
+    public event Action<int, IAutodocStep, int>? OnAddStep;
+    public event Action<int, int>? OnRemoveStep;
+    public event Action<int>? OnStart;
+    public event Action? OnStop;
+    public event Action<AutodocProgram>? OnImportProgram;
+
+    private DialogWindow? _dialog;
+    private AutodocProgramWindow? _currentProgram;
+
+    public AutodocWindow(EntityUid owner, IEntityManager entMan, IPlayerManager player)
+    {
+        RobustXamlLoader.Load(this);
+        IoCManager.InjectDependencies(this);
+
+        _entMan = entMan;
+        _player = player;
+        _autodoc = entMan.System<SharedAutodocSystem>();
+        _sawmill = _logMan.GetSawmill("autodoc-ui");
+
+        _owner = owner;
+
+        OnClose += () =>
+        {
+            _dialog?.Close();
+            _currentProgram?.Close();
+        };
+
+        ImportProgramButton.OnPressed += _ =>
+        {
+            ImportProgram();
+        };
+
+        CreateProgramButton.OnPressed += _ =>
+        {
+            if (_dialog != null)
+            {
+                _dialog.MoveToFront();
+                return;
+            }
+
+            if (!_entMan.TryGetComponent<AutodocComponent>(_owner, out var comp))
+                return;
+
+            var field = "title";
+            var prompt = Loc.GetString("autodoc-program-title");
+            var placeholder = Loc.GetString("autodoc-program-title-placeholder", ("number", comp.Programs.Count + 1));
+            var entry = new QuickDialogEntry(field, QuickDialogEntryType.ShortText, prompt, placeholder);
+            var entries = new List<QuickDialogEntry> { entry };
+            _dialog = new DialogWindow(CreateProgramButton.Text!, entries);
+            _dialog.OnConfirmed += responses =>
+            {
+                var title = responses[field].Trim();
+                if (title.Length < 1 || title.Length > comp.MaxProgramTitleLength)
+                    return;
+
+                OnCreateProgram?.Invoke(title);
+            };
+
+            // prevent MoveToFront being called on a closed window and double closing
+            _dialog.OnClose += () => _dialog = null;
+        };
+
+        AbortButton.AddStyleClass("Caution");
+        AbortButton.OnPressed += _ => OnStop?.Invoke();
+
+        UpdateActive();
+        UpdatePrograms();
+    }
+
+    public void UpdateActive()
+    {
+        if (!_entMan.TryGetComponent<AutodocComponent>(_owner, out var comp))
+            return;
+
+        // UI must be in the inactive state by default, since this wont run when inactive at startup
+        var active = _entMan.HasComponent<ActiveAutodocComponent>(_owner);
+        if (active == _active)
+            return;
+
+        _active = active;
+
+        CreateProgramButton.Disabled = active || _programCount >= comp.MaxPrograms;
+        AbortButton.Disabled = !active;
+        foreach (var button in Programs.Children)
+        {
+            ((Button) button).Disabled = active;
+        }
+
+        if (!active)
+            return;
+
+        // close windows that can only be open when inactive
+        _dialog?.Close();
+        _currentProgram?.Close();
+    }
+
+    private void UpdatePrograms()
+    {
+        if (!_entMan.TryGetComponent<AutodocComponent>(_owner, out var comp))
+            return;
+
+        var count = comp.Programs.Count;
+        if (count == _programCount)
+            return;
+
+        _programCount = count;
+
+        CreateProgramButton.Disabled = _active || _programCount >= comp.MaxPrograms;
+
+        Programs.RemoveAllChildren();
+        for (int i = 0; i < comp.Programs.Count; i++)
+        {
+            var button = new Button()
+            {
+                Text = comp.Programs[i].Title
+            };
+            var index = i;
+            button.OnPressed += _ => OpenProgram(index);
+            button.Disabled = _active;
+            Programs.AddChild(button);
+        }
+    }
+
+    private async void ImportProgram()
+    {
+        if (await _dialogMan.OpenFile(new FileDialogFilters(new FileDialogFilters.Group("yml"))) is not {} file)
+            return;
+
+        try
+        {
+            using var reader = new StreamReader(file, EncodingHelpers.UTF8);
+            var yamlStream = new YamlStream();
+            yamlStream.Load(reader);
+            var root = yamlStream.Documents[0].RootNode;
+            var program = _serMan.Read<AutodocProgram>(root.ToDataNode(), notNullableOverride: true);
+            OnImportProgram?.Invoke(program);
+        }
+        catch (Exception e)
+        {
+            _sawmill.Error($"Error when importing program: {e}");
+        }
+
+    }
+
+    private void OpenProgram(int index)
+    {
+        if (!_entMan.TryGetComponent<AutodocComponent>(_owner, out var comp))
+            return;
+
+        // no editing multiple programs at once
+        if (_currentProgram is {} existing)
+            existing.Close();
+
+        var window = new AutodocProgramWindow(_owner, comp.Programs[index]);
+        window.OnToggleSafety += () => OnToggleProgramSafety?.Invoke(index);
+        window.OnRemoveProgram += () =>
+        {
+            OnRemoveProgram?.Invoke(index);
+            Programs.RemoveChild(index);
+        };
+        window.OnAddStep += (step, stepIndex) => OnAddStep?.Invoke(index, step, stepIndex);
+        window.OnRemoveStep += step => OnRemoveStep?.Invoke(index, step);
+        window.OnStart += () =>
+        {
+            if (_active)
+                return;
+
+            OnStart?.Invoke(index);
+
+            // predict it starting the program
+            _entMan.EnsureComponent<ActiveAutodocComponent>(_owner);
+        };
+        window.OnClose += () => _currentProgram = null;
+        _currentProgram = window;
+
+        window.OpenCentered();
+    }
+
+    protected override void FrameUpdate(FrameEventArgs args)
+    {
+        base.FrameUpdate(args);
+
+        UpdateActive();
+        UpdatePrograms();
+    }
+}

+ 28 - 0
Content.Client/_Shitmed/Autodoc/PickSurgeryWindow.xaml

@@ -0,0 +1,28 @@
+<!--
+SPDX-FileCopyrightText: 2024 deltanedas <39013340+deltanedas@users.noreply.github.com>
+SPDX-FileCopyrightText: 2024 deltanedas <@deltanedas:kde.org>
+SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+
+SPDX-License-Identifier: AGPL-3.0-or-later
+-->
+
+<controls:FancyWindow
+        xmlns="https://spacestation14.io"
+        xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
+        Title="{Loc 'autodoc-add-step-surgery'}"
+        MinSize="600 400">
+    <BoxContainer Orientation="Horizontal" VerticalExpand="True" HorizontalExpand="True">
+        <BoxContainer Orientation="Vertical" MinWidth="200" HorizontalAlignment="Center">
+            <BoxContainer Name="Symmetry" Orientation="Horizontal" HorizontalExpand="True"/> <!-- Populated with a radio option of BodyPartSymmetry values -->
+            <PanelContainer StyleClasses="LowDivider" Margin="0 5 0 5"/>
+            <ScrollContainer VerticalExpand="True" HorizontalExpand="True">
+                <ItemList Name="Parts"/> <!-- Populated with BodyPartType values -->
+            </ScrollContainer>
+            <PanelContainer StyleClasses="LowDivider" Margin="0 5 0 5"/>
+            <Button Name="SubmitButton" Text="{Loc 'autodoc-submit'}" Disabled="True"/>
+        </BoxContainer>
+        <ScrollContainer HorizontalExpand="True">
+            <ItemList Name="Surgeries"/> <!-- Populated with SharedSurgerySystem.AllSurgeries -->
+        </ScrollContainer>
+    </BoxContainer>
+</controls:FancyWindow>

+ 154 - 0
Content.Client/_Shitmed/Autodoc/PickSurgeryWindow.xaml.cs

@@ -0,0 +1,154 @@
+// SPDX-FileCopyrightText: 2024 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 deltanedas <39013340+deltanedas@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 deltanedas <@deltanedas:kde.org>
+// SPDX-FileCopyrightText: 2025 pathetic meowmeow <uhhadd@gmail.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Client.UserInterface.Controls;
+using Content.Shared._Shitmed.Autodoc;
+using Content.Shared._Shitmed.Medical.Surgery;
+using Content.Shared._Shitmed.Medical.Surgery.Conditions;
+using Content.Shared.Body.Part;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client._Shitmed.Autodoc;
+
+[GenerateTypedNameReferences]
+public sealed partial class PickSurgeryWindow : FancyWindow
+{
+    [Dependency] private readonly IEntityManager _entMan = default!;
+    [Dependency] private readonly IPrototypeManager _proto = default!;
+    private readonly SharedSurgerySystem _surgery;
+
+    public event Action<IAutodocStep>? OnAddStep;
+
+    private BodyPartType? _part;
+    private BodyPartSymmetry? _symmetry;
+    private EntProtoId<SurgeryComponent>? _surgeryId;
+
+    public PickSurgeryWindow()
+    {
+        RobustXamlLoader.Load(this);
+        IoCManager.InjectDependencies(this);
+
+        _surgery = _entMan.System<SharedSurgerySystem>();
+
+        OnAddStep += _ => Close();
+
+        var options = new RadioOptions<BodyPartSymmetry?>(RadioOptionsLayout.Horizontal);
+        options.ButtonStyle = "OpenBoth";
+        options.FirstButtonStyle = "OpenRight";
+        options.LastButtonStyle = "OpenLeft";
+        options.AddItem(Loc.GetString("autodoc-body-symmetry-ignored"), null);
+
+        foreach (var symmetry in Enum.GetValues<BodyPartSymmetry>())
+        {
+            var name = Loc.GetString("autodoc-body-symmetry-" + symmetry.ToString());
+            options.AddItem(name, symmetry);
+        }
+
+        Symmetry.AddChild(options);
+
+        options.OnItemSelected += args =>
+        {
+            args.Button.Select(args.Id);
+            _symmetry = args.Button.SelectedValue;
+            UpdateSurgeries();
+        };
+
+        foreach (var part in Enum.GetValues<BodyPartType>())
+        {
+            var name = Loc.GetString("autodoc-body-part-" + part.ToString());
+            Parts.AddItem(name, metadata: part);
+        }
+
+        Parts.OnItemSelected += args =>
+        {
+            if (Parts[args.ItemIndex].Metadata is not BodyPartType part)
+                return;
+
+            _part = part;
+            UpdateSubmit();
+            UpdateSurgeries();
+        };
+        Parts.OnItemDeselected += _ =>
+        {
+            _part = null;
+            SubmitButton.Disabled = true;
+        };
+
+        Surgeries.OnItemSelected += args =>
+        {
+            if (Surgeries[args.ItemIndex].Metadata is not EntProtoId<SurgeryComponent> id)
+                return;
+
+            _surgeryId = id;
+            UpdateSubmit();
+        };
+        Surgeries.OnItemDeselected += _ =>
+        {
+            _surgeryId = null;
+            SubmitButton.Disabled = true;
+        };
+
+        SubmitButton.OnPressed += _ =>
+        {
+            var step = new SurgeryAutodocStep()
+            {
+                Part = _part!.Value,
+                Symmetry = _symmetry,
+                Surgery = _surgeryId!.Value
+            };
+            OnAddStep?.Invoke(step);
+        };
+
+        UpdateSurgeries();
+    }
+
+    // doesn't handle prototype reload so you have to reopen the window to see new surgeries
+    private void UpdateSurgeries()
+    {
+        Surgeries.Clear();
+
+        foreach (var id in _surgery.AllSurgeries)
+        {
+            var name = _proto.Index(id).Name;
+            var protoId = new EntProtoId<SurgeryComponent>(id);
+            if (_part is not BodyPartType part)
+            {
+                Surgeries.AddItem(name, metadata: protoId);
+                continue;
+            }
+
+            var ent = _surgery.GetSingleton(protoId);
+            if (ent is null)
+                continue;
+
+            if (!_entMan.TryGetComponent<SurgeryPartConditionComponent>(ent.Value, out var comp))
+            {
+                Surgeries.AddItem(name, metadata: protoId);
+                continue;
+            }
+
+            var partOk = comp.Part == part;
+            var symmetryOk = (comp.Symmetry == null || _symmetry == null) ? true : comp.Symmetry == _symmetry;
+
+            var passesFilter = (partOk && symmetryOk) ^ comp.Inverse;
+
+            if (passesFilter)
+                Surgeries.AddItem(name, metadata: protoId);
+        }
+        Surgeries.SortItemsByText();
+    }
+
+    private void UpdateSubmit()
+    {
+        // symmetry is optional, others are not
+        SubmitButton.Disabled = _part == null || _surgeryId == null;
+    }
+}

+ 11 - 0
Content.Client/_Shitmed/Autodoc/Systems/AutodocSystem.cs

@@ -0,0 +1,11 @@
+// SPDX-FileCopyrightText: 2024 deltanedas <39013340+deltanedas@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 deltanedas <@deltanedas:kde.org>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Shared._Shitmed.Autodoc.Systems;
+
+namespace Content.Client._Shitmed.Autodoc.Systems;
+
+public sealed class AutodocSystem : SharedAutodocSystem;

+ 8 - 0
Content.Client/_Shitmed/Body/Components/BrainComponent.cs

@@ -0,0 +1,8 @@
+// SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace Content.Client._Shitmed.Body.Components;
+[RegisterComponent]
+public sealed partial class BrainComponent : Component { }

+ 8 - 0
Content.Client/_Shitmed/Body/Components/LungComponent.cs

@@ -0,0 +1,8 @@
+// SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace Content.Client._Shitmed.Body.Components;
+[RegisterComponent]
+public sealed partial class LungComponent : Component { }

+ 8 - 0
Content.Client/_Shitmed/Body/Components/StomachComponent.cs

@@ -0,0 +1,8 @@
+// SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace Content.Client._Shitmed.Body.Components;
+[RegisterComponent]
+public sealed partial class StomachComponent : Component { }

+ 25 - 0
Content.Client/_Shitmed/Choice/UI/ChoiceControl.xaml

@@ -0,0 +1,25 @@
+<!--
+SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+SPDX-FileCopyrightText: 2025 Piras314 <p1r4s@proton.me>
+SPDX-FileCopyrightText: 2025 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+
+SPDX-License-Identifier: AGPL-3.0-or-later
+-->
+
+<ui:ChoiceControl
+    xmlns="https://spacestation14.io"
+    xmlns:ui="clr-namespace:Content.Client._Shitmed.Choice.UI">
+    <BoxContainer Orientation="Horizontal">
+        <Button Name="Button" Access="Public"
+                HorizontalExpand="True" VerticalExpand="False"
+                StyleClasses="ButtonSquare" Margin="0">
+            <BoxContainer Orientation="Horizontal" Margin="0">
+                <TextureRect Name="Texture" Access="Public"
+                             HorizontalExpand="False" VerticalExpand="False"
+                             Margin="1"/>
+                <Control MinWidth="5"/>
+                <RichTextLabel Name="NameLabel" Access="Public" VerticalAlignment="Center"/>
+            </BoxContainer>
+        </Button>
+    </BoxContainer>
+</ui:ChoiceControl>

+ 32 - 0
Content.Client/_Shitmed/Choice/UI/ChoiceControl.xaml.cs

@@ -0,0 +1,32 @@
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2025 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Robust.Client.AutoGenerated;
+using Robust.Client.Graphics;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Utility;
+
+namespace Content.Client._Shitmed.Choice.UI;
+
+[GenerateTypedNameReferences]
+[Virtual]
+public partial class ChoiceControl : Control
+{
+    public ChoiceControl() => RobustXamlLoader.Load(this);
+
+    public void Set(string name, Texture? texture)
+    {
+        NameLabel.SetMessage(name);
+        Texture.Texture = texture;
+    }
+
+    public void Set(FormattedMessage msg, Texture? texture)
+    {
+        NameLabel.SetMessage(msg);
+        Texture.Texture = texture;
+    }
+}

+ 362 - 0
Content.Client/_Shitmed/Medical/Surgery/SurgeryBui.cs

@@ -0,0 +1,362 @@
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2025 deltanedas <39013340+deltanedas@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 deltanedas <@deltanedas:kde.org>
+// SPDX-FileCopyrightText: 2025 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Client._Shitmed.Choice.UI;
+using Content.Client.Administration.UI.CustomControls;
+using Content.Shared._Shitmed.Medical.Surgery;
+using Content.Shared.Body.Components;
+using Content.Shared.Body.Part;
+using JetBrains.Annotations;
+using Robust.Client.GameObjects;
+using Robust.Client.Player;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Client._Shitmed.Medical.Surgery;
+
+[UsedImplicitly]
+public sealed class SurgeryBui : BoundUserInterface
+{
+    [Dependency] private readonly IEntityManager _entities = default!;
+    [Dependency] private readonly IPlayerManager _player = default!;
+
+    private readonly SurgerySystem _system;
+    [ViewVariables]
+    private SurgeryWindow? _window;
+    private EntityUid? _part;
+    private bool _isBody;
+    private (EntityUid Ent, EntProtoId Proto)? _surgery;
+    private readonly List<EntProtoId> _previousSurgeries = new();
+    public SurgeryBui(EntityUid owner, Enum uiKey) : base(owner, uiKey) => _system = _entities.System<SurgerySystem>();
+
+    protected override void ReceiveMessage(BoundUserInterfaceMessage message)
+    {
+        if (_window is null
+            || message is not SurgeryBuiRefreshMessage)
+            return;
+
+        RefreshUI();
+    }
+
+    protected override void UpdateState(BoundUserInterfaceState state)
+    {
+        if (state is not SurgeryBuiState s)
+            return;
+
+        Update(s);
+    }
+
+    protected override void Dispose(bool disposing)
+    {
+        base.Dispose(disposing);
+        if (disposing)
+            _window?.Dispose();
+    }
+
+    private void Update(SurgeryBuiState state)
+    {
+        if (_window == null)
+        {
+            _window = new SurgeryWindow();
+            _window.OnClose += Close;
+            _window.Title = Loc.GetString("surgery-ui-window-title");
+
+            _window.PartsButton.OnPressed += _ =>
+            {
+                _part = null;
+                _isBody = false;
+                _surgery = null;
+                _previousSurgeries.Clear();
+                View(ViewType.Parts);
+            };
+
+            _window.SurgeriesButton.OnPressed += _ =>
+            {
+                _surgery = null;
+                _previousSurgeries.Clear();
+
+                if (!_entities.TryGetNetEntity(_part, out var netPart)
+                    || State is not SurgeryBuiState s
+                    || !s.Choices.TryGetValue(netPart.Value, out var surgeries))
+                    return;
+
+                OnPartPressed(netPart.Value, surgeries);
+            };
+
+            _window.StepsButton.OnPressed += _ =>
+            {
+                if (!_entities.TryGetNetEntity(_part, out var netPart)
+                    || _previousSurgeries.Count == 0)
+                    return;
+
+                var last = _previousSurgeries[^1];
+                _previousSurgeries.RemoveAt(_previousSurgeries.Count - 1);
+
+                if (_system.GetSingleton(last) is not { } previousId
+                    || !_entities.TryGetComponent(previousId, out SurgeryComponent? previous))
+                    return;
+
+                OnSurgeryPressed((previousId, previous), netPart.Value, last);
+            };
+        }
+
+        _window.Surgeries.DisposeAllChildren();
+        _window.Steps.DisposeAllChildren();
+        _window.Parts.DisposeAllChildren();
+        View(ViewType.Parts);
+
+        var oldSurgery = _surgery;
+        var oldPart = _part;
+        _part = null;
+        _surgery = null;
+
+        var options = new List<(NetEntity netEntity, EntityUid entity, string Name, BodyPartType? PartType)>();
+        foreach (var choice in state.Choices.Keys)
+            if (_entities.TryGetEntity(choice, out var ent))
+            {
+                if (_entities.TryGetComponent(ent, out BodyPartComponent? part))
+                    options.Add((choice, ent.Value, _entities.GetComponent<MetaDataComponent>(ent.Value).EntityName, part.PartType));
+                else if (_entities.TryGetComponent(ent, out BodyComponent? body))
+                    options.Add((choice, ent.Value, _entities.GetComponent<MetaDataComponent>(ent.Value).EntityName, null));
+            }
+
+        options.Sort((a, b) =>
+        {
+            int GetScore(BodyPartType? partType)
+            {
+                return partType switch
+                {
+                    BodyPartType.Head => 1,
+                    BodyPartType.Torso => 2,
+                    BodyPartType.Arm => 3,
+                    BodyPartType.Hand => 4,
+                    BodyPartType.Leg => 5,
+                    BodyPartType.Foot => 6,
+                    // BodyPartType.Tail => 7, No tails yet!
+                    BodyPartType.Other => 8,
+                    _ => 9
+                };
+            }
+
+            return GetScore(a.PartType) - GetScore(b.PartType);
+        });
+
+        foreach (var (netEntity, entity, partName, _) in options)
+        {
+            //var netPart = _entities.GetNetEntity(part.Owner);
+            var surgeries = state.Choices[netEntity];
+            var partButton = new ChoiceControl();
+
+            partButton.Set(partName, null);
+            partButton.Button.OnPressed += _ => OnPartPressed(netEntity, surgeries);
+
+            _window.Parts.AddChild(partButton);
+
+            foreach (var surgeryId in surgeries)
+            {
+                if (_system.GetSingleton(surgeryId) is not { } surgery ||
+                    !_entities.TryGetComponent(surgery, out SurgeryComponent? surgeryComp))
+                    continue;
+
+                if (oldPart == entity && oldSurgery?.Proto == surgeryId)
+                    OnSurgeryPressed((surgery, surgeryComp), netEntity, surgeryId);
+            }
+
+            if (oldPart == entity && oldSurgery == null)
+                OnPartPressed(netEntity, surgeries);
+        }
+
+
+        if (!_window.IsOpen)
+            _window.OpenCentered();
+    }
+
+    private void AddStep(EntProtoId stepId, NetEntity netPart, EntProtoId surgeryId)
+    {
+        if (_window == null
+            || _system.GetSingleton(stepId) is not { } step)
+            return;
+
+        var stepName = new FormattedMessage();
+        stepName.AddText(_entities.GetComponent<MetaDataComponent>(step).EntityName);
+        var stepButton = new SurgeryStepButton { Step = step };
+        stepButton.Button.OnPressed += _ => SendMessage(new SurgeryStepChosenBuiMsg(netPart, surgeryId, stepId, _isBody));
+
+        _window.Steps.AddChild(stepButton);
+    }
+
+    private void OnSurgeryPressed(Entity<SurgeryComponent> surgery, NetEntity netPart, EntProtoId surgeryId)
+    {
+        if (_window == null)
+            return;
+
+        _part = _entities.GetEntity(netPart);
+        _isBody = _entities.HasComponent<BodyComponent>(_part);
+        _surgery = (surgery, surgeryId);
+
+        _window.Steps.DisposeAllChildren();
+
+        // This apparently does not consider if theres multiple surgery requirements in one surgery. Maybe thats fine.
+        if (surgery.Comp.Requirement is { } requirementId && _system.GetSingleton(requirementId) is { } requirement)
+        {
+            var label = new ChoiceControl();
+            label.Button.OnPressed += _ =>
+            {
+                _previousSurgeries.Add(surgeryId);
+
+                if (_entities.TryGetComponent(requirement, out SurgeryComponent? requirementComp))
+                    OnSurgeryPressed((requirement, requirementComp), netPart, requirementId);
+            };
+
+            var msg = new FormattedMessage();
+            var surgeryName = _entities.GetComponent<MetaDataComponent>(requirement).EntityName;
+            msg.AddMarkup($"[bold]{Loc.GetString("surgery-ui-window-require")}: {surgeryName}[/bold]");
+            label.Set(msg, null);
+
+            _window.Steps.AddChild(label);
+            _window.Steps.AddChild(new HSeparator { Margin = new Thickness(0, 0, 0, 1) });
+        }
+        foreach (var stepId in surgery.Comp.Steps)
+            AddStep(stepId, netPart, surgeryId);
+
+        View(ViewType.Steps);
+        RefreshUI();
+    }
+
+    private void OnPartPressed(NetEntity netPart, List<EntProtoId> surgeryIds)
+    {
+        if (_window == null)
+            return;
+
+        _part = _entities.GetEntity(netPart);
+        _isBody = _entities.HasComponent<BodyComponent>(_part);
+        _window.Surgeries.DisposeAllChildren();
+
+        var surgeries = new List<(Entity<SurgeryComponent> Ent, EntProtoId Id, string Name)>();
+        foreach (var surgeryId in surgeryIds)
+        {
+            if (_system.GetSingleton(surgeryId) is not { } surgery ||
+                !_entities.TryGetComponent(surgery, out SurgeryComponent? surgeryComp))
+            {
+                continue;
+            }
+
+            var name = _entities.GetComponent<MetaDataComponent>(surgery).EntityName;
+            surgeries.Add(((surgery, surgeryComp), surgeryId, name));
+        }
+
+        surgeries.Sort((a, b) =>
+        {
+            var priority = a.Ent.Comp.Priority.CompareTo(b.Ent.Comp.Priority);
+            if (priority != 0)
+                return priority;
+
+            return string.Compare(a.Name, b.Name, StringComparison.Ordinal);
+        });
+
+        foreach (var surgery in surgeries)
+        {
+            var surgeryButton = new ChoiceControl();
+            surgeryButton.Set(surgery.Name, null);
+
+            surgeryButton.Button.OnPressed += _ => OnSurgeryPressed(surgery.Ent, netPart, surgery.Id);
+            _window.Surgeries.AddChild(surgeryButton);
+        }
+
+        RefreshUI();
+        View(ViewType.Surgeries);
+    }
+
+    private void RefreshUI()
+    {
+        if (_window == null
+            || !_window.IsOpen
+            || _part == null
+            || !_entities.HasComponent<SurgeryComponent>(_surgery?.Ent)
+            || !_entities.TryGetComponent(_player.LocalEntity ?? EntityUid.Invalid, out SurgeryTargetComponent? surgeryComp)
+            || !surgeryComp.CanOperate)
+            return;
+
+        var next = _system.GetNextStep(Owner, _part.Value, _surgery.Value.Ent);
+        var i = 0;
+        foreach (var child in _window.Steps.Children)
+        {
+            if (child is not SurgeryStepButton stepButton)
+                continue;
+
+            var status = StepStatus.Incomplete;
+            if (next == null)
+                status = StepStatus.Complete;
+            else if (next.Value.Surgery.Owner != _surgery.Value.Ent)
+                status = StepStatus.Incomplete;
+            else if (next.Value.Step == i)
+                status = StepStatus.Next;
+            else if (i < next.Value.Step)
+                status = StepStatus.Complete;
+
+            stepButton.Button.Disabled = status != StepStatus.Next;
+
+            var stepName = new FormattedMessage();
+            stepName.AddText(_entities.GetComponent<MetaDataComponent>(stepButton.Step).EntityName);
+
+            if (status == StepStatus.Complete)
+                stepButton.Button.Modulate = Color.Green;
+            else
+            {
+                stepButton.Button.Modulate = Color.White;
+                if (_player.LocalEntity is { } player
+                    && status == StepStatus.Next
+                    && !_system.CanPerformStep(player, Owner, _part.Value, stepButton.Step, false, out var popup, out var reason, out _))
+                    stepButton.ToolTip = popup;
+            }
+
+            var texture = _entities.GetComponentOrNull<SpriteComponent>(stepButton.Step)?.Icon?.Default;
+            stepButton.Set(stepName, texture);
+            i++;
+        }
+    }
+
+    private void View(ViewType type)
+    {
+        if (_window == null)
+            return;
+
+        _window.PartsButton.Parent!.Margin = new Thickness(0, 0, 0, 10);
+
+        _window.Parts.Visible = type == ViewType.Parts;
+        _window.PartsButton.Disabled = type == ViewType.Parts;
+
+        _window.Surgeries.Visible = type == ViewType.Surgeries;
+        _window.SurgeriesButton.Disabled = type != ViewType.Steps;
+
+        _window.Steps.Visible = type == ViewType.Steps;
+        _window.StepsButton.Disabled = type != ViewType.Steps || _previousSurgeries.Count == 0;
+
+        if (_entities.TryGetComponent(_part, out MetaDataComponent? partMeta) &&
+            _entities.TryGetComponent(_surgery?.Ent, out MetaDataComponent? surgeryMeta))
+            _window.Title = $"Surgery - {partMeta.EntityName}, {surgeryMeta.EntityName}";
+        else if (partMeta != null)
+            _window.Title = $"Surgery - {partMeta.EntityName}";
+        else
+            _window.Title = "Surgery";
+    }
+
+    private enum ViewType
+    {
+        Parts,
+        Surgeries,
+        Steps
+    }
+
+    private enum StepStatus
+    {
+        Next,
+        Complete,
+        Incomplete
+    }
+}

+ 11 - 0
Content.Client/_Shitmed/Medical/Surgery/SurgeryStepButton.xaml

@@ -0,0 +1,11 @@
+<!--
+SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+
+SPDX-License-Identifier: AGPL-3.0-or-later
+-->
+
+<controls:SurgeryStepButton
+    xmlns="https://spacestation14.io"
+    xmlns:controls="clr-namespace:Content.Client._Shitmed.Medical.Surgery">
+</controls:SurgeryStepButton>

+ 22 - 0
Content.Client/_Shitmed/Medical/Surgery/SurgeryStepButton.xaml.cs

@@ -0,0 +1,22 @@
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2025 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Client._Shitmed.Choice.UI;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client._Shitmed.Medical.Surgery;
+
+[GenerateTypedNameReferences]
+public sealed partial class SurgeryStepButton : ChoiceControl
+{
+    public EntityUid Step { get; set; }
+
+    public SurgeryStepButton()
+    {
+        RobustXamlLoader.Load(this);
+    }
+}

+ 16 - 0
Content.Client/_Shitmed/Medical/Surgery/SurgerySystem.cs

@@ -0,0 +1,16 @@
+// SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Shared._Shitmed.Medical.Surgery;
+
+namespace Content.Client._Shitmed.Medical.Surgery;
+
+public sealed class SurgerySystem : SharedSurgerySystem
+{
+    public override void Initialize()
+    {
+        base.Initialize();
+    }
+}

+ 30 - 0
Content.Client/_Shitmed/Medical/Surgery/SurgeryWindow.xaml

@@ -0,0 +1,30 @@
+<!--
+SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+
+SPDX-License-Identifier: AGPL-3.0-or-later
+-->
+
+<controls:SurgeryWindow
+    xmlns="https://spacestation14.io"
+    xmlns:controls="clr-namespace:Content.Client._Shitmed.Medical.Surgery"
+    xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
+    xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
+    MinSize="400 400">
+    <BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
+        <BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="0 0 0 10">
+            <Button Name="PartsButton" Access="Public" Text="{Loc 'surgery-ui-window-parts'}"
+                    HorizontalExpand="True" StyleClasses="OpenBoth" />
+            <Button Name="SurgeriesButton" Access="Public" Text="{Loc 'surgery-ui-window-surgeries'}"
+                    HorizontalExpand="True" StyleClasses="OpenBoth" />
+            <Button Name="StepsButton" Access="Public" Text="{Loc 'surgery-ui-window-steps'}"
+                    HorizontalExpand="True" StyleClasses="OpenBoth" />
+        </BoxContainer>
+        <cc:HSeparator />
+        <ScrollContainer VScrollEnabled="True" HorizontalExpand="True" VerticalExpand="True">
+            <BoxContainer Name="Parts" Access="Public" Orientation="Vertical" Visible="False" />
+            <BoxContainer Name="Surgeries" Access="Public" Orientation="Vertical" Visible="False" />
+            <BoxContainer Name="Steps" Access="Public" Orientation="Vertical" Visible="False" />
+        </ScrollContainer>
+    </BoxContainer>
+</controls:SurgeryWindow>

+ 19 - 0
Content.Client/_Shitmed/Medical/Surgery/SurgeryWindow.xaml.cs

@@ -0,0 +1,19 @@
+// SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client._Shitmed.Medical.Surgery;
+
+[GenerateTypedNameReferences]
+public sealed partial class SurgeryWindow : DefaultWindow
+{
+    public SurgeryWindow()
+    {
+        RobustXamlLoader.Load(this);
+    }
+}

+ 107 - 0
Content.Client/_Shitmed/Targeting/TargetingSystem.cs

@@ -0,0 +1,107 @@
+// SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Shared.Input;
+using Content.Shared._Shitmed.Targeting;
+using Content.Shared._Shitmed.Targeting.Events;
+using Robust.Client.Player;
+using Robust.Shared.Input.Binding;
+using Robust.Shared.Player;
+
+namespace Content.Client._Shitmed.Targeting;
+public sealed class TargetingSystem : SharedTargetingSystem
+{
+    [Dependency] private readonly IPlayerManager _playerManager = default!;
+
+    public event Action<TargetingComponent>? TargetingStartup;
+    public event Action? TargetingShutdown;
+    public event Action<TargetBodyPart>? TargetChange;
+    public event Action<TargetingComponent>? PartStatusStartup;
+    public event Action<TargetingComponent>? PartStatusUpdate;
+    public event Action? PartStatusShutdown;
+    public override void Initialize()
+    {
+        base.Initialize();
+        SubscribeLocalEvent<TargetingComponent, LocalPlayerAttachedEvent>(HandlePlayerAttached);
+        SubscribeLocalEvent<TargetingComponent, LocalPlayerDetachedEvent>(HandlePlayerDetached);
+        SubscribeLocalEvent<TargetingComponent, ComponentStartup>(OnTargetingStartup);
+        SubscribeLocalEvent<TargetingComponent, ComponentShutdown>(OnTargetingShutdown);
+        SubscribeNetworkEvent<TargetIntegrityChangeEvent>(OnTargetIntegrityChange);
+
+        CommandBinds.Builder
+        .Bind(ContentKeyFunctions.TargetHead,
+            InputCmdHandler.FromDelegate((session) => HandleTargetChange(session, TargetBodyPart.Head)))
+        .Bind(ContentKeyFunctions.TargetTorso,
+            InputCmdHandler.FromDelegate((session) => HandleTargetChange(session, TargetBodyPart.Torso)))
+        .Bind(ContentKeyFunctions.TargetLeftArm,
+            InputCmdHandler.FromDelegate((session) => HandleTargetChange(session, TargetBodyPart.LeftArm)))
+        .Bind(ContentKeyFunctions.TargetLeftHand,
+            InputCmdHandler.FromDelegate((session) => HandleTargetChange(session, TargetBodyPart.LeftHand)))
+        .Bind(ContentKeyFunctions.TargetRightArm,
+            InputCmdHandler.FromDelegate((session) => HandleTargetChange(session, TargetBodyPart.RightArm)))
+        .Bind(ContentKeyFunctions.TargetRightHand,
+            InputCmdHandler.FromDelegate((session) => HandleTargetChange(session, TargetBodyPart.RightHand)))
+        .Bind(ContentKeyFunctions.TargetLeftLeg,
+            InputCmdHandler.FromDelegate((session) => HandleTargetChange(session, TargetBodyPart.LeftLeg)))
+        .Bind(ContentKeyFunctions.TargetLeftFoot,
+            InputCmdHandler.FromDelegate((session) => HandleTargetChange(session, TargetBodyPart.LeftFoot)))
+        .Bind(ContentKeyFunctions.TargetRightLeg,
+            InputCmdHandler.FromDelegate((session) => HandleTargetChange(session, TargetBodyPart.RightLeg)))
+        .Bind(ContentKeyFunctions.TargetRightFoot,
+            InputCmdHandler.FromDelegate((session) => HandleTargetChange(session, TargetBodyPart.RightFoot)))
+        .Register<SharedTargetingSystem>();
+    }
+
+    private void HandlePlayerAttached(EntityUid uid, TargetingComponent component, LocalPlayerAttachedEvent args)
+    {
+        TargetingStartup?.Invoke(component);
+        PartStatusStartup?.Invoke(component);
+    }
+
+    private void HandlePlayerDetached(EntityUid uid, TargetingComponent component, LocalPlayerDetachedEvent args)
+    {
+        TargetingShutdown?.Invoke();
+        PartStatusShutdown?.Invoke();
+    }
+
+    private void OnTargetingStartup(EntityUid uid, TargetingComponent component, ComponentStartup args)
+    {
+        if (_playerManager.LocalEntity != uid)
+            return;
+
+        TargetingStartup?.Invoke(component);
+        PartStatusStartup?.Invoke(component);
+    }
+
+    private void OnTargetingShutdown(EntityUid uid, TargetingComponent component, ComponentShutdown args)
+    {
+        if (_playerManager.LocalEntity != uid)
+            return;
+
+        TargetingShutdown?.Invoke();
+        PartStatusShutdown?.Invoke();
+    }
+
+    private void OnTargetIntegrityChange(TargetIntegrityChangeEvent args)
+    {
+        if (!TryGetEntity(args.Uid, out var uid)
+            || !_playerManager.LocalEntity.Equals(uid)
+            || !TryComp(uid, out TargetingComponent? component)
+            || !args.RefreshUi)
+            return;
+
+        PartStatusUpdate?.Invoke(component);
+    }
+
+    private void HandleTargetChange(ICommonSession? session, TargetBodyPart target)
+    {
+        if (session == null
+            || session.AttachedEntity is not { } uid
+            || !TryComp<TargetingComponent>(uid, out var targeting))
+            return;
+
+        TargetChange?.Invoke(target);
+    }
+}

+ 87 - 0
Content.Client/_Shitmed/UserInterface/Systems/PartStatus/PartStatusUIController.cs

@@ -0,0 +1,87 @@
+// SPDX-FileCopyrightText: 2024 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Client.Gameplay;
+using Content.Client._Shitmed.UserInterface.Systems.PartStatus.Widgets;
+using Content.Shared._Shitmed.Targeting;
+using Content.Client._Shitmed.Targeting;
+using Robust.Client.GameObjects;
+using Robust.Client.UserInterface.Controllers;
+using Robust.Shared.Utility;
+using Robust.Client.Graphics;
+
+
+namespace Content.Client._Shitmed.UserInterface.Systems.PartStatus;
+
+public sealed class PartStatusUIController : UIController, IOnStateEntered<GameplayState>, IOnSystemChanged<TargetingSystem>
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    [Dependency] private readonly IEntityNetworkManager _net = default!;
+    private SpriteSystem _spriteSystem = default!;
+    private TargetingComponent? _targetingComponent;
+    private PartStatusControl? PartStatusControl => UIManager.GetActiveUIWidgetOrNull<PartStatusControl>();
+
+    public void OnSystemLoaded(TargetingSystem system)
+    {
+        system.PartStatusStartup += AddPartStatusControl;
+        system.PartStatusShutdown += RemovePartStatusControl;
+        system.PartStatusUpdate += UpdatePartStatusControl;
+    }
+
+    public void OnSystemUnloaded(TargetingSystem system)
+    {
+        system.PartStatusStartup -= AddPartStatusControl;
+        system.PartStatusShutdown -= RemovePartStatusControl;
+        system.PartStatusUpdate -= UpdatePartStatusControl;
+    }
+
+    public void OnStateEntered(GameplayState state)
+    {
+        if (PartStatusControl != null)
+        {
+            PartStatusControl.SetVisible(_targetingComponent != null);
+
+            if (_targetingComponent != null)
+                PartStatusControl.SetTextures(_targetingComponent.BodyStatus);
+        }
+    }
+
+    public void AddPartStatusControl(TargetingComponent component)
+    {
+        _targetingComponent = component;
+
+        if (PartStatusControl != null)
+        {
+            PartStatusControl.SetVisible(_targetingComponent != null);
+
+            if (_targetingComponent != null)
+                PartStatusControl.SetTextures(_targetingComponent.BodyStatus);
+        }
+
+    }
+
+    public void RemovePartStatusControl()
+    {
+        if (PartStatusControl != null)
+            PartStatusControl.SetVisible(false);
+
+        _targetingComponent = null;
+    }
+
+    public void UpdatePartStatusControl(TargetingComponent component)
+    {
+        if (PartStatusControl != null && _targetingComponent != null)
+            PartStatusControl.SetTextures(_targetingComponent.BodyStatus);
+    }
+
+    public Texture GetTexture(SpriteSpecifier specifier)
+    {
+        if (_spriteSystem == null)
+            _spriteSystem = _entManager.System<SpriteSystem>();
+
+        return _spriteSystem.Frame0(specifier);
+    }
+}

+ 64 - 0
Content.Client/_Shitmed/UserInterface/Systems/PartStatus/Widgets/PartStatusControl.xaml

@@ -0,0 +1,64 @@
+<!--
+SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+
+SPDX-License-Identifier: AGPL-3.0-or-later
+-->
+
+<widgets:PartStatusControl
+    xmlns="https://spacestation14.io"
+    xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
+    xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
+    xmlns:widgets="clr-namespace:Content.Client._Shitmed.UserInterface.Systems.PartStatus.Widgets"
+    Name="PartStatusIndicator"
+    VerticalAlignment="Center"
+    HorizontalAlignment="Center">
+    <Control>
+        <PanelContainer>
+            <TextureRect
+                Name="DollHead"
+                Stretch="KeepAspectCentered"
+                SetSize="64 64"/>
+            <TextureRect
+                Name="DollTorso"
+                Stretch="KeepAspectCentered"
+                SetSize="64 64"/>
+            <TextureRect
+                Name="DollGroin"
+                Stretch="KeepAspectCentered"
+                SetSize="64 64"/>
+            <TextureRect
+                Name="DollLeftArm"
+                Stretch="KeepAspectCentered"
+                SetSize="64 64"/>
+            <TextureRect
+                Name="DollLeftHand"
+                Stretch="KeepAspectCentered"
+                SetSize="64 64"/>
+            <TextureRect
+                Name="DollRightArm"
+                Stretch="KeepAspectCentered"
+                SetSize="64 64"/>
+            <TextureRect
+                Name="DollRightHand"
+                Stretch="KeepAspectCentered"
+                SetSize="64 64"/>
+            <TextureRect
+                Name="DollLeftLeg"
+                Stretch="KeepAspectCentered"
+                SetSize="64 64"/>
+            <TextureRect
+                Name="DollLeftFoot"
+                Stretch="KeepAspectCentered"
+                SetSize="64 64"/>
+            <TextureRect
+                Name="DollRightLeg"
+                Stretch="KeepAspectCentered"
+                SetSize="64 64"/>
+            <TextureRect
+                Name="DollRightFoot"
+                Stretch="KeepAspectCentered"
+                SetSize="64 64"/>
+        </PanelContainer>
+    </Control>
+</widgets:PartStatusControl>

+ 54 - 0
Content.Client/_Shitmed/UserInterface/Systems/PartStatus/Widgets/PartStatusControl.xaml.cs

@@ -0,0 +1,54 @@
+// SPDX-FileCopyrightText: 2024 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Shared._Shitmed.Targeting;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Utility;
+
+namespace Content.Client._Shitmed.UserInterface.Systems.PartStatus.Widgets;
+
+[GenerateTypedNameReferences]
+public sealed partial class PartStatusControl : UIWidget
+{
+    private readonly Dictionary<TargetBodyPart, TextureRect> _partStatusControls;
+    private readonly PartStatusUIController _controller;
+    public PartStatusControl()
+    {
+        RobustXamlLoader.Load(this);
+
+        _controller = UserInterfaceManager.GetUIController<PartStatusUIController>();
+        _partStatusControls = new Dictionary<TargetBodyPart, TextureRect>
+        {
+            { TargetBodyPart.Head, DollHead },
+            { TargetBodyPart.Torso, DollTorso },
+            { TargetBodyPart.Groin, DollGroin },
+            { TargetBodyPart.LeftArm, DollLeftArm },
+            { TargetBodyPart.LeftHand, DollLeftHand },
+            { TargetBodyPart.RightArm, DollRightArm },
+            { TargetBodyPart.RightHand, DollRightHand },
+            { TargetBodyPart.LeftLeg, DollLeftLeg },
+            { TargetBodyPart.LeftFoot, DollLeftFoot },
+            { TargetBodyPart.RightLeg, DollRightLeg },
+            { TargetBodyPart.RightFoot, DollRightFoot }
+        };
+    }
+
+    public void SetTextures(Dictionary<TargetBodyPart, TargetIntegrity> state)
+    {
+        foreach (var (bodyPart, integrity) in state)
+        {
+            string enumName = Enum.GetName(typeof(TargetBodyPart), bodyPart) ?? "Unknown";
+            int enumValue = (int) integrity;
+            var texture = new SpriteSpecifier.Rsi(new ResPath($"/Textures/_Shitmed/Interface/Targeting/Status/{enumName.ToLowerInvariant()}.rsi"), $"{enumName.ToLowerInvariant()}_{enumValue}");
+            _partStatusControls[bodyPart].Texture = _controller.GetTexture(texture);
+        }
+    }
+
+    public void SetVisible(bool visible) => this.Visible = visible;
+
+}

+ 87 - 0
Content.Client/_Shitmed/UserInterface/Systems/Targeting/TargetingUIController.cs

@@ -0,0 +1,87 @@
+// SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Client.Gameplay;
+using Content.Client._Shitmed.UserInterface.Systems.Targeting.Widgets;
+using Content.Shared._Shitmed.Targeting;
+using Content.Client._Shitmed.Targeting;
+using Content.Shared._Shitmed.Targeting.Events;
+using Robust.Client.UserInterface.Controllers;
+using Robust.Client.Player;
+
+namespace Content.Client._Shitmed.UserInterface.Systems.Targeting;
+
+public sealed class TargetingUIController : UIController, IOnStateEntered<GameplayState>, IOnSystemChanged<TargetingSystem>
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    [Dependency] private readonly IEntityNetworkManager _net = default!;
+    [Dependency] private readonly IPlayerManager _playerManager = default!;
+
+    private TargetingComponent? _targetingComponent;
+    private TargetingControl? TargetingControl => UIManager.GetActiveUIWidgetOrNull<TargetingControl>();
+
+    public void OnSystemLoaded(TargetingSystem system)
+    {
+        system.TargetingStartup += AddTargetingControl;
+        system.TargetingShutdown += RemoveTargetingControl;
+        system.TargetChange += CycleTarget;
+    }
+
+    public void OnSystemUnloaded(TargetingSystem system)
+    {
+        system.TargetingStartup -= AddTargetingControl;
+        system.TargetingShutdown -= RemoveTargetingControl;
+        system.TargetChange -= CycleTarget;
+    }
+
+    public void OnStateEntered(GameplayState state)
+    {
+        if (TargetingControl == null)
+            return;
+
+        TargetingControl.SetTargetDollVisible(_targetingComponent != null);
+
+        if (_targetingComponent != null)
+            TargetingControl.SetBodyPartsVisible(_targetingComponent.Target);
+    }
+
+    public void AddTargetingControl(TargetingComponent component)
+    {
+        _targetingComponent = component;
+
+        if (TargetingControl != null)
+        {
+            TargetingControl.SetTargetDollVisible(_targetingComponent != null);
+
+            if (_targetingComponent != null)
+                TargetingControl.SetBodyPartsVisible(_targetingComponent.Target);
+        }
+
+    }
+
+    public void RemoveTargetingControl()
+    {
+        if (TargetingControl != null)
+            TargetingControl.SetTargetDollVisible(false);
+
+        _targetingComponent = null;
+    }
+
+    public void CycleTarget(TargetBodyPart bodyPart)
+    {
+        if (_playerManager.LocalEntity is not { } user
+            || _entManager.GetComponent<TargetingComponent>(user) is not { } targetingComponent
+            || TargetingControl == null)
+            return;
+
+        var player = _entManager.GetNetEntity(user);
+        if (bodyPart != targetingComponent.Target)
+        {
+            var msg = new TargetChangeEvent(player, bodyPart);
+            _net.SendSystemNetworkMessage(msg);
+            TargetingControl?.SetBodyPartsVisible(bodyPart);
+        }
+    }
+}

+ 223 - 0
Content.Client/_Shitmed/UserInterface/Systems/Targeting/Widgets/TargetingControl.xaml

@@ -0,0 +1,223 @@
+<!--
+SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+
+SPDX-License-Identifier: AGPL-3.0-or-later
+-->
+
+<widgets:TargetingControl
+    xmlns="https://spacestation14.io"
+    xmlns:widgets="clr-namespace:Content.Client._Shitmed.UserInterface.Systems.Targeting.Widgets"
+    Name="TargetingButton"
+    VerticalExpand="True"
+    VerticalAlignment="Bottom"
+    HorizontalAlignment="Right">
+    <Control HorizontalAlignment="Right">
+        <TextureRect
+            Name="TargetDoll"
+            Stretch="KeepAspectCentered"
+            SetSize="96 96">
+            <PanelContainer
+                SetSize="57 96"
+                Margin="18 0 0 0"
+                VerticalAlignment="Bottom"
+                HorizontalAlignment="Left">
+                <PanelContainer
+                    SetSize="18 39"
+                    Margin="0 7 0 0"
+                    HorizontalAlignment="Left">
+                    <TextureButton
+                        Name="RightArmButton"
+                        MinSize="18 30"
+                        StyleClasses="TargetDollButtonRightArm"
+                        VerticalAlignment="Top">
+                        <TextureRect
+                            TexturePath="/Textures/_Shitmed/Interface/Targeting/Doll/rightarm.png"
+                            Stretch="KeepAspectCentered"
+                            SetSize="12 24"
+                            VerticalAlignment="Center"
+                            HorizontalAlignment="Center"/>
+                    </TextureButton>
+                    <TextureButton
+                        Name="RightHandButton"
+                        MinSize="18 18"
+                        VerticalAlignment="Bottom"
+                        StyleClasses="TargetDollButtonRightHand">
+                        <TextureRect
+                            TexturePath="/Textures/_Shitmed/Interface/Targeting/Doll/righthand.png"
+                            Stretch="KeepAspectCentered"
+                            SetSize="12 12"
+                            VerticalAlignment="Center"
+                            HorizontalAlignment="Center"/>
+                    </TextureButton>
+                </PanelContainer>
+                <PanelContainer
+                    SetSize="51 90"
+                    Margin="0 64 0 0"
+                    VerticalAlignment="Bottom"
+                    HorizontalAlignment="Center">
+                    <TextureButton
+                        Name="HeadButton"
+                        MinSize="33 27"
+                        VerticalAlignment="Top"
+                        HorizontalAlignment="Center"
+                        StyleClasses="TargetDollButtonHead">
+                        <TextureRect
+                            TexturePath="/Textures/_Shitmed/Interface/Targeting/Doll/head.png"
+                            Stretch="KeepAspectCentered"
+                            SetSize="27 21"
+                            VerticalAlignment="Center"
+                            HorizontalAlignment="Center"/>
+                        <!--<PanelContainer
+                            SetSize="15 15"
+                            Margin="0 9 0 0"
+                            HorizontalAlignment="Center">
+                            <TextureButton
+                                Name="EyesButton"
+                                MinSize="15 9"
+                                VerticalAlignment="Top"
+                                StyleClasses="TargetDollButtonEyes">
+                                <TextureRect
+                                    TexturePath="/Textures/_Shitmed/Interface/Targeting/Doll/eyes.png"
+                                    Stretch="KeepAspectCentered"
+                                    SetSize="15 9"/>
+                            </TextureButton>
+                            <TextureButton
+                                Name="MouthButton"
+                                SetSize="9 6"
+                                VerticalAlignment="Bottom"
+                                StyleClasses="TargetDollButtonMouth">
+                                <TextureRect
+                                    TexturePath="/Textures/_Shitmed/Interface/Targeting/Doll/mouth.png"
+                                    Stretch="KeepAspectCentered"
+                                    SetSize="9 6"/>
+                            </TextureButton>
+                        </PanelContainer>-->
+                    </TextureButton>
+                    <TextureButton
+                        Name="ChestButton"
+                        SetSize="33 36"
+                        Margin="0 21 0 0"
+                        VerticalAlignment="Top"
+                        HorizontalAlignment="Center"
+                        StyleClasses="TargetDollButtonChest">
+                        <TextureRect
+                            TexturePath="/Textures/_Shitmed/Interface/Targeting/Doll/torso.png"
+                            Stretch="KeepAspectCentered"
+                            SetSize="27 30"
+                            VerticalAlignment="Center"
+                            HorizontalAlignment="Center"/>
+                    </TextureButton>
+                    <PanelContainer
+                        MinSize="45 42"
+                        VerticalAlignment="Bottom"
+                        HorizontalAlignment="Center">
+                        <TextureButton
+                            Name="GroinButton"
+                            MinSize="33 18"
+                            VerticalAlignment="Top"
+                            HorizontalAlignment="Center"
+                            StyleClasses="TargetDollButtonGroin">
+                            <TextureRect
+                                TexturePath="/Textures/_Shitmed/Interface/Targeting/Doll/groin.png"
+                                Stretch="KeepAspectCentered"
+                                SetSize="27 12"
+                                VerticalAlignment="Center"
+                                HorizontalAlignment="Center"/>
+                        </TextureButton>
+                        <PanelContainer
+                            MinSize="24 36"
+                            VerticalAlignment="Bottom"
+                            HorizontalAlignment="Right">
+                            <TextureButton
+                                Name="LeftLegButton"
+                                MinSize="18 33"
+                                VerticalAlignment="Top"
+                                HorizontalAlignment="Left"
+                                StyleClasses="TargetDollButtonLeftLeg">
+                                <TextureRect
+                                    TexturePath="/Textures/_Shitmed/Interface/Targeting/Doll/leftleg.png"
+                                    Stretch="KeepAspectCentered"
+                                    SetSize="12 27"
+                                    VerticalAlignment="Center"
+                                    HorizontalAlignment="Center"/>
+                            </TextureButton>
+                            <TextureButton
+                                Name="LeftFootButton"
+                                MinSize="24 12"
+                                VerticalAlignment="Bottom"
+                                StyleClasses="TargetDollButtonLeftFoot">
+                                    <TextureRect
+                                        TexturePath="/Textures/_Shitmed/Interface/Targeting/Doll/leftfoot.png"
+                                        Stretch="KeepAspectCentered"
+                                        SetSize="18 9"
+                                        VerticalAlignment="Center"
+                                        HorizontalAlignment="Center"/>
+                            </TextureButton>
+                        </PanelContainer>
+                        <PanelContainer
+                            MinSize="24 36"
+                            VerticalAlignment="Bottom"
+                            HorizontalAlignment="Left">
+                            <TextureButton
+                                Name="RightLegButton"
+                                MinSize="18 33"
+                                VerticalAlignment="Top"
+                                HorizontalAlignment="Right"
+                                StyleClasses="TargetDollButtonRightLeg">
+                                <TextureRect
+                                    TexturePath="/Textures/_Shitmed/Interface/Targeting/Doll/rightleg.png"
+                                    Stretch="KeepAspectCentered"
+                                    SetSize="12 27"
+                                    VerticalAlignment="Center"
+                                    HorizontalAlignment="Center"/>
+                            </TextureButton>
+                            <TextureButton
+                                Name="RightFootButton"
+                                MinSize="24 12"
+                                VerticalAlignment="Bottom"
+                                HorizontalAlignment="Center"
+                                StyleClasses="TargetDollButtonRightFoot">
+                                <TextureRect
+                                    TexturePath="/Textures/_Shitmed/Interface/Targeting/Doll/rightfoot.png"
+                                    Stretch="KeepAspectCentered"
+                                    SetSize="18 9"
+                                    VerticalAlignment="Center"
+                                    HorizontalAlignment="Center"/>
+                            </TextureButton>
+                        </PanelContainer>
+                    </PanelContainer>
+                </PanelContainer>
+                <PanelContainer
+                    SetSize="18 39"
+                    Margin="0 7 0 0"
+                    HorizontalAlignment="Right">
+                    <TextureButton
+                        Name="LeftArmButton"
+                        MinSize="18 30"
+                        StyleClasses="TargetDollButtonLeftArm"
+                        VerticalAlignment="Top">
+                        <TextureRect
+                            TexturePath="/Textures/_Shitmed/Interface/Targeting/Doll/leftarm.png"
+                            Stretch="KeepAspectCentered"
+                            SetSize="12 24"
+                            VerticalAlignment="Center"
+                            HorizontalAlignment="Center"/>
+                    </TextureButton>
+                    <TextureButton
+                        Name="LeftHandButton"
+                        MinSize="18 18"
+                        VerticalAlignment="Bottom"
+                        StyleClasses="TargetDollButtonLeftHand">
+                        <TextureRect
+                            TexturePath="/Textures/_Shitmed/Interface/Targeting/Doll/lefthand.png"
+                            Stretch="KeepAspectCentered"
+                            SetSize="12 12"
+                            VerticalAlignment="Center"
+                            HorizontalAlignment="Center"/>
+                    </TextureButton>
+                </PanelContainer>
+            </PanelContainer>
+        </TextureRect>
+    </Control>
+</widgets:TargetingControl>

+ 63 - 0
Content.Client/_Shitmed/UserInterface/Systems/Targeting/Widgets/TargetingControl.xaml.cs

@@ -0,0 +1,63 @@
+// SPDX-FileCopyrightText: 2024 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using System.Linq;
+using Content.Shared._Shitmed.Targeting;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client._Shitmed.UserInterface.Systems.Targeting.Widgets;
+
+[GenerateTypedNameReferences]
+public sealed partial class TargetingControl : UIWidget
+{
+    private readonly TargetingUIController _controller;
+    private readonly Dictionary<TargetBodyPart, TextureButton> _bodyPartControls;
+
+    public TargetingControl()
+    {
+        RobustXamlLoader.Load(this);
+        _controller = UserInterfaceManager.GetUIController<TargetingUIController>();
+
+        _bodyPartControls = new Dictionary<TargetBodyPart, TextureButton>
+        {
+            // TODO: ADD EYE AND MOUTH TARGETING
+            { TargetBodyPart.Head, HeadButton },
+            { TargetBodyPart.Torso, ChestButton },
+            { TargetBodyPart.Groin, GroinButton },
+            { TargetBodyPart.LeftArm, LeftArmButton },
+            { TargetBodyPart.LeftHand, LeftHandButton },
+            { TargetBodyPart.RightArm, RightArmButton },
+            { TargetBodyPart.RightHand, RightHandButton },
+            { TargetBodyPart.LeftLeg, LeftLegButton },
+            { TargetBodyPart.LeftFoot, LeftFootButton },
+            { TargetBodyPart.RightLeg, RightLegButton },
+            { TargetBodyPart.RightFoot, RightFootButton },
+        };
+
+        foreach (var bodyPartButton in _bodyPartControls)
+        {
+            bodyPartButton.Value.MouseFilter = MouseFilterMode.Stop;
+            bodyPartButton.Value.OnPressed += _ => SetActiveBodyPart(bodyPartButton.Key);
+
+            TargetDoll.Texture = Theme.ResolveTexture("target_doll");
+        }
+    }
+
+    private void SetActiveBodyPart(TargetBodyPart bodyPart) => _controller.CycleTarget(bodyPart);
+
+    public void SetBodyPartsVisible(TargetBodyPart bodyPart)
+    {
+        foreach (var bodyPartButton in _bodyPartControls)
+            bodyPartButton.Value.Children.First().Visible = bodyPartButton.Key == bodyPart;
+    }
+
+    protected override void OnThemeUpdated() => TargetDoll.Texture = Theme.ResolveTexture("target_doll");
+
+    public void SetTargetDollVisible(bool visible) => Visible = visible;
+
+}

+ 4 - 4
Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs

@@ -17,7 +17,7 @@ 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!;
 
         private const float UpdateTimer = 1f;
@@ -221,7 +221,7 @@ public override void Update(float frameTime)
 
                 var pressure = 1f;
 
-                if (_atmosphereSystem.GetContainingMixture(uid) is {} mixture)
+                if (_atmosphereSystem.GetContainingMixture(uid) is { } mixture)
                 {
                     pressure = MathF.Max(mixture.Pressure, 1f);
                 }
@@ -237,7 +237,7 @@ public override void Update(float frameTime)
                 if (pressure <= Atmospherics.HazardLowPressure)
                 {
                     // Deal damage and ignore resistances. Resistance to pressure damage should be done via pressure protection gear.
-                    _damageableSystem.TryChangeDamage(uid, barotrauma.Damage * Atmospherics.LowPressureDamage, true, false);
+                    _damageableSystem.TryChangeDamage(uid, barotrauma.Damage * Atmospherics.LowPressureDamage, true, false, canSever: false, partMultiplier: 0.2f); // Shitmed Change
 
                     if (!barotrauma.TakingDamage)
                     {
@@ -252,7 +252,7 @@ public override void Update(float frameTime)
                     var damageScale = MathF.Min(((pressure / Atmospherics.HazardHighPressure) - 1) * Atmospherics.PressureDamageCoefficient, Atmospherics.MaxHighPressureDamage);
 
                     // Deal damage and ignore resistances. Resistance to pressure damage should be done via pressure protection gear.
-                    _damageableSystem.TryChangeDamage(uid, barotrauma.Damage * damageScale, true, false);
+                    _damageableSystem.TryChangeDamage(uid, barotrauma.Damage * damageScale, true, false, canSever: false); // Shitmed Change
 
                     if (!barotrauma.TakingDamage)
                     {

+ 5 - 1
Content.Server/Body/Commands/AddHandCommand.cs

@@ -133,7 +133,11 @@ public void Execute(IConsoleShell shell, string argStr, string[] args)
             if (attachAt == default)
                 attachAt = bodySystem.GetBodyChildren(entity, body).First();
 
-            var slotId = part.GetHashCode().ToString();
+
+            // Shitmed Change Start
+            var slotId = $"{part.Symmetry.ToString().ToLower()} {part.GetHashCode().ToString()}";
+            part.SlotId = part.GetHashCode().ToString();
+            // Shitmed Change End
 
             if (!bodySystem.TryCreatePartSlotAndAttach(attachAt.Id, slotId, hand, BodyPartType.Hand, attachAt.Component, part))
             {

+ 8 - 1
Content.Server/Body/Commands/AttachBodyPartCommand.cs

@@ -98,8 +98,15 @@ public void Execute(IConsoleShell shell, string argStr, string[] args)
                 return;
             }
 
-            var slotId = $"AttachBodyPartVerb-{partUid}";
+            // Shitmed Change Start
+            var slotId = "";
+            if (part.Symmetry != BodyPartSymmetry.None)
+                slotId = $"{part.Symmetry.ToString().ToLower()} {part.GetHashCode().ToString()}";
+            else
+                slotId = $"{part.GetHashCode().ToString()}";
 
+            part.SlotId = part.GetHashCode().ToString();
+            // Shitmed Change End
             // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
             if (body.RootContainer.ContainedEntity != null)
             {

+ 5 - 0
Content.Server/Body/Components/BrainComponent.cs

@@ -5,5 +5,10 @@ namespace Content.Server.Body.Components
     [RegisterComponent, Access(typeof(BrainSystem))]
     public sealed partial class BrainComponent : Component
     {
+        /// <summary>
+        ///     Shitmed Change: Is this brain currently controlling the entity?
+        /// </summary>
+        [DataField]
+        public bool Active = true;
     }
 }

+ 85 - 7
Content.Server/Body/Systems/BodySystem.cs

@@ -13,13 +13,21 @@
 using Robust.Shared.Timing;
 using System.Numerics;
 
+// Shitmed Change
+using System.Linq;
+using Content.Shared._Shitmed.Body.Part;
+using Content.Shared.Gibbing.Events;
+
 namespace Content.Server.Body.Systems;
 
-public sealed class BodySystem : SharedBodySystem
+public sealed partial class BodySystem : SharedBodySystem
 {
+    [Dependency] private readonly BloodstreamSystem _bloodstream = default!; // Shitmed Change
     [Dependency] private readonly GhostSystem _ghostSystem = default!;
     [Dependency] private readonly IGameTiming _gameTiming = default!;
     [Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!;
+    [Dependency] private readonly SharedAppearanceSystem _appearance = default!; // Shitmed Change
+
     [Dependency] private readonly MobStateSystem _mobState = default!;
     [Dependency] private readonly SharedMindSystem _mindSystem = default!;
 
@@ -64,7 +72,8 @@ private void OnRelayMoveInput(Entity<BodyComponent> ent, ref MoveInputEvent args
     {
         // TODO: Predict this probably.
         base.AddPart(bodyEnt, partEnt, slotId);
-
+        if (!TryComp<HumanoidAppearanceComponent>(bodyEnt, out var humanoid))
+            return;
         var layer = partEnt.Comp.ToHumanoidLayers();
         if (layer != null)
         {
@@ -89,7 +98,9 @@ private void OnRelayMoveInput(Entity<BodyComponent> ent, ref MoveInputEvent args
             return;
 
         var layers = HumanoidVisualLayersExtension.Sublayers(layer.Value);
-        _humanoidSystem.SetLayersVisibility((bodyEnt, humanoid), layers, visible: false);
+        _humanoidSystem.SetLayersVisibility(
+            bodyEnt, layers, visible: false, permanent: true, humanoid);
+        _appearance.SetData(bodyEnt, layer, true); // Shitmed Change
     }
 
     public override HashSet<EntityUid> GibBody(
@@ -100,8 +111,11 @@ private void OnRelayMoveInput(Entity<BodyComponent> ent, ref MoveInputEvent args
         Vector2? splatDirection = null,
         float splatModifier = 1,
         Angle splatCone = default,
-        SoundSpecifier? gibSoundOverride = null
-    )
+        SoundSpecifier? gibSoundOverride = null,
+        // Shitmed Change
+        GibType gib = GibType.Gib,
+        GibContentsOption contents = GibContentsOption.Drop)
+
     {
         if (!Resolve(bodyId, ref body, logMissing: false)
             || TerminatingOrDeleted(bodyId)
@@ -115,8 +129,8 @@ private void OnRelayMoveInput(Entity<BodyComponent> ent, ref MoveInputEvent args
             return new HashSet<EntityUid>();
 
         var gibs = base.GibBody(bodyId, gibOrgans, body, launchGibs: launchGibs,
-            splatDirection: splatDirection, splatModifier: splatModifier, splatCone:splatCone);
-
+            splatDirection: splatDirection, splatModifier: splatModifier, splatCone: splatCone,
+            gib: gib, contents: contents); // Shitmed Change
         var ev = new BeingGibbedEvent(gibs);
         RaiseLocalEvent(bodyId, ref ev);
 
@@ -124,4 +138,68 @@ private void OnRelayMoveInput(Entity<BodyComponent> ent, ref MoveInputEvent args
 
         return gibs;
     }
+
+    // Shitmed Change Start
+    public override HashSet<EntityUid> GibPart(
+        EntityUid partId,
+        BodyPartComponent? part = null,
+        bool launchGibs = true,
+        Vector2? splatDirection = null,
+        float splatModifier = 1,
+        Angle splatCone = default,
+        SoundSpecifier? gibSoundOverride = null)
+    {
+        if (!Resolve(partId, ref part, logMissing: false)
+            || TerminatingOrDeleted(partId)
+            || EntityManager.IsQueuedForDeletion(partId))
+            return new HashSet<EntityUid>();
+
+        if (Transform(partId).MapUid is null)
+            return new HashSet<EntityUid>();
+
+        var gibs = base.GibPart(partId, part, launchGibs: launchGibs,
+            splatDirection: splatDirection, splatModifier: splatModifier, splatCone: splatCone);
+
+        var ev = new BeingGibbedEvent(gibs);
+        RaiseLocalEvent(partId, ref ev);
+
+        if (gibs.Any())
+            QueueDel(partId);
+
+        return gibs;
+    }
+
+    public override bool BurnPart(EntityUid partId, BodyPartComponent? part = null)
+    {
+        if (!Resolve(partId, ref part, logMissing: false)
+            || TerminatingOrDeleted(partId)
+            || EntityManager.IsQueuedForDeletion(partId))
+            return false;
+
+        return base.BurnPart(partId, part);
+    }
+
+    protected override void ApplyPartMarkings(EntityUid target, BodyPartAppearanceComponent component)
+    {
+        return;
+    }
+
+    protected override void RemoveBodyMarkings(EntityUid target, BodyPartAppearanceComponent partAppearance, HumanoidAppearanceComponent bodyAppearance)
+    {
+        foreach (var (visualLayer, markingList) in partAppearance.Markings)
+            foreach (var marking in markingList)
+                _humanoidSystem.RemoveMarking(target, marking.MarkingId, sync: false, humanoid: bodyAppearance);
+
+        Dirty(target, bodyAppearance);
+    }
+
+    protected override void PartRemoveDamage(Entity<BodyComponent?> bodyEnt, Entity<BodyPartComponent> partEnt)
+    {
+        var bleeding = partEnt.Comp.SeverBleeding;
+        if (partEnt.Comp.IsVital)
+            bleeding *= 2f;
+        _bloodstream.TryModifyBleedAmount(bodyEnt, bleeding);
+    }
+
+    // Shitmed Change End
 }

+ 61 - 3
Content.Server/Body/Systems/BrainSystem.cs

@@ -6,22 +6,54 @@
 using Content.Shared.Mind.Components;
 using Content.Shared.Pointing;
 
+// Shitmed Change
+using Content.Shared._Shitmed.Body.Organ;
+using Content.Shared.Body.Systems;
+
 namespace Content.Server.Body.Systems
 {
     public sealed class BrainSystem : EntitySystem
     {
         [Dependency] private readonly SharedMindSystem _mindSystem = default!;
-
+        [Dependency] private readonly SharedBodySystem _bodySystem = default!; // Shitmed Change
         public override void Initialize()
         {
             base.Initialize();
 
             SubscribeLocalEvent<BrainComponent, OrganAddedToBodyEvent>((uid, _, args) => HandleMind(args.Body, uid));
-            SubscribeLocalEvent<BrainComponent, OrganRemovedFromBodyEvent>((uid, _, args) => HandleMind(uid, args.OldBody));
+            // Shitmed Change Start
+            SubscribeLocalEvent<BrainComponent, OrganRemovedFromBodyEvent>(HandleRemoval);
             SubscribeLocalEvent<BrainComponent, PointAttemptEvent>(OnPointAttempt);
         }
 
-        private void HandleMind(EntityUid newEntity, EntityUid oldEntity)
+        private void HandleRemoval(EntityUid uid, BrainComponent brain, ref OrganRemovedFromBodyEvent args)
+        {
+            if (TerminatingOrDeleted(uid) || TerminatingOrDeleted(args.OldBody))
+                return;
+
+            brain.Active = false;
+            if (!CheckOtherBrains(args.OldBody))
+            {
+                // Prevents revival, should kill the user within a given timespan too.
+                EnsureComp<DebrainedComponent>(args.OldBody);
+                HandleMind(uid, args.OldBody);
+            }
+        }
+
+        private void HandleAddition(EntityUid uid, BrainComponent brain, ref OrganAddedToBodyEvent args)
+        {
+            if (TerminatingOrDeleted(uid) || TerminatingOrDeleted(args.Body))
+                return;
+
+            if (!CheckOtherBrains(args.Body))
+            {
+                RemComp<DebrainedComponent>(args.Body);
+                HandleMind(args.Body, uid, brain);
+            }
+        }
+
+
+        private void HandleMind(EntityUid newEntity, EntityUid oldEntity, BrainComponent? brain = null)
         {
             if (TerminatingOrDeleted(newEntity) || TerminatingOrDeleted(oldEntity))
                 return;
@@ -37,8 +69,34 @@ private void HandleMind(EntityUid newEntity, EntityUid oldEntity)
                 return;
 
             _mindSystem.TransferTo(mindId, newEntity, mind: mind);
+            if (brain != null)
+                brain.Active = true;
+        }
+
+        private bool CheckOtherBrains(EntityUid entity)
+        {
+            var hasOtherBrains = false;
+            if (TryComp<BodyComponent>(entity, out var body))
+            {
+                if (TryComp<BrainComponent>(entity, out var bodyBrain))
+                    hasOtherBrains = true;
+                else
+                {
+                    foreach (var (organ, _) in _bodySystem.GetBodyOrgans(entity, body))
+                    {
+                        if (TryComp<BrainComponent>(organ, out var brain) && brain.Active)
+                        {
+                            hasOtherBrains = true;
+                            break;
+                        }
+                    }
+                }
+            }
+
+            return hasOtherBrains;
         }
 
+        // Shitmed Change End
         private void OnPointAttempt(Entity<BrainComponent> ent, ref PointAttemptEvent args)
         {
             args.Cancel();

+ 11 - 1
Content.Server/Body/Systems/MetabolizerSystem.cs

@@ -14,6 +14,7 @@
 using Robust.Shared.Prototypes;
 using Robust.Shared.Random;
 using Robust.Shared.Timing;
+using Content.Shared.Bed.Sleep; // Shitmed Change
 
 namespace Content.Server.Body.Systems
 {
@@ -182,15 +183,24 @@ private void TryMetabolize(Entity<MetabolizerComponent, OrganComponent?, Solutio
                     // Remove $rate, as long as there's enough reagent there to actually remove that much
                     mostToRemove = FixedPoint2.Clamp(rate, 0, quantity);
 
-                    float scale = (float) mostToRemove / (float) rate;
+                    float scale = (float)mostToRemove / (float)rate;
 
                     // if it's possible for them to be dead, and they are,
                     // then we shouldn't process any effects, but should probably
                     // still remove reagents
                     if (TryComp<MobStateComponent>(solutionEntityUid.Value, out var state))
                     {
+                        // Shitmed Change Start
+
                         if (!proto.WorksOnTheDead && _mobStateSystem.IsDead(solutionEntityUid.Value, state))
                             continue;
+
+                        if (proto.WorksOnUnconscious == true &&
+                            (_mobStateSystem.IsCritical(solutionEntityUid.Value, state) ||
+                             HasComp<SleepingComponent>(solutionEntityUid.Value)))
+                            continue;
+
+                        // Shitmed Change End
                     }
 
                     var actualEntity = ent.Comp2?.Body ?? solutionEntityUid.Value;

+ 6 - 5
Content.Server/Body/Systems/RespiratorSystem.cs

@@ -18,6 +18,8 @@
 using JetBrains.Annotations;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Timing;
+using Content.Shared._Shitmed.Body.Components; // Shitmed Change
+using Content.Shared._Shitmed.Body.Organ; // Shitmed Change
 
 namespace Content.Server.Body.Systems;
 
@@ -70,13 +72,12 @@ public override void Update(float frameTime)
                 continue;
 
             respirator.NextUpdate += respirator.UpdateInterval;
-
-            if (_mobState.IsDead(uid))
+            if (_mobState.IsDead(uid) || HasComp<BreathingImmunityComponent>(uid)) // Shitmed: BreathingImmunity
                 continue;
 
-            UpdateSaturation(uid, -(float) respirator.UpdateInterval.TotalSeconds, respirator);
+            UpdateSaturation(uid, -(float)respirator.UpdateInterval.TotalSeconds, respirator);
 
-            if (!_mobState.IsIncapacitated(uid)) // cannot breathe in crit.
+            if (!_mobState.IsIncapacitated(uid) && !HasComp<DebrainedComponent>(uid)) // Shitmed Change - Cannot breathe in crit or when no brain.
             {
                 switch (respirator.Status)
                 {
@@ -207,7 +208,7 @@ public bool CanMetabolizeGas(Entity<RespiratorComponent?> ent, GasMixture gas)
 
         gas = new GasMixture(gas);
         var lungRatio = 1.0f / organs.Count;
-        gas.Multiply(MathF.Min(lungRatio * gas.Volume/Atmospherics.BreathVolume, lungRatio));
+        gas.Multiply(MathF.Min(lungRatio * gas.Volume / Atmospherics.BreathVolume, lungRatio));
         var solution = _lungSystem.GasToReagent(gas);
 
         float saturation = 0;

+ 15 - 0
Content.Server/Damage/Components/DamageUserOnTriggerComponent.cs

@@ -1,4 +1,13 @@
+// SPDX-FileCopyrightText: 2022 Flipp Syder <76629141+vulppine@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2022 Kara <lunarautomaton6@gmail.com>
+// SPDX-FileCopyrightText: 2023 DrSmugleaf <DrSmugleaf@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 deltanedas <@deltanedas:kde.org>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
 using Content.Shared.Damage;
+using Content.Shared._Shitmed.Targeting; // Shitmed
 
 namespace Content.Server.Damage.Components;
 
@@ -9,4 +18,10 @@ public sealed partial class DamageUserOnTriggerComponent : Component
 
     [DataField("damage", required: true)]
     public DamageSpecifier Damage = default!;
+
+    /// <summary>
+    /// Shitmed Change: Lets mousetraps, etc. target the feet.
+    /// </summary>
+    [DataField]
+    public TargetBodyPart? TargetPart = TargetBodyPart.Feet;
 }

+ 2 - 2
Content.Server/Damage/Systems/DamageUserOnTriggerSystem.cs

@@ -35,13 +35,13 @@ private bool OnDamageTrigger(EntityUid source, EntityUid target, DamageUserOnTri
         var ev = new BeforeDamageUserOnTriggerEvent(damage, target);
         RaiseLocalEvent(source, ev);
 
-        return _damageableSystem.TryChangeDamage(target, ev.Damage, component.IgnoreResistances, origin: source) is not null;
+        return _damageableSystem.TryChangeDamage(target, ev.Damage, component.IgnoreResistances, origin: source, targetPart: component.TargetPart) is not null; // Shitmed Change
     }
 }
 
 public sealed class BeforeDamageUserOnTriggerEvent : EntityEventArgs
 {
-    public DamageSpecifier Damage { get; set;  }
+    public DamageSpecifier Damage { get; set; }
     public EntityUid Tripper { get; }
 
     public BeforeDamageUserOnTriggerEvent(DamageSpecifier damage, EntityUid target)

+ 3 - 1
Content.Server/Destructible/Thresholds/Behaviors/BurnBodyBehavior.cs

@@ -1,4 +1,6 @@
-using Content.Shared.Body.Components;
+
+using Content.Shared.Body.Part; // Shitmed Change
+using Content.Shared.Body.Components;
 using Content.Shared.Inventory;
 using Content.Shared.Popups;
 using JetBrains.Annotations;

+ 5 - 1
Content.Server/Destructible/Thresholds/Behaviors/GibBehavior.cs

@@ -1,5 +1,6 @@
 using Content.Shared.Body.Components;
 using JetBrains.Annotations;
+using Content.Shared.Gibbing.Events; // Shitmed Change
 
 namespace Content.Server.Destructible.Thresholds.Behaviors
 {
@@ -7,13 +8,16 @@ namespace Content.Server.Destructible.Thresholds.Behaviors
     [DataDefinition]
     public sealed partial class GibBehavior : IThresholdBehavior
     {
+        [DataField] public GibType GibType = GibType.Gib; // Shitmed Change
+        [DataField] public GibContentsOption GibContents = GibContentsOption.Drop; // Shitmed Change
+
         [DataField("recursive")] private bool _recursive = true;
 
         public void Execute(EntityUid owner, DestructibleSystem system, EntityUid? cause = null)
         {
             if (system.EntityManager.TryGetComponent(owner, out BodyComponent? body))
             {
-                system.BodySystem.GibBody(owner, _recursive, body);
+                system.BodySystem.GibBody(owner, _recursive, body, gib: GibType, contents: GibContents); // Shitmed Change
             }
         }
     }

+ 9 - 1
Content.Server/EntityEffects/Effects/HealthChange.cs

@@ -7,6 +7,9 @@
 using Robust.Shared.Prototypes;
 using System.Linq;
 using System.Text.Json.Serialization;
+using Content.Shared._Shitmed.EntityEffects.Effects; // Shitmed Change
+using Content.Shared._Shitmed.Targeting; // Shitmed Change
+using Content.Server.Temperature.Components; // Shitmed Change
 
 namespace Content.Server.EntityEffects.Effects
 {
@@ -164,7 +167,12 @@ public override void Effect(EntityEffectBaseArgs args)
                     args.TargetEntity,
                     damageSpec * scale,
                     IgnoreResistances,
-                    interruptsDoAfters: false);
+                    interruptsDoAfters: false,
+                    // Shitmed Change Start
+                    targetPart: TargetBodyPart.All,
+                    partMultiplier: 0.5f,
+                    canSever: false);
+            // Shitmed Change End
         }
     }
 }

+ 2 - 2
Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs

@@ -195,7 +195,7 @@ public bool IsBlockingTurf(EntityUid uid)
         if (!_physicsQuery.TryGetComponent(uid, out var physics))
             return false;
 
-        return physics.CanCollide && physics.Hard && (physics.CollisionLayer & (int) CollisionGroup.Impassable) != 0;
+        return physics.CanCollide && physics.Hard && (physics.CollisionLayer & (int)CollisionGroup.Impassable) != 0;
     }
 
     /// <summary>
@@ -464,7 +464,7 @@ private void GetEntitiesToDamage(EntityUid uid, DamageSpecifier originalDamage,
                 }
 
                 // TODO EXPLOSIONS turn explosions into entities, and pass the the entity in as the damage origin.
-                _damageableSystem.TryChangeDamage(entity, damage * _damageableSystem.UniversalExplosionDamageModifier, ignoreResistances: true);
+                _damageableSystem.TryChangeDamage(entity, damage * _damageableSystem.UniversalExplosionDamageModifier, ignoreResistances: true, partMultiplier: 0.3f); // Shitmed: Temp change, nerf explosion delimbing
 
             }
         }

+ 39 - 12
Content.Server/Hands/Systems/HandsSystem.cs

@@ -23,6 +23,8 @@
 using Robust.Shared.Random;
 using Robust.Shared.Timing;
 using Robust.Shared.Utility;
+using Content.Shared.Body.Systems; // Shitmed Change
+using Content.Shared._Shitmed.Body.Events; // Shitmed Change
 
 namespace Content.Server.Hands.Systems
 {
@@ -36,12 +38,12 @@ public sealed class HandsSystem : SharedHandsSystem
         [Dependency] private readonly SharedTransformSystem _transformSystem = default!;
         [Dependency] private readonly PullingSystem _pullingSystem = default!;
         [Dependency] private readonly ThrowingSystem _throwingSystem = default!;
-
+        [Dependency] private readonly SharedBodySystem _bodySystem = default!; // Shitmed Change
         public override void Initialize()
         {
             base.Initialize();
 
-            SubscribeLocalEvent<HandsComponent, DisarmedEvent>(OnDisarmed, before: new[] {typeof(StunSystem), typeof(StaminaSystem)});
+            SubscribeLocalEvent<HandsComponent, DisarmedEvent>(OnDisarmed, before: new[] { typeof(StunSystem), typeof(StaminaSystem) });
 
             SubscribeLocalEvent<HandsComponent, PullStartedMessage>(HandlePullStarted);
             SubscribeLocalEvent<HandsComponent, PullStoppedMessage>(HandlePullStopped);
@@ -52,6 +54,8 @@ public override void Initialize()
             SubscribeLocalEvent<HandsComponent, ComponentGetState>(GetComponentState);
 
             SubscribeLocalEvent<HandsComponent, BeforeExplodeEvent>(OnExploded);
+            SubscribeLocalEvent<HandsComponent, BodyPartEnabledEvent>(HandleBodyPartEnabled); // Shitmed Change
+            SubscribeLocalEvent<HandsComponent, BodyPartDisabledEvent>(HandleBodyPartDisabled); // Shitmed Change
 
             CommandBinds.Builder
                 .Bind(ContentKeyFunctions.ThrowItemInHand, new PointerInputCmdHandler(HandleThrowItem))
@@ -100,33 +104,56 @@ private void OnDisarmed(EntityUid uid, HandsComponent component, DisarmedEvent a
 
             args.Handled = true; // no shove/stun.
         }
-
-        private void HandleBodyPartAdded(EntityUid uid, HandsComponent component, ref BodyPartAddedEvent args)
+        // Shitmed Change Start
+        private void TryAddHand(EntityUid uid, HandsComponent component, Entity<BodyPartComponent> part, string slot)
         {
-            if (args.Part.Comp.PartType != BodyPartType.Hand)
+            if (part.Comp is null
+                || part.Comp.PartType != BodyPartType.Hand)
                 return;
 
             // If this annoys you, which it should.
             // Ping Smugleaf.
-            var location = args.Part.Comp.Symmetry switch
+            var location = part.Comp.Symmetry switch
             {
                 BodyPartSymmetry.None => HandLocation.Middle,
                 BodyPartSymmetry.Left => HandLocation.Left,
                 BodyPartSymmetry.Right => HandLocation.Right,
-                _ => throw new ArgumentOutOfRangeException(nameof(args.Part.Comp.Symmetry))
+                _ => throw new ArgumentOutOfRangeException(nameof(part.Comp.Symmetry))
             };
 
-            AddHand(uid, args.Slot, location);
+            if (part.Comp.Enabled
+                && _bodySystem.TryGetParentBodyPart(part, out var _, out var parentPartComp)
+                && parentPartComp.Enabled)
+                AddHand(uid, slot, location);
+        }
+
+        private void HandleBodyPartAdded(EntityUid uid, HandsComponent component, ref BodyPartAddedEvent args)
+        {
+            TryAddHand(uid, component, args.Part, args.Slot);
         }
 
         private void HandleBodyPartRemoved(EntityUid uid, HandsComponent component, ref BodyPartRemovedEvent args)
         {
-            if (args.Part.Comp.PartType != BodyPartType.Hand)
+            if (args.Part.Comp is null
+                || args.Part.Comp.PartType != BodyPartType.Hand)
                 return;
-
             RemoveHand(uid, args.Slot);
         }
 
+        private void HandleBodyPartEnabled(EntityUid uid, HandsComponent component, ref BodyPartEnabledEvent args) =>
+            TryAddHand(uid, component, args.Part, SharedBodySystem.GetPartSlotContainerId(args.Part.Comp.ParentSlot?.Id ?? string.Empty));
+
+        private void HandleBodyPartDisabled(EntityUid uid, HandsComponent component, ref BodyPartDisabledEvent args)
+        {
+            if (TerminatingOrDeleted(uid)
+                || args.Part.Comp is null
+                || args.Part.Comp.PartType != BodyPartType.Hand)
+                return;
+
+            RemoveHand(uid, SharedBodySystem.GetPartSlotContainerId(args.Part.Comp.ParentSlot?.Id ?? string.Empty));
+        }
+
+        // Shitmed Change End
         #region pulling
 
         private void HandlePullStarted(EntityUid uid, HandsComponent component, PullStartedMessage args)
@@ -170,7 +197,7 @@ private void HandlePullStopped(EntityUid uid, HandsComponent component, PullStop
 
         private bool HandleThrowItem(ICommonSession? playerSession, EntityCoordinates coordinates, EntityUid entity)
         {
-            if (playerSession?.AttachedEntity is not {Valid: true} player || !Exists(player) || !coordinates.IsValid(EntityManager))
+            if (playerSession?.AttachedEntity is not { Valid: true } player || !Exists(player) || !coordinates.IsValid(EntityManager))
                 return false;
 
             return ThrowHeldItem(player, coordinates);
@@ -195,7 +222,7 @@ public bool ThrowHeldItem(EntityUid player, EntityCoordinates coordinates, float
             {
                 var splitStack = _stackSystem.Split(throwEnt, 1, EntityManager.GetComponent<TransformComponent>(player).Coordinates, stack);
 
-                if (splitStack is not {Valid: true})
+                if (splitStack is not { Valid: true })
                     return false;
 
                 throwEnt = splitStack.Value;

+ 17 - 0
Content.Server/Kitchen/Components/SharpComponent.cs

@@ -12,4 +12,21 @@ public sealed partial class SharpComponent : Component
 
     [DataField("butcherDelayModifier")]
     public float ButcherDelayModifier = 1.0f;
+    /// <summary>
+    /// Shitmed: Whether this item had <c>SurgeryToolComponent</c> before sharp was added.
+    /// </summary>
+    [DataField]
+    public bool HadSurgeryTool;
+
+    /// <summary>
+    /// Shitmed: Whether this item had <c>ScalpelComponent</c> before sharp was added.
+    /// </summary>
+    [DataField]
+    public bool HadScalpel;
+
+    /// <summary>
+    /// Shitmed: Whether this item had <c>BoneSawComponent</c> before sharp was added.
+    /// </summary>
+    [DataField]
+    public bool HadBoneSaw;
 }

+ 5 - 0
Content.Server/Medical/Components/HealthAnalyzerComponent.cs

@@ -37,6 +37,11 @@ public sealed partial class HealthAnalyzerComponent : Component
     /// </summary>
     [DataField]
     public EntityUid? ScannedEntity;
+    /// <summary>
+    /// Shitmed Change: The body part that is currently being scanned.
+    /// </summary>
+    [DataField]
+    public EntityUid? CurrentBodyPart;
 
     /// <summary>
     /// The maximum range in tiles at which the analyzer can receive continuous updates

+ 5 - 0
Content.Server/Medical/Components/MedBookComponent.cs

@@ -38,6 +38,11 @@ public sealed partial class MedBookComponent : Component
     [DataField]
     public EntityUid? ScannedEntity;
 
+    /// <summary>
+    /// Shitmed Change: The body part that is currently being scanned.
+    /// </summary>
+    [DataField]
+    public EntityUid? CurrentBodyPart;
     /// <summary>
     /// The maximum range in tiles at which the analyzer can receive continuous updates
     /// </summary>

+ 9 - 3
Content.Server/Medical/CryoPodSystem.cs

@@ -32,6 +32,9 @@
 using Robust.Shared.Timing;
 using SharedToolSystem = Content.Shared.Tools.Systems.SharedToolSystem;
 
+using Content.Shared.Actions; // Shitmed Change
+using Content.Shared.Bed.Sleep; // Shitmed Change
+
 namespace Content.Server.Medical;
 
 public sealed partial class CryoPodSystem : SharedCryoPodSystem
@@ -50,7 +53,7 @@ public sealed partial class CryoPodSystem : SharedCryoPodSystem
     [Dependency] private readonly ReactiveSystem _reactiveSystem = default!;
     [Dependency] private readonly IAdminLogManager _adminLogger = default!;
     [Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
-
+    [Dependency] private readonly SleepingSystem _sleepingSystem = default!;
     public override void Initialize()
     {
         base.Initialize();
@@ -202,7 +205,8 @@ private void OnActivateUI(Entity<CryoPodComponent> entity, ref AfterActivatableU
                 : 0,
             null,
             null,
-            null
+            null,
+            null // Shitmed Change
         ));
     }
 
@@ -237,7 +241,7 @@ private void OnPowerChanged(Entity<CryoPodComponent> entity, ref PowerChangedEve
         {
             return;
         }
-
+        var insidePod = entity.Comp.BodyContainer.ContainedEntity; // Shitmed Change
         if (args.Powered)
         {
             EnsureComp<ActiveCryoPodComponent>(entity);
@@ -245,6 +249,8 @@ private void OnPowerChanged(Entity<CryoPodComponent> entity, ref PowerChangedEve
         else
         {
             RemComp<ActiveCryoPodComponent>(entity);
+            if (insidePod is { } patient) // Shitmed Change
+                _sleepingSystem.TryWaking(patient);
             _uiSystem.CloseUi(entity.Owner, HealthAnalyzerUiKey.Key);
         }
         UpdateAppearance(entity.Owner, entity.Comp);

+ 24 - 6
Content.Server/Medical/HealingSystem.cs

@@ -21,7 +21,8 @@
 using Content.Shared.Stacks;
 using Robust.Shared.Audio.Systems;
 using Robust.Shared.Random;
-
+using Content.Shared.Body.Systems; // Shitmed Change
+using Content.Shared._Shitmed.Targeting; // Shitmed Change
 namespace Content.Server.Medical;
 
 public sealed class HealingSystem : EntitySystem
@@ -29,6 +30,7 @@ public sealed class HealingSystem : EntitySystem
     [Dependency] private readonly SharedAudioSystem _audio = default!;
     [Dependency] private readonly IAdminLogManager _adminLogger = default!;
     [Dependency] private readonly DamageableSystem _damageable = default!;
+    [Dependency] private readonly SharedTargetingSystem _targetingSystem = default!; // Shitmed Change
     [Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!;
     [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
     [Dependency] private readonly IRobustRandom _random = default!;
@@ -37,7 +39,7 @@ public sealed class HealingSystem : EntitySystem
     [Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!;
     [Dependency] private readonly PopupSystem _popupSystem = default!;
     [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
-
+    [Dependency] private readonly SharedBodySystem _bodySystem = default!; // Shitmed Change
     public override void Initialize()
     {
         base.Initialize();
@@ -83,7 +85,7 @@ private void OnDoAfter(Entity<DamageableComponent> entity, ref HealingDoAfterEve
         if (healing.ModifyBloodLevel != 0)
             _bloodstreamSystem.TryModifyBloodLevel(entity.Owner, healing.ModifyBloodLevel);
 
-        var healed = _damageable.TryChangeDamage(entity.Owner, healing.Damage * _damageable.UniversalTopicalsHealModifier, true, origin: args.Args.User);
+        var healed = _damageable.TryChangeDamage(entity.Owner, healing.Damage * _damageable.UniversalTopicalsHealModifier, true, origin: args.User, canSever: false); // Shitmed Change
 
         if (healed == null && healing.BloodlossModifier != 0)
             return;
@@ -118,7 +120,7 @@ private void OnDoAfter(Entity<DamageableComponent> entity, ref HealingDoAfterEve
         _audio.PlayPvs(healing.HealingEndSound, entity.Owner, AudioHelpers.WithVariation(0.125f, _random).WithVolume(1f));
 
         // Logic to determine the whether or not to repeat the healing action
-        args.Repeat = (HasDamage(entity, healing) && !dontRepeat);
+        args.Repeat = (HasDamage(entity, healing) || IsPartDamaged(args.User, entity)) && !dontRepeat; // Shitmed change
         if (!args.Repeat && !dontRepeat)
             _popupSystem.PopupEntity(Loc.GetString("medical-item-finished-using", ("item", args.Used)), entity.Owner, args.User);
         args.Handled = true;
@@ -156,6 +158,22 @@ private bool HasDamage(Entity<DamageableComponent> ent, HealingComponent healing
         return false;
     }
 
+    // Shitmed Change Start
+    private bool IsPartDamaged(EntityUid user, EntityUid target)
+    {
+        if (!TryComp(user, out TargetingComponent? targeting))
+            return false;
+
+        var (targetType, targetSymmetry) = _bodySystem.ConvertTargetBodyPart(targeting.Target);
+        foreach (var part in _bodySystem.GetBodyChildrenOfType(target, targetType, symmetry: targetSymmetry))
+            if (TryComp<DamageableComponent>(part.Id, out var damageable)
+                && damageable.TotalDamage > part.Component.MinIntegrity)
+                return true;
+
+        return false;
+    }
+
+    // Shitmed Change End
     private void OnHealingUse(Entity<HealingComponent> entity, ref UseInHandEvent args)
     {
         if (args.Handled)
@@ -192,7 +210,7 @@ private bool TryHeal(EntityUid uid, EntityUid user, EntityUid target, HealingCom
         if (TryComp<StackComponent>(uid, out var stack) && stack.Count < 1)
             return false;
 
-        if (!HasDamage((target, targetDamage), component))
+        if (!HasDamage((target, targetDamage), component) && !IsPartDamaged(user, target)) // Shitmed Change
         {
             _popupSystem.PopupEntity(Loc.GetString("medical-item-cant-use", ("item", uid)), uid, user);
             return false;
@@ -242,7 +260,7 @@ public float GetScaledHealingPenalty(EntityUid uid, HealingComponent component)
         if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out var amount, mobThreshold))
             return 1;
 
-        var percentDamage = (float) (damageable.TotalDamage / amount);
+        var percentDamage = (float)(damageable.TotalDamage / amount);
         //basically make it scale from 1 to the multiplier.
         var modifier = percentDamage * (component.SelfHealPenaltyMultiplier - 1) + 1;
         return Math.Max(modifier, 1);

+ 65 - 7
Content.Server/Medical/HealthAnalyzerSystem.cs

@@ -18,6 +18,11 @@
 using Robust.Shared.Audio.Systems;
 using Robust.Shared.Containers;
 using Robust.Shared.Timing;
+// Shitmed Change
+using Content.Shared.Body.Part;
+using Content.Shared.Body.Systems;
+using Content.Shared._Shitmed.Targeting;
+using System.Linq;
 
 namespace Content.Server.Medical;
 
@@ -27,6 +32,8 @@ public sealed class HealthAnalyzerSystem : EntitySystem
     [Dependency] private readonly PowerCellSystem _cell = default!;
     [Dependency] private readonly SharedAudioSystem _audio = default!;
     [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
+
+    [Dependency] private readonly SharedBodySystem _bodySystem = default!; // Shitmed Change
     [Dependency] private readonly ItemToggleSystem _toggle = default!;
     [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
     [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
@@ -40,6 +47,12 @@ public override void Initialize()
         SubscribeLocalEvent<HealthAnalyzerComponent, EntGotInsertedIntoContainerMessage>(OnInsertedIntoContainer);
         SubscribeLocalEvent<HealthAnalyzerComponent, ItemToggledEvent>(OnToggled);
         SubscribeLocalEvent<HealthAnalyzerComponent, DroppedEvent>(OnDropped);
+        // Shitmed Change Start
+        Subs.BuiEvents<HealthAnalyzerComponent>(HealthAnalyzerUiKey.Key, subs =>
+        {
+            subs.Event<HealthAnalyzerPartMessage>(OnHealthAnalyzerPartSelected);
+        });
+        // Shitmed Change End
     }
 
     public override void Update(float frameTime)
@@ -51,7 +64,7 @@ public override void Update(float frameTime)
             if (component.NextUpdate > _timing.CurTime)
                 continue;
 
-            if (component.ScannedEntity is not {} patient)
+            if (component.ScannedEntity is not { } patient)
                 continue;
 
             if (Deleted(patient))
@@ -60,6 +73,16 @@ public override void Update(float frameTime)
                 continue;
             }
 
+            // Shitmed Change Start
+            if (component.CurrentBodyPart != null
+                && (Deleted(component.CurrentBodyPart)
+                || TryComp(component.CurrentBodyPart, out BodyPartComponent? bodyPartComponent)
+                && bodyPartComponent.Body is null))
+            {
+                BeginAnalyzingEntity((uid, component), patient, null);
+                continue;
+            }
+            // Shitmed Change End
             component.NextUpdate = _timing.CurTime + component.UpdateInterval;
 
             //Get distance between health analyzer and the scanned entity
@@ -71,7 +94,7 @@ public override void Update(float frameTime)
                 continue;
             }
 
-            UpdateScannedUser(uid, patient, true);
+            UpdateScannedUser(uid, patient, true, component.CurrentBodyPart); // Shitmed Change
         }
     }
 
@@ -146,14 +169,16 @@ private void OpenUserInterface(EntityUid user, EntityUid analyzer)
     /// </summary>
     /// <param name="healthAnalyzer">The health analyzer that should receive the updates</param>
     /// <param name="target">The entity to start analyzing</param>
-    private void BeginAnalyzingEntity(Entity<HealthAnalyzerComponent> healthAnalyzer, EntityUid target)
+    /// <param name="part">Shitmed Change: The body part to analyze, if any</param>
+    private void BeginAnalyzingEntity(Entity<HealthAnalyzerComponent> healthAnalyzer, EntityUid target, EntityUid? part = null)
     {
         //Link the health analyzer to the scanned entity
         healthAnalyzer.Comp.ScannedEntity = target;
+        healthAnalyzer.Comp.CurrentBodyPart = part; // Shitmed Change
 
         _toggle.TryActivate(healthAnalyzer.Owner);
 
-        UpdateScannedUser(healthAnalyzer, target, true);
+        UpdateScannedUser(healthAnalyzer, target, true, part); // Shitmed Change
     }
 
     /// <summary>
@@ -165,19 +190,44 @@ private void StopAnalyzingEntity(Entity<HealthAnalyzerComponent> healthAnalyzer,
     {
         //Unlink the analyzer
         healthAnalyzer.Comp.ScannedEntity = null;
-
+        healthAnalyzer.Comp.CurrentBodyPart = null; // Shitmed Change
         _toggle.TryDeactivate(healthAnalyzer.Owner);
 
         UpdateScannedUser(healthAnalyzer, target, false);
     }
 
+    // Shitmed Change Start
+    /// <summary>
+    /// Shitmed Change: Handle the selection of a body part on the health analyzer
+    /// </summary>
+    /// <param name="healthAnalyzer">The health analyzer that's receiving the updates</param>
+    /// <param name="args">The message containing the selected part</param>
+    private void OnHealthAnalyzerPartSelected(Entity<HealthAnalyzerComponent> healthAnalyzer, ref HealthAnalyzerPartMessage args)
+    {
+        if (!TryGetEntity(args.Owner, out var owner))
+            return;
+
+        if (args.BodyPart == null)
+        {
+            BeginAnalyzingEntity(healthAnalyzer, owner.Value, null);
+        }
+        else
+        {
+            var (targetType, targetSymmetry) = _bodySystem.ConvertTargetBodyPart(args.BodyPart.Value);
+            if (_bodySystem.GetBodyChildrenOfType(owner.Value, targetType, symmetry: targetSymmetry) is { } part)
+                BeginAnalyzingEntity(healthAnalyzer, owner.Value, part.FirstOrDefault().Id);
+        }
+    }
+    // Shitmed Change End
+
     /// <summary>
     /// Send an update for the target to the healthAnalyzer
     /// </summary>
     /// <param name="healthAnalyzer">The health analyzer</param>
     /// <param name="target">The entity being scanned</param>
     /// <param name="scanMode">True makes the UI show ACTIVE, False makes the UI show INACTIVE</param>
-    public void UpdateScannedUser(EntityUid healthAnalyzer, EntityUid target, bool scanMode)
+    /// <param name="part">Shitmed Change: The body part being scanned, if any</param>
+    public void UpdateScannedUser(EntityUid healthAnalyzer, EntityUid target, bool scanMode, EntityUid? part = null)
     {
         if (!_uiSystem.HasUi(healthAnalyzer, HealthAnalyzerUiKey.Key))
             return;
@@ -202,6 +252,11 @@ public void UpdateScannedUser(EntityUid healthAnalyzer, EntityUid target, bool s
             bleeding = bloodstream.BleedAmount > 0;
         }
 
+        // Shitmed Change Start
+        Dictionary<TargetBodyPart, TargetIntegrity>? body = null;
+        if (HasComp<TargetingComponent>(target))
+            body = _bodySystem.GetBodyPartStatus(target);
+        // Shitmed Change End
         if (TryComp<UnrevivableComponent>(target, out var unrevivableComp) && unrevivableComp.Analyzable)
             unrevivable = true;
 
@@ -211,7 +266,10 @@ public void UpdateScannedUser(EntityUid healthAnalyzer, EntityUid target, bool s
             bloodAmount,
             scanMode,
             bleeding,
-            unrevivable
+            unrevivable,
+            // Shitmed Change
+            body,
+            part != null ? GetNetEntity(part) : null
         ));
     }
 }

+ 5 - 1
Content.Server/Medical/InsideCryoPodSystem.cs

@@ -1,6 +1,9 @@
-using Content.Server.Atmos.EntitySystems;
+using Content.Server.Actions;
+using Content.Server.Atmos.EntitySystems;
 using Content.Server.Body.Systems;
 using Content.Server.Medical.Components;
+using Content.Shared.Bed.Sleep;
+using Content.Shared.Actions;
 using Content.Shared.Medical.Cryogenics;
 
 namespace Content.Server.Medical
@@ -16,6 +19,7 @@ public override void InitializeInsideCryoPod()
             SubscribeLocalEvent<InsideCryoPodComponent, AtmosExposedGetAirEvent>(OnGetAir);
         }
 
+
         #region Atmos handlers
 
         private void OnGetAir(EntityUid uid, InsideCryoPodComponent component, ref AtmosExposedGetAirEvent args)

+ 62 - 8
Content.Server/Medical/MedBookSystem.cs

@@ -17,6 +17,11 @@
 using Robust.Server.GameObjects;
 using Robust.Shared.Containers;
 using Robust.Shared.Timing;
+// Shitmed Change
+using Content.Shared.Body.Part;
+using Content.Shared.Body.Systems;
+using Content.Shared._Shitmed.Targeting;
+using System.Linq;
 
 namespace Content.Server.Medical;
 
@@ -25,6 +30,7 @@ public sealed class MedBookSystem : EntitySystem
     [Dependency] private readonly IGameTiming _timing = default!;
     [Dependency] private readonly PowerCellSystem _cell = default!;
     [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
+    [Dependency] private readonly SharedBodySystem _bodySystem = default!; // Shitmed Change
     [Dependency] private readonly ItemToggleSystem _toggle = default!;
     [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
     [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
@@ -38,6 +44,12 @@ public override void Initialize()
         SubscribeLocalEvent<MedBookComponent, EntGotInsertedIntoContainerMessage>(OnInsertedIntoContainer);
         SubscribeLocalEvent<MedBookComponent, ItemToggledEvent>(OnToggled);
         SubscribeLocalEvent<MedBookComponent, DroppedEvent>(OnDropped);
+        // Shitmed Change Start
+        Subs.BuiEvents<MedBookComponent>(MedBookUiKey.Key, subs =>
+        {
+            subs.Event<MedBookPartMessage>(OnMedBookPartSelected);
+        });
+        // Shitmed Change End
     }
 
     public override void Update(float frameTime)
@@ -57,7 +69,16 @@ public override void Update(float frameTime)
                 StopAnalyzingEntity((uid, component), patient);
                 continue;
             }
-
+            // Shitmed Change Start
+            if (component.CurrentBodyPart != null
+                && (Deleted(component.CurrentBodyPart)
+                || TryComp(component.CurrentBodyPart, out BodyPartComponent? bodyPartComponent)
+                && bodyPartComponent.Body is null))
+            {
+                BeginAnalyzingEntity((uid, component), patient, null);
+                continue;
+            }
+            // Shitmed Change End
             component.NextUpdate = _timing.CurTime + component.UpdateInterval;
 
             //Get distance between health analyzer and the scanned entity
@@ -69,7 +90,7 @@ public override void Update(float frameTime)
                 continue;
             }
 
-            UpdateScannedUser(uid, patient, true);
+            UpdateScannedUser(uid, patient, true, component.CurrentBodyPart); // Shitmed Change
         }
     }
 
@@ -144,14 +165,16 @@ private void OpenUserInterface(EntityUid user, EntityUid analyzer)
     /// </summary>
     /// <param name="medBook">The health analyzer that should receive the updates</param>
     /// <param name="target">The entity to start analyzing</param>
-    private void BeginAnalyzingEntity(Entity<MedBookComponent> medBook, EntityUid target)
+    /// <param name="part">Shitmed Change: The body part to analyze, if any</param>
+    private void BeginAnalyzingEntity(Entity<MedBookComponent> medBook, EntityUid target, EntityUid? part = null)
     {
         //Link the health analyzer to the scanned entity
         medBook.Comp.ScannedEntity = target;
+        medBook.Comp.CurrentBodyPart = part; // Shitmed Change
 
         _toggle.TryActivate(medBook.Owner);
 
-        UpdateScannedUser(medBook, target, true);
+        UpdateScannedUser(medBook, target, true, part); // Shitmed Change
     }
 
     /// <summary>
@@ -163,19 +186,43 @@ private void StopAnalyzingEntity(Entity<MedBookComponent> medBook, EntityUid tar
     {
         //Unlink the analyzer
         medBook.Comp.ScannedEntity = null;
-
+        medBook.Comp.CurrentBodyPart = null; // Shitmed Change
         _toggle.TryDeactivate(medBook.Owner);
 
         UpdateScannedUser(medBook, target, false);
     }
 
+    // Shitmed Change Start
+    /// <summary>
+    /// Shitmed Change: Handle the selection of a body part on the health analyzer
+    /// </summary>
+    /// <param name="medBook">The health analyzer that's receiving the updates</param>
+    /// <param name="args">The message containing the selected part</param>
+    private void OnMedBookPartSelected(Entity<MedBookComponent> medBook, ref MedBookPartMessage args)
+    {
+        if (!TryGetEntity(args.Owner, out var owner))
+            return;
+
+        if (args.BodyPart == null)
+        {
+            BeginAnalyzingEntity(medBook, owner.Value, null);
+        }
+        else
+        {
+            var (targetType, targetSymmetry) = _bodySystem.ConvertTargetBodyPart(args.BodyPart.Value);
+            if (_bodySystem.GetBodyChildrenOfType(owner.Value, targetType, symmetry: targetSymmetry) is { } part)
+                BeginAnalyzingEntity(medBook, owner.Value, part.FirstOrDefault().Id);
+        }
+    }
+    // Shitmed Change End
     /// <summary>
     /// Send an update for the target to the medBook
     /// </summary>
     /// <param name="medBook">The health analyzer</param>
     /// <param name="target">The entity being scanned</param>
     /// <param name="scanMode">True makes the UI show ACTIVE, False makes the UI show INACTIVE</param>
-    public void UpdateScannedUser(EntityUid medBook, EntityUid target, bool scanMode)
+    /// <param name="part">Shitmed Change: The body part being scanned, if any</param>
+    public void UpdateScannedUser(EntityUid medBook, EntityUid target, bool scanMode, EntityUid? part = null)
     {
         if (!_uiSystem.HasUi(medBook, MedBookUiKey.Key))
             return;
@@ -199,7 +246,11 @@ public void UpdateScannedUser(EntityUid medBook, EntityUid target, bool scanMode
             bloodAmount = bloodSolution.FillFraction;
             bleeding = bloodstream.BleedAmount > 0;
         }
-
+        // Shitmed Change Start
+        Dictionary<TargetBodyPart, TargetIntegrity>? body = null;
+        if (HasComp<TargetingComponent>(target))
+            body = _bodySystem.GetBodyPartStatus(target);
+        // Shitmed Change End
         if (TryComp<UnrevivableComponent>(target, out var unrevivableComp) && unrevivableComp.Analyzable)
             unrevivable = true;
 
@@ -209,7 +260,10 @@ public void UpdateScannedUser(EntityUid medBook, EntityUid target, bool scanMode
             bloodAmount,
             scanMode,
             bleeding,
-            unrevivable
+            unrevivable,
+            // Shitmed Change
+            body,
+            part != null ? GetNetEntity(part) : null
         ));
     }
 }

+ 7 - 1
Content.Server/Spawners/EntitySystems/SpawnerSystem.cs

@@ -1,6 +1,8 @@
 using System.Threading;
 using Content.Server.Spawners.Components;
 using Robust.Shared.Random;
+using Content.Shared.Friends.Components; // Shitmed Change
+using Content.Shared._Shitmed.Spawners.EntitySystems; // Shitmed Change
 
 namespace Content.Server.Spawners.EntitySystems;
 
@@ -33,7 +35,11 @@ private void OnTimerFired(EntityUid uid, TimedSpawnerComponent component)
         for (var i = 0; i < number; i++)
         {
             var entity = _random.Pick(component.Prototypes);
-            SpawnAtPosition(entity, coordinates);
+            // Shitmed Change Start
+            var spawnedEnt = SpawnAtPosition(entity, coordinates);
+            var ev = new SpawnerSpawnedEvent(spawnedEnt, HasComp<PettableFriendComponent>(spawnedEnt));
+            RaiseLocalEvent(uid, ev);
+            // Shitmed Change End
         }
     }
 

+ 7 - 7
Content.Server/Wires/WiresSystem.cs

@@ -181,10 +181,10 @@ private void SetOrCreateWireLayout(EntityUid uid, WiresComponent? wires = null)
             return null;
 
         List<WireColor> colors =
-            new((WireColor[]) Enum.GetValues(typeof(WireColor)));
+            new((WireColor[])Enum.GetValues(typeof(WireColor)));
 
         List<WireLetter> letters =
-            new((WireLetter[]) Enum.GetValues(typeof(WireLetter)));
+            new((WireLetter[])Enum.GetValues(typeof(WireLetter)));
 
 
         var wireSet = new List<Wire>();
@@ -499,7 +499,7 @@ private void GenerateSerialNumber(EntityUid uid, WiresComponent? wires = null)
             for (var i = 0; i < 4; i++)
             {
                 // Cyrillic Letters
-                data[i] = (char) _random.Next(0x0410, 0x0430);
+                data[i] = (char)_random.Next(0x0410, 0x0430);
             }
         }
         else
@@ -507,14 +507,14 @@ private void GenerateSerialNumber(EntityUid uid, WiresComponent? wires = null)
             for (var i = 0; i < 4; i++)
             {
                 // Letters
-                data[i] = (char) _random.Next(0x41, 0x5B);
+                data[i] = (char)_random.Next(0x41, 0x5B);
             }
         }
 
         for (var i = 5; i < 9; i++)
         {
             // Digits
-            data[i] = (char) _random.Next(0x30, 0x3A);
+            data[i] = (char)_random.Next(0x30, 0x3A);
         }
 
         wires.SerialNumber = new string(data);
@@ -542,7 +542,7 @@ private void UpdateUserInterface(EntityUid uid, WiresComponent? wires = null, Us
         var statuses = new List<(int position, object key, object value)>();
         foreach (var (key, value) in wires.Statuses)
         {
-            var valueCast = ((int position, StatusLightData? value)) value;
+            var valueCast = ((int position, StatusLightData? value))value;
             statuses.Add((valueCast.position, key, valueCast.value!));
         }
 
@@ -792,7 +792,7 @@ public bool TryGetData<T>(EntityUid uid, object identifier, [NotNullWhen(true)]
             return false;
         }
 
-        data = (T) result;
+        data = (T)result;
 
         return true;
     }

+ 43 - 0
Content.Server/_Shitmed/Autodoc/AutodocSafetyWireAction.cs

@@ -0,0 +1,43 @@
+// SPDX-FileCopyrightText: 2024 deltanedas <39013340+deltanedas@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 deltanedas <@deltanedas:kde.org>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Server.Wires;
+using Content.Shared._Shitmed.Autodoc.Components;
+using Content.Shared._Shitmed.Autodoc.Systems;
+using Content.Shared.Wires;
+
+namespace Content.Server._Shitmed.Autodoc;
+
+public sealed partial class AutodocSafetyWireAction : ComponentWireAction<AutodocComponent>
+{
+    public override Color Color { get; set; } = Color.Red;
+    public override string Name { get; set; } = "wire-name-autodoc-safety";
+
+    public override StatusLightState? GetLightState(Wire wire, AutodocComponent comp)
+        => comp.RequireSleeping ? StatusLightState.On : StatusLightState.Off;
+
+    public override object StatusKey { get; } = AutodocWireStatus.SafetyIndicator;
+
+    public override bool Cut(EntityUid user, Wire wire, AutodocComponent comp)
+    {
+        var uid = wire.Owner;
+        EntityManager.System<SharedAutodocSystem>().SetSafety((uid, comp), false);
+        return true;
+    }
+
+    public override bool Mend(EntityUid user, Wire wire, AutodocComponent comp)
+    {
+        var uid = wire.Owner;
+        EntityManager.System<SharedAutodocSystem>().SetSafety((uid, comp), true);
+        return true;
+    }
+
+    public override void Pulse(EntityUid user, Wire wire, AutodocComponent comp)
+    {
+        var uid = wire.Owner;
+        EntityManager.System<SharedAutodocSystem>().SetSafety((uid, comp), !comp.RequireSleeping);
+    }
+}

+ 57 - 0
Content.Server/_Shitmed/Autodoc/Systems/AutodocSystem.cs

@@ -0,0 +1,57 @@
+// SPDX-FileCopyrightText: 2024 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2024 deltanedas <39013340+deltanedas@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 deltanedas <@deltanedas:kde.org>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Server.Body.Components;
+using Content.Server.Body.Systems;
+using Content.Server.Chat.Systems;
+using Content.Shared.DoAfter;
+using Content.Shared.Power.EntitySystems;
+using Content.Shared._Shitmed.Autodoc.Components;
+using Content.Shared._Shitmed.Autodoc.Systems;
+
+namespace Content.Server._Shitmed.Autodoc.Systems;
+
+public sealed class AutodocSystem : SharedAutodocSystem
+{
+    [Dependency] private readonly InternalsSystem _internals = default!;
+    [Dependency] private readonly ChatSystem _chat = default!;
+    [Dependency] private readonly SharedPowerReceiverSystem _power = default!;
+
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
+
+        var query = EntityQueryEnumerator<ActiveAutodocComponent, AutodocComponent>();
+        var now = Timing.CurTime;
+        while (query.MoveNext(out var uid, out var active, out var comp))
+        {
+            if (now < active.NextUpdate)
+                continue;
+
+            active.NextUpdate = now + comp.UpdateDelay;
+            if (HasComp<ActiveDoAfterComponent>(uid) || !_power.IsPowered(uid))
+                continue;
+
+            if (Proceed((uid, comp, active)))
+                RemCompDeferred<ActiveAutodocComponent>(uid);
+        }
+    }
+
+    protected override void WakePatient(EntityUid patient)
+    {
+        // incase they are using nitrous, disconnect it so they can get woken up later on
+        if (TryComp<InternalsComponent>(patient, out var internals) && _internals.AreInternalsWorking(patient, internals))
+            _internals.DisconnectTank((patient, internals));
+
+        base.WakePatient(patient);
+    }
+
+    public override void Say(EntityUid uid, string msg)
+    {
+        _chat.TrySendInGameICMessage(uid, msg, InGameICChatType.Speak, hideChat: false, hideLog: true, checkRadioPrefix: false);
+    }
+}

+ 57 - 0
Content.Server/_Shitmed/Body/BodyEffects/Subsystems/RandomStatusActivationSystem.cs

@@ -0,0 +1,57 @@
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2025 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Shared._Shitmed.BodyEffects.Subsystems;
+using Content.Shared.Body.Organ;
+using Content.Shared.StatusEffect;
+using Robust.Shared.Timing;
+using Robust.Shared.Random;
+
+namespace Content.Server._Shitmed.BodyEffects.Subsystems;
+
+public sealed class RandomStatusActivationSystem : EntitySystem
+{
+    [Dependency] private readonly IGameTiming _timing = default!;
+    [Dependency] private readonly StatusEffectsSystem _effects = default!;
+    [Dependency] private readonly IRobustRandom _random = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+        SubscribeLocalEvent<RandomStatusActivationComponent, ComponentInit>(OnInit);
+    }
+
+    private void OnInit(EntityUid uid, RandomStatusActivationComponent component, ComponentInit args) => GetRandomTime(component);
+    private void GetRandomTime(RandomStatusActivationComponent component)
+    {
+        var minTime = component.MinActivationTime;
+        var maxTime = component.MaxActivationTime;
+        var randomSeconds = _random.NextDouble() * (maxTime - minTime).TotalSeconds;
+        var randomSpan = TimeSpan.FromSeconds(randomSeconds);
+        component.NextUpdate = _timing.CurTime + minTime + randomSpan;
+    }
+
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
+
+        var query = EntityQueryEnumerator<RandomStatusActivationComponent>();
+        var now = _timing.CurTime;
+        while (query.MoveNext(out var uid, out var comp))
+        {
+            if (now < comp.NextUpdate)
+                continue;
+
+            if (!TryComp<StatusEffectsComponent>(uid, out var effects))
+                continue;
+
+            foreach (var (key, component) in comp.StatusEffects)
+                _effects.TryAddStatusEffect(uid, key, comp.Duration ?? TimeSpan.FromSeconds(1), refresh: true, component, effects);
+
+            GetRandomTime(comp);
+        }
+    }
+}

+ 43 - 0
Content.Server/_Shitmed/Body/Organ/HeartSystem.cs

@@ -0,0 +1,43 @@
+// SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Shared.Body.Events;
+using Content.Server.Body.Components;
+using Content.Shared.Body.Systems;
+using Content.Shared._Shitmed.Body.Organ;
+using Content.Server._Shitmed.DelayedDeath;
+
+namespace Content.Server._Shitmed.Body.Organ;
+
+public sealed class HeartSystem : EntitySystem
+{
+    [Dependency] private readonly SharedBodySystem _bodySystem = default!;
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<HeartComponent, OrganAddedToBodyEvent>(HandleAddition);
+        SubscribeLocalEvent<HeartComponent, OrganRemovedFromBodyEvent>(HandleRemoval);
+    }
+
+    private void HandleRemoval(EntityUid uid, HeartComponent _, ref OrganRemovedFromBodyEvent args)
+    {
+        if (TerminatingOrDeleted(uid) || TerminatingOrDeleted(args.OldBody))
+            return;
+
+        // TODO: Add some form of very violent bleeding effect.
+        EnsureComp<DelayedDeathComponent>(args.OldBody);
+    }
+
+    private void HandleAddition(EntityUid uid, HeartComponent _, ref OrganAddedToBodyEvent args)
+    {
+        if (TerminatingOrDeleted(uid) || TerminatingOrDeleted(args.Body))
+            return;
+
+        if (_bodySystem.TryGetBodyOrganEntityComps<BrainComponent>(args.Body, out var _))
+            RemComp<DelayedDeathComponent>(args.Body);
+    }
+    // Shitmed-End
+}

+ 32 - 0
Content.Server/_Shitmed/Body/Organ/StatusEffectOrganComponent.cs

@@ -0,0 +1,32 @@
+// SPDX-FileCopyrightText: 2024 deltanedas <39013340+deltanedas@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 deltanedas <@deltanedas:kde.org>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Shared.StatusEffect;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Server._Shitmed.Body.Organ;
+
+[RegisterComponent, Access(typeof(StatusEffectOrganSystem))]
+[AutoGenerateComponentPause]
+public sealed partial class StatusEffectOrganComponent : Component
+{
+    /// <summary>
+    /// List of status effects and components to refresh while the organ is installed.
+    /// </summary>
+    [DataField(required: true)]
+    public Dictionary<ProtoId<StatusEffectPrototype>, string> Refresh = new();
+
+    /// <summary>
+    /// How long to wait between each refresh.
+    /// Effects can only last at most this long once the organ is removed.
+    /// </summary>
+    [DataField]
+    public TimeSpan Delay = TimeSpan.FromSeconds(5);
+
+    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
+    public TimeSpan NextUpdate = TimeSpan.Zero;
+}

+ 39 - 0
Content.Server/_Shitmed/Body/Organ/StatusEffectOrganSystem.cs

@@ -0,0 +1,39 @@
+// SPDX-FileCopyrightText: 2024 deltanedas <39013340+deltanedas@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 deltanedas <@deltanedas:kde.org>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Shared.Body.Organ;
+using Content.Shared.StatusEffect;
+using Robust.Shared.Timing;
+
+namespace Content.Server._Shitmed.Body.Organ;
+
+public sealed class StatusEffectOrganSystem : EntitySystem
+{
+    [Dependency] private readonly IGameTiming _timing = default!;
+    [Dependency] private readonly StatusEffectsSystem _effects = default!;
+
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
+
+        var query = EntityQueryEnumerator<StatusEffectOrganComponent, OrganComponent>();
+        var now = _timing.CurTime;
+        while (query.MoveNext(out var uid, out var comp, out var organ))
+        {
+            if (now < comp.NextUpdate || organ.Body is not {} body)
+                continue;
+
+            comp.NextUpdate = now + comp.Delay;
+            if (!TryComp<StatusEffectsComponent>(body, out var effects))
+                continue;
+
+            foreach (var (key, component) in comp.Refresh)
+            {
+                _effects.TryAddStatusEffect(body, key, comp.Delay, refresh: true, component, effects);
+            }
+        }
+    }
+}

+ 31 - 0
Content.Server/_Shitmed/Body/Systems/BodySystem.VitalOrgans.cs

@@ -0,0 +1,31 @@
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Aiden <aiden@djkraz.com>
+// SPDX-FileCopyrightText: 2025 deltanedas <39013340+deltanedas@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 deltanedas <@deltanedas:kde.org>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Server.Body.Components;
+using Content.Shared._Shitmed.Body.Organ;
+using Content.Shared.Body.Components;
+
+namespace Content.Server.Body.Systems;
+
+public partial class BodySystem
+{
+    /// <summary>
+    /// Returns whether an entity is missing a brain and heart.
+    /// If it does not have a body this returns false.
+    /// </summary>
+    public bool MissingVitalOrgans(EntityUid uid)
+    {
+        if (!TryComp<BodyComponent>(uid, out var body))
+            return false; // no organs to be missing
+
+        var ent = (uid, body);
+        if (!TryGetBodyOrganEntityComps<BrainComponent>(ent, out _))
+            return false;
+
+        return TryGetBodyOrganEntityComps<HeartComponent>(ent, out _);
+    }
+}

+ 67 - 0
Content.Server/_Shitmed/Body/Systems/DebrainedSystem.cs

@@ -0,0 +1,67 @@
+// SPDX-FileCopyrightText: 2024 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Server._Shitmed.DelayedDeath;
+using Content.Shared._Shitmed.Body.Organ;
+using Content.Shared.Body.Systems;
+using Content.Server.Popups;
+using Content.Shared.Speech;
+using Content.Shared.Standing;
+using Content.Shared.Stunnable;
+
+namespace Content.Server._Shitmed.Body.Systems;
+
+/// <summary>
+///     This system handles behavior on entities when they lose their head or their brains are removed.
+///     MindComponent fuckery should still be mainly handled on BrainSystem as usual.
+/// </summary>
+public sealed class DebrainedSystem : EntitySystem
+{
+    [Dependency] private readonly SharedBodySystem _bodySystem = default!;
+    [Dependency] private readonly PopupSystem _popupSystem = default!;
+    [Dependency] private readonly StandingStateSystem _standingSystem = default!;
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<DebrainedComponent, ComponentInit>(OnComponentInit);
+        SubscribeLocalEvent<DebrainedComponent, ComponentRemove>(OnComponentRemove);
+        SubscribeLocalEvent<DebrainedComponent, SpeakAttemptEvent>(OnSpeakAttempt);
+        SubscribeLocalEvent<DebrainedComponent, StandAttemptEvent>(OnStandAttempt);
+    }
+
+    private void OnComponentInit(EntityUid uid, DebrainedComponent _, ComponentInit args)
+    {
+        if (TerminatingOrDeleted(uid))
+            return;
+
+        EnsureComp<DelayedDeathComponent>(uid);
+        EnsureComp<StunnedComponent>(uid);
+        _standingSystem.Down(uid);
+    }
+
+    private void OnComponentRemove(EntityUid uid, DebrainedComponent _, ComponentRemove args)
+    {
+        if (TerminatingOrDeleted(uid))
+            return;
+
+        RemComp<DelayedDeathComponent>(uid);
+        RemComp<StunnedComponent>(uid);
+        if (_bodySystem.TryGetBodyOrganEntityComps<HeartComponent>(uid, out var _))
+            RemComp<DelayedDeathComponent>(uid);
+    }
+
+    private void OnSpeakAttempt(EntityUid uid, DebrainedComponent _, SpeakAttemptEvent args)
+    {
+        _popupSystem.PopupEntity(Loc.GetString("speech-muted"), uid, uid);
+        args.Cancel();
+    }
+
+    private void OnStandAttempt(EntityUid uid, DebrainedComponent _, StandAttemptEvent args)
+    {
+        args.Cancel();
+    }
+}

+ 91 - 0
Content.Server/_Shitmed/Body/Systems/EyesSystem.cs

@@ -0,0 +1,91 @@
+// SPDX-FileCopyrightText: 2024 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Shared.Body.Components;
+using Content.Shared._Shitmed.Body.Organ;
+using Content.Shared.Eye.Blinding.Components;
+using Content.Shared.Eye.Blinding.Systems;
+
+namespace Content.Server.Body.Systems
+{
+    public sealed class EyesSystem : EntitySystem
+    {
+        [Dependency] private readonly IEntityManager _entityManager = default!;
+        [Dependency] private readonly BlindableSystem _blindableSystem = default!;
+        [Dependency] private readonly BodySystem _bodySystem = default!;
+
+        public override void Initialize()
+        {
+            base.Initialize();
+
+            SubscribeLocalEvent<EyesComponent, OrganEnabledEvent>(OnOrganEnabled);
+            SubscribeLocalEvent<EyesComponent, OrganDisabledEvent>(OnOrganDisabled);
+        }
+
+        private void HandleSight(EntityUid newEntity, EntityUid oldEntity)
+        {
+            if (TerminatingOrDeleted(newEntity) || TerminatingOrDeleted(oldEntity))
+                return;
+
+            BlindableComponent? newSight;
+            BlindableComponent? oldSight;
+            //transfer existing component to organ
+            if (!TryComp(newEntity, out newSight))
+                newSight = EnsureComp<BlindableComponent>(newEntity);
+
+            if (!TryComp(oldEntity, out oldSight))
+                oldSight = EnsureComp<BlindableComponent>(oldEntity);
+
+            //give new sight all values of old sight
+            _blindableSystem.TransferBlindness(newSight, oldSight, newEntity);
+
+            var hasOtherEyes = false;
+            //check for other eye components on owning body and owning body organs (if old entity has a body)
+            if (TryComp<BodyComponent>(oldEntity, out var body))
+            {
+                if (TryComp<EyesComponent>(oldEntity, out var bodyEyes)) //some bodies see through their skin!!! (slimes)
+                    hasOtherEyes = true;
+                else
+                {
+                    foreach (var (organ, _) in _bodySystem.GetBodyOrgans(oldEntity, body))
+                    {
+                        if (TryComp<EyesComponent>(organ, out var eyes))
+                        {
+                            hasOtherEyes = true;
+                            break;
+                        }
+                    }
+                    //TODO (MS14): Should we do this for body parts too? might be a little overpowered but could be funny/interesting
+                }
+            }
+
+            //if there are no existing eye components for the old entity - set old sight to be blind otherwise leave it as is
+            if (!hasOtherEyes && !TryComp<EyesComponent>(oldEntity, out var self))
+                _blindableSystem.AdjustEyeDamage((oldEntity, oldSight), oldSight.MaxDamage);
+
+        }
+
+        private void OnOrganEnabled(EntityUid uid, EyesComponent component, OrganEnabledEvent args)
+        {
+            if (TerminatingOrDeleted(uid)
+            || args.Organ.Comp.Body is not { Valid: true } body)
+                return;
+
+            RemComp<TemporaryBlindnessComponent>(body);
+            HandleSight(uid, body);
+        }
+
+        private void OnOrganDisabled(EntityUid uid, EyesComponent component, OrganDisabledEvent args)
+        {
+            if (TerminatingOrDeleted(uid)
+            || args.Organ.Comp.Body is not { Valid: true } body)
+                return;
+
+            EnsureComp<TemporaryBlindnessComponent>(body);
+            HandleSight(body, uid);
+        }
+    }
+}

+ 60 - 0
Content.Server/_Shitmed/Cybernetics/CyberneticsSystem.cs

@@ -0,0 +1,60 @@
+// SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Server.Emp;
+using Content.Shared.Body.Part;
+using Content.Shared.Body.Organ;
+using Content.Shared._Shitmed.Body.Organ;
+using Content.Shared._Shitmed.Body.Events;
+using Content.Shared._Shitmed.Cybernetics;
+
+namespace Content.Server._Shitmed.Cybernetics;
+
+internal sealed class CyberneticsSystem : EntitySystem
+{
+    public override void Initialize()
+    {
+        SubscribeLocalEvent<CyberneticsComponent, EmpPulseEvent>(OnEmpPulse);
+        SubscribeLocalEvent<CyberneticsComponent, EmpDisabledRemoved>(OnEmpDisabledRemoved);
+    }
+    private void OnEmpPulse(Entity<CyberneticsComponent> cyberEnt, ref EmpPulseEvent ev)
+    {
+        if (!cyberEnt.Comp.Disabled)
+        {
+            ev.Affected = true;
+            ev.Disabled = true;
+            cyberEnt.Comp.Disabled = true;
+
+            if (HasComp<OrganComponent>(cyberEnt))
+            {
+                var disableEvent = new OrganEnableChangedEvent(false);
+                RaiseLocalEvent(cyberEnt, ref disableEvent);
+            }
+            else if (HasComp<BodyPartComponent>(cyberEnt))
+            {
+                var disableEvent = new BodyPartEnableChangedEvent(false);
+                RaiseLocalEvent(cyberEnt, ref disableEvent);
+            }
+        }
+    }
+
+    private void OnEmpDisabledRemoved(Entity<CyberneticsComponent> cyberEnt, ref EmpDisabledRemoved ev)
+    {
+        if (cyberEnt.Comp.Disabled)
+        {
+            cyberEnt.Comp.Disabled = false;
+            if (HasComp<OrganComponent>(cyberEnt))
+            {
+                var enableEvent = new OrganEnableChangedEvent(true);
+                RaiseLocalEvent(cyberEnt, ref enableEvent);
+            }
+            else if (HasComp<BodyPartComponent>(cyberEnt))
+            {
+                var enableEvent = new BodyPartEnableChangedEvent(true);
+                RaiseLocalEvent(cyberEnt, ref enableEvent);
+            }
+        }
+    }
+}

+ 21 - 0
Content.Server/_Shitmed/DelayedDeath/DelayedDeathComponent.cs

@@ -0,0 +1,21 @@
+// SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace Content.Server._Shitmed.DelayedDeath;
+
+[RegisterComponent]
+public sealed partial class DelayedDeathComponent : Component
+{
+    /// <summary>
+    /// How long it takes to kill the entity.
+    /// </summary>
+    [DataField]
+    public float DeathTime = 60;
+
+    /// <summary>
+    /// How long it has been since the delayed death timer started.
+    /// </summary>
+    public float DeathTimer;
+}

+ 55 - 0
Content.Server/_Shitmed/DelayedDeath/DelayedDeathSystem.cs

@@ -0,0 +1,55 @@
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Aiden <aiden@djkraz.com>
+// SPDX-FileCopyrightText: 2025 deltanedas <39013340+deltanedas@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 deltanedas <@deltanedas:kde.org>
+// SPDX-FileCopyrightText: 2025 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Server.Chat.Systems;
+using Content.Shared.Medical;
+using Content.Shared.Mobs;
+using Content.Shared.Mobs.Components;
+using Content.Shared.Mobs.Systems;
+
+namespace Content.Server._Shitmed.DelayedDeath;
+
+public partial class DelayedDeathSystem : EntitySystem
+{
+    [Dependency] private readonly ChatSystem _chat = default!;
+    [Dependency] private readonly MobStateSystem _mobState = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<DelayedDeathComponent, TargetBeforeDefibrillatorZapsEvent>(OnDefibZap);
+    }
+
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
+
+        using var query = EntityQueryEnumerator<DelayedDeathComponent, MobStateComponent>();
+        while (query.MoveNext(out var ent, out var comp, out var mob))
+        {
+            comp.DeathTimer += frameTime;
+
+            if (comp.DeathTimer >= comp.DeathTime && !_mobState.IsDead(ent, mob))
+            {
+                // go crit then dead so deathgasp can happen
+                _mobState.ChangeMobState(ent, MobState.Critical, mob);
+                _mobState.ChangeMobState(ent, MobState.Dead, mob);
+            }
+        }
+    }
+
+    private void OnDefibZap(Entity<DelayedDeathComponent> ent, ref TargetBeforeDefibrillatorZapsEvent args)
+    {
+        // can't defib someone without a heart or brain pal
+        args.Cancel();
+
+        _chat.TrySendInGameICMessage(args.Defib, Loc.GetString("defibrillator-missing-organs"),
+            InGameICChatType.Speak, true);
+    }
+}

+ 25 - 0
Content.Server/_Shitmed/Destructible/Thresholds/Behaviors/GibPartBehavior.cs

@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: 2024 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Shared.Body.Part;
+using JetBrains.Annotations;
+
+// Leaving this one in the default namespace because I am afraid to test it
+// in the Shitmed namespace lmao.
+namespace Content.Server.Destructible.Thresholds.Behaviors;
+
+[UsedImplicitly]
+[DataDefinition]
+public sealed partial class GibPartBehavior : IThresholdBehavior
+{
+    public void Execute(EntityUid owner, DestructibleSystem system, EntityUid? cause = null)
+    {
+        if (!system.EntityManager.TryGetComponent(owner, out BodyPartComponent? part))
+            return;
+
+        system.BodySystem.GibPart(owner, part);
+    }
+}

+ 71 - 0
Content.Server/_Shitmed/Medical/Surgery/GhettoSurgerySystem.cs

@@ -0,0 +1,71 @@
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 deltanedas <39013340+deltanedas@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 deltanedas <@deltanedas:kde.org>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Server.Kitchen.Components;
+using Content.Shared._Shitmed.Medical.Surgery.Tools;
+using Robust.Shared.Audio;
+
+namespace Content.Server._Shitmed.Medical.Surgery;
+
+/// <summary>
+/// Makes all sharp things usable for incisions and sawing through bones, though worse than any other kind of ghetto analogue.
+/// </summary>
+public sealed partial class GhettoSurgerySystem : EntitySystem
+{
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<SharpComponent, MapInitEvent>(OnSharpInit);
+        SubscribeLocalEvent<SharpComponent, ComponentShutdown>(OnSharpShutdown);
+    }
+
+    private void OnSharpInit(Entity<SharpComponent> ent, ref MapInitEvent args)
+    {
+        if (EnsureComp<SurgeryToolComponent>(ent, out var tool))
+        {
+            ent.Comp.HadSurgeryTool = true;
+        }
+        else
+        {
+            tool.StartSound = new SoundPathSpecifier("/Audio/_Shitmed/Medical/Surgery/scalpel1.ogg");
+            tool.EndSound = new SoundPathSpecifier("/Audio/_Shitmed/Medical/Surgery/scalpel2.ogg");
+            Dirty(ent.Owner, tool);
+        }
+
+        if (EnsureComp<ScalpelComponent>(ent, out var scalpel))
+        {
+            ent.Comp.HadScalpel = true;
+        }
+        else
+        {
+            scalpel.Speed = 0.3f;
+            Dirty(ent.Owner, scalpel);
+        }
+
+        if (EnsureComp<BoneSawComponent>(ent, out var saw))
+        {
+            ent.Comp.HadBoneSaw = true;
+        }
+        else
+        {
+            saw.Speed = 0.2f;
+            Dirty(ent.Owner, saw);
+        }
+    }
+
+    private void OnSharpShutdown(Entity<SharpComponent> ent, ref ComponentShutdown args)
+    {
+        if (!ent.Comp.HadSurgeryTool)
+            RemComp<SurgeryToolComponent>(ent);
+
+        if (!ent.Comp.HadScalpel)
+            RemComp<ScalpelComponent>(ent);
+
+        if (!ent.Comp.HadBoneSaw)
+            RemComp<BoneSawComponent>(ent);
+    }
+}

+ 172 - 0
Content.Server/_Shitmed/Medical/Surgery/SurgerySystem.cs

@@ -0,0 +1,172 @@
+// SPDX-FileCopyrightText: 2024 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2024 deltanedas <39013340+deltanedas@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 deltanedas <@deltanedas:kde.org>
+// SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Misandry <mary@thughunt.ing>
+// SPDX-FileCopyrightText: 2025 gus <august.eymann@gmail.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Server.Atmos.Rotting;
+using Content.Server.Body.Systems;
+using Content.Server.Chat.Systems;
+using Content.Shared.Body.Part;
+using Content.Server.Popups;
+using Content.Shared.Bed.Sleep;
+using Content.Shared.Damage;
+using Content.Shared.Eye.Blinding.Systems;
+using Content.Shared._Shitmed.Medical.Surgery;
+using Content.Shared._Shitmed.Medical.Surgery.Conditions;
+using Content.Shared._Shitmed.Medical.Surgery.Effects.Step;
+using Content.Shared._Shitmed.Medical.Surgery.Tools;
+using Robust.Server.GameObjects;
+using Robust.Shared.Configuration;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+using Content.Shared.Verbs;
+
+namespace Content.Server._Shitmed.Medical.Surgery;
+
+public sealed class SurgerySystem : SharedSurgerySystem
+{
+    [Dependency] private readonly BodySystem _body = default!;
+    [Dependency] private readonly ChatSystem _chat = default!;
+    [Dependency] private readonly IConfigurationManager _config = default!;
+    [Dependency] private readonly DamageableSystem _damageable = default!;
+    [Dependency] private readonly IPrototypeManager _prototypes = default!;
+    [Dependency] private readonly PopupSystem _popup = default!;
+    [Dependency] private readonly UserInterfaceSystem _ui = default!;
+    [Dependency] private readonly RottingSystem _rot = default!;
+    [Dependency] private readonly BlindableSystem _blindableSystem = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<SurgeryToolComponent, GetVerbsEvent<UtilityVerb>>(OnUtilityVerb);
+        SubscribeLocalEvent<SurgeryTargetComponent, SurgeryStepDamageEvent>(OnSurgeryStepDamage);
+        // You might be wondering "why aren't we using StepEvent for these two?" reason being that StepEvent fires off regardless of success on the previous functions
+        // so this would heal entities even if you had a used or incorrect organ.
+        SubscribeLocalEvent<SurgerySpecialDamageChangeEffectComponent, SurgeryStepDamageChangeEvent>(OnSurgerySpecialDamageChange);
+        SubscribeLocalEvent<SurgeryDamageChangeEffectComponent, SurgeryStepDamageChangeEvent>(OnSurgeryDamageChange);
+        SubscribeLocalEvent<SurgeryStepEmoteEffectComponent, SurgeryStepEvent>(OnStepScreamComplete);
+        SubscribeLocalEvent<SurgeryStepSpawnEffectComponent, SurgeryStepEvent>(OnStepSpawnComplete);
+    }
+
+    protected override void RefreshUI(EntityUid body)
+    {
+        var surgeries = new Dictionary<NetEntity, List<EntProtoId>>();
+        foreach (var surgery in AllSurgeries)
+        {
+            if (GetSingleton(surgery) is not { } surgeryEnt)
+                continue;
+
+            foreach (var part in _body.GetBodyChildren(body))
+            {
+                var ev = new SurgeryValidEvent(body, part.Id);
+                RaiseLocalEvent(surgeryEnt, ref ev);
+
+                if (ev.Cancelled)
+                    continue;
+
+                surgeries.GetOrNew(GetNetEntity(part.Id)).Add(surgery);
+            }
+
+        }
+        _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. I love shitcode.
+        */
+        _ui.ServerSendUiMessage(body, SurgeryUIKey.Key, new SurgeryBuiRefreshMessage());
+    }
+    private void SetDamage(EntityUid body,
+        DamageSpecifier damage,
+        float partMultiplier,
+        EntityUid user,
+        EntityUid part)
+    {
+        if (!TryComp<BodyPartComponent>(part, out var partComp))
+            return;
+
+        _damageable.TryChangeDamage(body,
+            damage,
+            true,
+            origin: user,
+            canSever: false,
+            partMultiplier: partMultiplier,
+            targetPart: _body.GetTargetBodyPart(partComp));
+    }
+
+    private void AttemptStartSurgery(Entity<SurgeryToolComponent> ent, EntityUid user, EntityUid target)
+    {
+        if (!IsLyingDown(target, user))
+            return;
+
+        if (user == target)
+        {
+            _popup.PopupEntity(Loc.GetString("surgery-error-self-surgery"), user, user);
+            return;
+        }
+
+        _ui.OpenUi(target, SurgeryUIKey.Key, user);
+        RefreshUI(target);
+    }
+
+    private void OnUtilityVerb(Entity<SurgeryToolComponent> ent, ref GetVerbsEvent<UtilityVerb> args)
+    {
+        if (!args.CanInteract
+            || !args.CanAccess
+            || !HasComp<SurgeryTargetComponent>(args.Target))
+            return;
+
+        var user = args.User;
+        var target = args.Target;
+
+        var verb = new UtilityVerb()
+        {
+            Act = () => AttemptStartSurgery(ent, user, target),
+            Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Specific/Medical/Surgery/scalpel.rsi/"), "scalpel"),
+            Text = Loc.GetString("surgery-verb-text"),
+            Message = Loc.GetString("surgery-verb-message"),
+            DoContactInteraction = true
+        };
+
+        args.Verbs.Add(verb);
+    }
+
+    private void OnSurgeryStepDamage(Entity<SurgeryTargetComponent> ent, ref SurgeryStepDamageEvent args) =>
+        SetDamage(args.Body, args.Damage, args.PartMultiplier, args.User, args.Part);
+
+    private void OnSurgeryDamageChange(Entity<SurgeryDamageChangeEffectComponent> ent, ref SurgeryStepDamageChangeEvent args)
+    {
+        var damageChange = ent.Comp.Damage;
+        if (HasComp<ForcedSleepingComponent>(args.Body))
+            damageChange = damageChange * ent.Comp.SleepModifier;
+
+        SetDamage(args.Body, damageChange, 0.5f, args.User, args.Part);
+    }
+
+    private void OnSurgerySpecialDamageChange(Entity<SurgerySpecialDamageChangeEffectComponent> ent, ref SurgeryStepDamageChangeEvent args)
+    {
+        // Im killing this shit soon too, inshallah.
+        if (ent.Comp.DamageType == "Rot")
+            _rot.ReduceAccumulator(args.Body, TimeSpan.FromSeconds(2147483648)); // BEHOLD, SHITCODE THAT I JUST COPY PASTED. I'll redo it at some point, pinky swear :)
+        /*else if (ent.Comp.DamageType == "Eye"
+            && TryComp(ent, out BlindableComponent? blindComp)
+            && blindComp.EyeDamage > 0)
+            _blindableSystem.AdjustEyeDamage((args.Body, blindComp), -blindComp!.EyeDamage);*/
+    }
+
+    private void OnStepScreamComplete(Entity<SurgeryStepEmoteEffectComponent> ent, ref SurgeryStepEvent args)
+    {
+        if (HasComp<ForcedSleepingComponent>(args.Body))
+            return;
+
+        _chat.TryEmoteWithChat(args.Body, ent.Comp.Emote);
+    }
+    private void OnStepSpawnComplete(Entity<SurgeryStepSpawnEffectComponent> ent, ref SurgeryStepEvent args) =>
+        SpawnAtPosition(ent.Comp.Entity, Transform(args.Body).Coordinates);
+}

+ 15 - 0
Content.Server/_Shitmed/Objectives/Components/RoleplayObjectiveComponent.cs

@@ -0,0 +1,15 @@
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2025 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Server.Objectives.Systems;
+
+namespace Content.Server._Shitmed.Objectives.Components;
+
+/// <summary>
+///     Objective doesn't have a completion objective. Purely exists as a fancier roleplay prompt. Greentext by default.
+/// </summary>
+[RegisterComponent]
+public sealed partial class RoleplayObjectiveComponent : Component;

+ 28 - 0
Content.Server/_Shitmed/Objectives/Systems/RoleplayObjectiveSystem.cs

@@ -0,0 +1,28 @@
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2025 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Server._Shitmed.Objectives.Components;
+using Content.Shared.Mind;
+using Content.Shared.Objectives.Components;
+
+namespace Content.Server._Shitmed.Objectives.Systems;
+
+public sealed class RoleplayObjectiveSystem : EntitySystem
+{
+    [Dependency] private readonly SharedMindSystem _mind = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<RoleplayObjectiveComponent, ObjectiveGetProgressEvent>(OnRoleplayGetProgress);
+    }
+
+    private void OnRoleplayGetProgress(EntityUid uid, RoleplayObjectiveComponent comp, ref ObjectiveGetProgressEvent args)
+    {
+        args.Progress = 1f;
+    }
+}

+ 42 - 0
Content.Server/_Shitmed/OnHit/OnHitSystem.cs

@@ -0,0 +1,42 @@
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2025 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Shared._Shitmed.Medical.Surgery;
+using Content.Shared._Shitmed.OnHit;
+using Content.Shared.Actions;
+using Content.Shared.DoAfter;
+using Robust.Shared.Prototypes;
+using Content.Shared.Cuffs.Components;
+using Content.Shared.Damage.Components;
+using Content.Shared.Weapons.Melee.Events;
+
+namespace Content.Server._Shitmed.OnHit;
+
+public sealed partial class OnHitSystem : SharedOnHitSystem
+{
+    public override void Initialize()
+    {
+        SubscribeLocalEvent<CuffsOnHitComponent, CuffsOnHitDoAfter>(OnCuffsOnHitDoAfter);
+        base.Initialize();
+    }
+    private void OnCuffsOnHitDoAfter(Entity<CuffsOnHitComponent> ent, ref CuffsOnHitDoAfter args)
+    {
+        if (!args.Args.Target.HasValue || args.Handled || args.Cancelled) return;
+
+        var user = args.Args.User;
+        var target = args.Args.Target.Value;
+
+        if (!TryComp<CuffableComponent>(target, out var cuffable) || cuffable.Container.Count != 0)
+            return;
+
+        args.Handled = true;
+
+        var handcuffs = SpawnNextToOrDrop(ent.Comp.HandcuffPrototype, args.User);
+
+        if (!_cuffs.TryAddNewCuffs(target, user, handcuffs, cuffable))
+            QueueDel(handcuffs);
+    }
+}

+ 28 - 0
Content.Server/_Shitmed/StatusEffects/ActivateArtifactEffectSystem.cs

@@ -0,0 +1,28 @@
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2025 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Shared._Shitmed.StatusEffects;
+using Content.Server.Xenoarchaeology.XenoArtifacts;
+
+namespace Content.Server._Shitmed.StatusEffects;
+
+public sealed class ActivateArtifactEffectSystem : EntitySystem
+{
+    [Dependency] private readonly ArtifactSystem _artifact = default!;
+    public override void Initialize()
+    {
+        SubscribeLocalEvent<ActivateArtifactEffectComponent, ComponentInit>(OnInit);
+    }
+    private void OnInit(EntityUid uid, ActivateArtifactEffectComponent component, ComponentInit args)
+    {
+        if (!TryComp<ArtifactComponent>(uid, out var artifact))
+            return;
+
+        _artifact.TryActivateArtifact(uid, logMissing: false);
+    }
+
+
+}

+ 33 - 0
Content.Server/_Shitmed/StatusEffects/ExpelGasSystem.cs

@@ -0,0 +1,33 @@
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2025 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Shared._Shitmed.StatusEffects;
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.Chat.Systems;
+using Robust.Shared.Random;
+
+namespace Content.Server._Shitmed.StatusEffects;
+
+public sealed class ExpelGasEffectSystem : EntitySystem
+{
+    [Dependency] private readonly AtmosphereSystem _atmos = default!;
+    [Dependency] private readonly ChatSystem _chat = default!;
+    [Dependency] private readonly IRobustRandom _random = default!;
+
+    public override void Initialize()
+    {
+        SubscribeLocalEvent<ExpelGasComponent, ComponentInit>(OnInit);
+    }
+    private void OnInit(EntityUid uid, ExpelGasComponent component, ComponentInit args)
+    {
+        var mix = _atmos.GetContainingMixture((uid, Transform(uid)), true, true) ?? new();
+        var gas = _random.Pick(component.PossibleGases);
+        mix.AdjustMoles(gas, 60);
+        _chat.TryEmoteWithChat(uid, "Fart");
+    }
+
+
+}

+ 53 - 0
Content.Server/_Shitmed/StatusEffects/ScrambleDnaEffectSystem.cs

@@ -0,0 +1,53 @@
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2025 SX_7 <sn1.test.preria.2002@gmail.com>
+// SPDX-FileCopyrightText: 2025 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Server.Forensics;
+using Content.Server.Humanoid;
+using Content.Shared._Shitmed.StatusEffects;
+using Content.Shared.Forensics;
+using Content.Shared.Humanoid;
+using Content.Shared.Preferences;
+using Content.Shared.Popups;
+using Content.Shared.Forensics.Components;
+
+namespace Content.Server._Shitmed.StatusEffects;
+
+public sealed class ScrambleDnaEffectSystem : EntitySystem
+{
+    [Dependency] private readonly HumanoidAppearanceSystem _humanoidAppearance = default!;
+    [Dependency] private readonly ForensicsSystem _forensicsSystem = default!;
+    [Dependency] private readonly MetaDataSystem _metaData = default!;
+    [Dependency] private readonly SharedPopupSystem _popup = default!;
+    public override void Initialize()
+    {
+        SubscribeLocalEvent<ScrambleDnaEffectComponent, ComponentInit>(OnInit);
+    }
+
+    private void OnInit(EntityUid uid, ScrambleDnaEffectComponent component, ComponentInit args)
+    {
+        if (TryComp<HumanoidAppearanceComponent>(uid, out var humanoid))
+        {
+            var newProfile = HumanoidCharacterProfile.RandomWithSpecies(humanoid.Species);
+            _humanoidAppearance.LoadProfile(uid, newProfile, humanoid);
+            _metaData.SetEntityName(uid, newProfile.Name);
+            if (TryComp<DnaComponent>(uid, out var dna))
+            {
+                dna.DNA = _forensicsSystem.GenerateDNA();
+
+                var ev = new GenerateDnaEvent { Owner = uid, DNA = dna.DNA };
+                RaiseLocalEvent(uid, ref ev);
+            }
+            if (TryComp<FingerprintComponent>(uid, out var fingerprint))
+            {
+                fingerprint.Fingerprint = _forensicsSystem.GenerateFingerprint();
+            }
+            _popup.PopupEntity(Loc.GetString("scramble-implant-activated-popup"), uid, uid);
+        }
+    }
+
+
+}

+ 53 - 0
Content.Server/_Shitmed/StatusEffects/SpawnEntityEffectSystem.cs

@@ -0,0 +1,53 @@
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2025 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Shared.NPC.Components;
+using Content.Shared.NPC.Systems;
+using Content.Shared._Shitmed.StatusEffects;
+
+namespace Content.Server._Shitmed.StatusEffects;
+
+public sealed class SpawnEntityEffectSystem : EntitySystem
+{
+    [Dependency] private readonly SharedTransformSystem _xformSys = default!;
+    [Dependency] private readonly NpcFactionSystem _factionException = default!;
+
+    public override void Initialize()
+    {
+        SubscribeLocalEvent<SpawnSpiderEggsComponent, ComponentInit>(OnInit);
+        SubscribeLocalEvent<SpawnSlimesComponent, ComponentInit>(OnInit);
+        SubscribeLocalEvent<SpawnEmpComponent, ComponentInit>(OnInit);
+        SubscribeLocalEvent<SpawnGravityWellComponent, ComponentInit>(OnInit);
+        SubscribeLocalEvent<SpawnFlashComponent, ComponentInit>(OnInit);
+        SubscribeLocalEvent<SpawnSmokeComponent, ComponentInit>(OnInit);
+    }
+
+    private void OnInit(EntityUid uid, SpawnEntityEffectComponent component, ComponentInit args)
+    {
+        EntityUid entity;
+
+        if (component.AttachToParent)
+        {
+            entity = SpawnAttachedTo(component.EntityPrototype, Transform(uid).Coordinates);
+            _xformSys.SetParent(entity, uid);
+        }
+        else
+        {
+            entity = Spawn(component.EntityPrototype, Transform(uid).Coordinates);
+        }
+
+        if (component.IsFriendly)
+        {
+            if (EnsureComp<FactionExceptionComponent>(entity, out var comp))
+                return;
+
+            _factionException.IgnoreEntities(entity, new[] { uid });
+        }
+
+    }
+
+
+}

+ 60 - 0
Content.Server/_Shitmed/Targeting/TargetingSystem.cs

@@ -0,0 +1,60 @@
+// SPDX-FileCopyrightText: 2024 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Shared.Body.Systems;
+using Content.Shared.Mobs;
+using Content.Shared._Shitmed.Targeting;
+using Content.Shared._Shitmed.Targeting.Events;
+
+namespace Content.Server._Shitmed.Targeting;
+public sealed class TargetingSystem : SharedTargetingSystem
+{
+    [Dependency] private readonly SharedBodySystem _bodySystem = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+        SubscribeNetworkEvent<TargetChangeEvent>(OnTargetChange);
+        SubscribeLocalEvent<TargetingComponent, MobStateChangedEvent>(OnMobStateChange);
+    }
+
+    private void OnTargetChange(TargetChangeEvent message, EntitySessionEventArgs args)
+    {
+        if (!TryComp<TargetingComponent>(GetEntity(message.Uid), out var target))
+            return;
+
+        target.Target = message.BodyPart;
+        Dirty(GetEntity(message.Uid), target);
+    }
+
+    private void OnMobStateChange(EntityUid uid, TargetingComponent component, MobStateChangedEvent args)
+    {
+        // Revival is handled by the server, so we're keeping all of this here.
+        var changed = false;
+
+        if (args.NewMobState == MobState.Dead)
+        {
+            foreach (var part in GetValidParts())
+            {
+                component.BodyStatus[part] = TargetIntegrity.Dead;
+                changed = true;
+            }
+            // I love groin shitcode.
+            component.BodyStatus[TargetBodyPart.Groin] = TargetIntegrity.Dead;
+        }
+        else if (args.OldMobState == MobState.Dead && (args.NewMobState == MobState.Alive || args.NewMobState == MobState.Critical))
+        {
+            component.BodyStatus = _bodySystem.GetBodyPartStatus(uid);
+            changed = true;
+        }
+
+        if (changed)
+        {
+            Dirty(uid, component);
+            RaiseNetworkEvent(new TargetIntegrityChangeEvent(GetNetEntity(uid)), uid);
+        }
+    }
+}

+ 7 - 2
Content.Shared/Bed/Sleep/SleepingSystem.cs

@@ -66,7 +66,7 @@ public override void Initialize()
         SubscribeLocalEvent<SleepingComponent, UnbuckleAttemptEvent>(OnUnbuckleAttempt);
         SubscribeLocalEvent<SleepingComponent, EmoteAttemptEvent>(OnEmoteAttempt);
 
-        SubscribeLocalEvent<SleepingComponent, BeforeForceSayEvent>(OnChangeForceSay, after: new []{typeof(PainNumbnessSystem)});
+        SubscribeLocalEvent<SleepingComponent, BeforeForceSayEvent>(OnChangeForceSay, after: new[] { typeof(PainNumbnessSystem) });
     }
 
     private void OnUnbuckleAttempt(Entity<SleepingComponent> ent, ref UnbuckleAttemptEvent args)
@@ -214,9 +214,14 @@ private void OnDamageChanged(Entity<SleepingComponent> ent, ref DamageChangedEve
     {
         if (!args.DamageIncreased || args.DamageDelta == null)
             return;
+        /* Shitmed Change Start - Surgery needs this, sorry! If the nocturine gamers get too feisty
+        I'll probably just increase the threshold */
 
-        if (args.DamageDelta.GetTotal() >= ent.Comp.WakeThreshold)
+        if (args.DamageDelta.GetTotal() >= ent.Comp.WakeThreshold
+            && !HasComp<ForcedSleepingComponent>(ent))
             TryWaking((ent, ent.Comp));
+
+        // Shitmed Change End
     }
 
     /// <summary>

+ 59 - 2
Content.Shared/Body/Organ/OrganComponent.cs

@@ -1,16 +1,73 @@
 using Content.Shared.Body.Systems;
 using Robust.Shared.Containers;
 using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes; // Shitmed Change
+using Content.Shared._Shitmed.Medical.Surgery; // Shitmed Change
+using Content.Shared._Shitmed.Medical.Surgery.Tools; // Shitmed Change
 
 namespace Content.Shared.Body.Organ;
 
 [RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
-[Access(typeof(SharedBodySystem))]
-public sealed partial class OrganComponent : Component
+[Access(typeof(SharedBodySystem), typeof(SharedSurgerySystem))]
+public sealed partial class OrganComponent : Component, ISurgeryToolComponent // Shitmed Change
 {
     /// <summary>
     /// Relevant body this organ is attached to.
     /// </summary>
     [DataField, AutoNetworkedField]
     public EntityUid? Body;
+
+    /// <summary>
+    ///     Shitmed Change:Relevant body this organ originally belonged to.
+    ///     FOR WHATEVER FUCKING REASON AUTONETWORKING THIS CRASHES GIBTEST AAAAAAAAAAAAAAA
+    /// </summary>
+    [DataField]
+    public EntityUid? OriginalBody;
+
+    // Shitmed Change Start
+    /// <summary>
+    ///     Shitmed Change: Shitcodey solution to not being able to know what name corresponds to each organ's slot ID
+    ///     without referencing the prototype or hardcoding.
+    /// </summary>
+
+    [DataField]
+    public string SlotId = string.Empty;
+
+    [DataField]
+    public string ToolName { get; set; } = "An organ";
+
+    [DataField]
+    public float Speed { get; set; } = 1f;
+
+    /// <summary>
+    ///     Shitmed Change: If true, the organ will not heal an entity when transplanted into them.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool? Used { get; set; }
+
+
+    /// <summary>
+    ///     When attached, the organ will ensure these components on the entity, and delete them on removal.
+    /// </summary>
+    [DataField]
+    public ComponentRegistry? OnAdd;
+
+    /// <summary>
+    ///     When removed, the organ will ensure these components on the entity, and delete them on insertion.
+    /// </summary>
+    [DataField]
+    public ComponentRegistry? OnRemove;
+
+    /// <summary>
+    ///     Is this organ working or not?
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool Enabled = true;
+
+    /// <summary>
+    ///     Can this organ be enabled or disabled? Used mostly for prop, damaged or useless organs.
+    /// </summary>
+    [DataField]
+    public bool CanEnable = true;
+    // Shitmed Change End
 }

+ 165 - 5
Content.Shared/Body/Part/BodyPartComponent.cs

@@ -1,14 +1,38 @@
-using Content.Shared.Body.Components;
+// SPDX-FileCopyrightText: 2022 Jezithyr <Jezithyr@gmail.com>
+// SPDX-FileCopyrightText: 2023 DrSmugleaf <DrSmugleaf@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2023 Jezithyr <jezithyr@gmail.com>
+// SPDX-FileCopyrightText: 2023 Leon Friedrich <60421075+ElectroJr@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2023 metalgearsloth <comedian_vs_clown@hotmail.com>
+// SPDX-FileCopyrightText: 2024 Aviu00 <93730715+Aviu00@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2024 username <113782077+whateverusername0@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 whateverusername0 <whateveremail>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 deltanedas <39013340+deltanedas@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 deltanedas <@deltanedas:kde.org>
+// SPDX-FileCopyrightText: 2025 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Shared.Body.Components;
 using Content.Shared.Body.Systems;
 using Robust.Shared.Containers;
 using Robust.Shared.GameStates;
 using Robust.Shared.Serialization;
 
+// Shitmed Change
+
+using Content.Shared.Containers.ItemSlots;
+using Content.Shared.FixedPoint;
+using Content.Shared._Shitmed.Medical.Surgery.Tools;
+using Content.Shared._Shitmed.Targeting;
+using Robust.Shared.Prototypes;
+
 namespace Content.Shared.Body.Part;
 
 [RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
-[Access(typeof(SharedBodySystem))]
-public sealed partial class BodyPartComponent : Component
+//[Access(typeof(SharedBodySystem))] // goob edit - all access :godo:
+public sealed partial class BodyPartComponent : Component, ISurgeryToolComponent // Shitmed Change
 {
     // Need to set this on container changes as it may be several transform parents up the hierarchy.
     /// <summary>
@@ -17,9 +41,131 @@ public sealed partial class BodyPartComponent : Component
     [DataField, AutoNetworkedField]
     public EntityUid? Body;
 
+    // Shitmed Change Start
+
+    [DataField, AutoNetworkedField]
+    public BodyPartSlot? ParentSlot;
+
+    /// <summary>
+    /// Shitmed Change: Bleeding stacks to give when this body part is severed.
+    /// Doubled for <see cref="IsVital"/>. parts.
+    /// </summary>
+    [DataField]
+    public float SeverBleeding = 4f;
+
+    [DataField]
+    public string ToolName { get; set; } = "A body part";
+
+    [DataField]
+    public string SlotId = string.Empty;
+
+    [DataField, AutoNetworkedField]
+    public bool? Used { get; set; } = null;
+
+    [DataField]
+    public float Speed { get; set; } = 1f;
+
+    /// <summary>
+    /// Shitmed Change: What's the max health this body part can have?
+    /// </summary>
+    [DataField]
+    public float MinIntegrity;
+
+    /// <summary>
+    /// Whether this body part can be severed or not
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool CanSever = true;
+
+    /// <summary>
+    ///     Shitmed Change: Whether this body part is enabled or not.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool Enabled = true;
+
+    /// <summary>
+    ///     Shitmed Change: Whether this body part can be enabled or not. Used for non-functional prosthetics.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool CanEnable = true;
+
+    /// <summary>
+    /// Whether this body part can attach children or not.
+    /// </summary>
+    [DataField]
+    public bool CanAttachChildren = true;
+
+    /// <summary>
+    ///     Shitmed Change: How long it takes to run another self heal tick on the body part.
+    /// </summary>
+    [DataField]
+    public float HealingTime = 30;
+
+    /// <summary>
+    ///     Shitmed Change: How long it has been since the last self heal tick on the body part.
+    /// </summary>
+    public float HealingTimer;
+
+    /// <summary>
+    ///     Shitmed Change: How much health to heal on the body part per tick.
+    /// </summary>
+    [DataField]
+    public float SelfHealingAmount = 5;
+
+    /// <summary>
+    ///     Shitmed Change: The name of the container for this body part. Used in insertion surgeries.
+    /// </summary>
+    [DataField]
+    public string ContainerName { get; set; } = "part_slot";
+
+    /// <summary>
+    ///     Shitmed Change: The slot for item insertion.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public ItemSlot ItemInsertionSlot = new();
+
+
+    /// <summary>
+    ///     Shitmed Change: Current species. Dictates things like body part sprites.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public string Species { get; set; } = "";
+
+    /// <summary>
+    ///     Shitmed Change: The total damage that has to be dealt to a body part
+    ///     to make possible severing it.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public float SeverIntegrity = 90;
+
+    /// <summary>
+    ///     Shitmed Change: The ID of the base layer for this body part.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public string? BaseLayerId;
+
+    /// <summary>
+    ///     Shitmed Change: On what TargetIntegrity we should re-enable the part.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public TargetIntegrity EnableIntegrity = TargetIntegrity.ModeratelyWounded;
+
+    [DataField, AutoNetworkedField]
+    public Dictionary<TargetIntegrity, float> IntegrityThresholds = new()
+    {
+        { TargetIntegrity.CriticallyWounded, 90 },
+        { TargetIntegrity.HeavilyWounded, 75 },
+        { TargetIntegrity.ModeratelyWounded, 60 },
+        { TargetIntegrity.SomewhatWounded, 40},
+        { TargetIntegrity.LightlyWounded, 20 },
+        { TargetIntegrity.Healthy, 10 },
+    };
+
+
     [DataField, AutoNetworkedField]
     public BodyPartType PartType = BodyPartType.Other;
 
+
     // TODO BODY Replace with a simulation of organs
     /// <summary>
     ///     Whether or not the owning <see cref="Body"/> will die if all
@@ -31,6 +177,20 @@ public sealed partial class BodyPartComponent : Component
     [DataField, AutoNetworkedField]
     public BodyPartSymmetry Symmetry = BodyPartSymmetry.None;
 
+    /// <summary>
+    ///     When attached, the part will ensure these components on the entity, and delete them on removal.
+    /// </summary>
+    [DataField, AlwaysPushInheritance]
+    public ComponentRegistry? OnAdd;
+
+    /// <summary>
+    ///     When removed, the part will ensure these components on the entity, and add them on removal.
+    /// </summary>
+    [DataField, AlwaysPushInheritance]
+    public ComponentRegistry? OnRemove;
+
+    // Shitmed Change End
+
     /// <summary>
     /// Child body parts attached to this body part.
     /// </summary>
@@ -56,7 +216,7 @@ private List<ContainerSlot> BodyPartSlotsVV
 
             foreach (var slotId in Children.Keys)
             {
-                temp.Add((ContainerSlot) containerSystem.GetContainer(Owner, SharedBodySystem.PartSlotContainerIdPrefix+slotId));
+                temp.Add((ContainerSlot)containerSystem.GetContainer(Owner, SharedBodySystem.PartSlotContainerIdPrefix + slotId));
             }
 
             return temp;
@@ -73,7 +233,7 @@ private List<ContainerSlot> OrganSlotsVV
 
             foreach (var slotId in Organs.Keys)
             {
-                temp.Add((ContainerSlot) containerSystem.GetContainer(Owner, SharedBodySystem.OrganSlotContainerIdPrefix+slotId));
+                temp.Add((ContainerSlot)containerSystem.GetContainer(Owner, SharedBodySystem.OrganSlotContainerIdPrefix + slotId));
             }
 
             return temp;

+ 206 - 9
Content.Shared/Body/Systems/SharedBodySystem.Body.cs

@@ -1,3 +1,29 @@
+// SPDX-FileCopyrightText: 2022 Jezithyr <Jezithyr@gmail.com>
+// SPDX-FileCopyrightText: 2022 Paul Ritter <ritter.paul1@googlemail.com>
+// SPDX-FileCopyrightText: 2022 keronshb <54602815+keronshb@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2022 metalgearsloth <metalgearsloth@gmail.com>
+// SPDX-FileCopyrightText: 2023 Doru991 <75124791+Doru991@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2023 DrSmugleaf <DrSmugleaf@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2023 Leon Friedrich <60421075+ElectroJr@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2023 Nemanja <98561806+EmoGarbage404@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2023 Psychpsyo <60073468+Psychpsyo@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2023 TemporalOroboros <TemporalOroboros@gmail.com>
+// SPDX-FileCopyrightText: 2023 metalgearsloth <comedian_vs_clown@hotmail.com>
+// SPDX-FileCopyrightText: 2024 0x6273 <0x40@keemail.me>
+// SPDX-FileCopyrightText: 2024 Jezithyr <jezithyr@gmail.com>
+// SPDX-FileCopyrightText: 2024 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2024 ShadowCommander <shadowjjt@gmail.com>
+// SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 username <113782077+whateverusername0@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 whateverusername0 <whateveremail>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Aviu00 <93730715+Aviu00@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 deltanedas <39013340+deltanedas@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 deltanedas <@deltanedas:kde.org>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
 using System.Linq;
 using System.Numerics;
 using Content.Shared.Body.Components;
@@ -15,6 +41,20 @@
 using Robust.Shared.Map;
 using Robust.Shared.Utility;
 
+// Shitmed Change
+using Content.Shared._Shitmed.Body.Events;
+using Content.Shared._Shitmed.Body.Part;
+using Content.Shared._Shitmed.Humanoid.Events;
+using Content.Shared._Shitmed.Medical.Surgery;
+using Content.Shared.Silicons.Borgs.Components;
+using Content.Shared.Containers.ItemSlots;
+using Content.Shared.Humanoid;
+using Content.Shared.Inventory.Events;
+using Content.Shared.Pulling.Events;
+using Content.Shared.Standing;
+using Robust.Shared.Network;
+using Robust.Shared.Timing;
+
 namespace Content.Shared.Body.Systems;
 
 public partial class SharedBodySystem
@@ -29,6 +69,8 @@ public partial class SharedBodySystem
     [Dependency] private readonly InventorySystem _inventory = default!;
     [Dependency] private readonly GibbingSystem _gibbingSystem = default!;
     [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
+    [Dependency] private readonly ItemSlotsSystem _slots = default!; // Shitmed Change
+    [Dependency] private readonly IGameTiming _gameTiming = default!; // Shitmed Change
 
     private const float GibletLaunchImpulse = 8;
     private const float GibletLaunchImpulseVariance = 3;
@@ -42,6 +84,23 @@ private void InitializeBody()
         SubscribeLocalEvent<BodyComponent, ComponentInit>(OnBodyInit);
         SubscribeLocalEvent<BodyComponent, MapInitEvent>(OnBodyMapInit);
         SubscribeLocalEvent<BodyComponent, CanDragEvent>(OnBodyCanDrag);
+        SubscribeLocalEvent<BodyComponent, StandAttemptEvent>(OnStandAttempt); // Shitmed Change
+        SubscribeLocalEvent<BodyComponent, ProfileLoadFinishedEvent>(OnProfileLoadFinished); // Shitmed change
+        SubscribeLocalEvent<BodyComponent, IsEquippingAttemptEvent>(OnBeingEquippedAttempt); // Shitmed Change
+    }
+
+    private void OnAttemptStopPulling(Entity<BodyComponent> ent, ref AttemptStopPullingEvent args) // Goobstation
+    {
+        if (args.User == null || !Exists(args.User.Value))
+            return;
+
+        if (args.User.Value != ent.Owner)
+            return;
+
+        if (ent.Comp.LegEntities.Count > 0 || ent.Comp.RequiredLegs == 0)
+            return;
+
+        args.Cancelled = true;
     }
 
     private void OnBodyInserted(Entity<BodyComponent> ent, ref EntInsertedIntoContainerMessage args)
@@ -103,6 +162,7 @@ private void OnBodyMapInit(Entity<BodyComponent> ent, ref MapInitEvent args)
         // Obviously can't run in Init to avoid double-spawns on save / load.
         var prototype = Prototypes.Index(ent.Comp.Prototype.Value);
         MapInitBody(ent, prototype);
+        EnsureComp<SurgeryTargetComponent>(ent); // Shitmed change
     }
 
     private void MapInitBody(EntityUid bodyEntity, BodyPrototype prototype)
@@ -119,7 +179,7 @@ private void MapInitBody(EntityUid bodyEntity, BodyPrototype prototype)
 
         // Setup the rest of the body entities.
         SetupOrgans((rootPartUid, rootPart), protoRoot.Organs);
-        MapInitParts(rootPartUid, prototype);
+        MapInitParts(rootPartUid, rootPart, prototype); // Shitmed Change
     }
 
     private void OnBodyCanDrag(Entity<BodyComponent> ent, ref CanDragEvent args)
@@ -130,7 +190,7 @@ private void OnBodyCanDrag(Entity<BodyComponent> ent, ref CanDragEvent args)
     /// <summary>
     /// Sets up all of the relevant body parts for a particular body entity and root part.
     /// </summary>
-    private void MapInitParts(EntityUid rootPartId, BodyPrototype prototype)
+    private void MapInitParts(EntityUid rootPartId, BodyPartComponent rootPart, BodyPrototype prototype) // Shitmed Change
     {
         // Start at the root part and traverse the body graph, setting up parts as we go.
         // Basic BFS pathfind.
@@ -167,7 +227,11 @@ private void MapInitParts(EntityUid rootPartId, BodyPrototype prototype)
                 cameFromEntities[connection] = childPart;
 
                 var childPartComponent = Comp<BodyPartComponent>(childPart);
-                var partSlot = CreatePartSlot(parentEntity, connection, childPartComponent.PartType, parentPartComponent);
+                TryCreatePartSlot(parentEntity, connection, childPartComponent.PartType, out var partSlot, parentPartComponent);
+                // Shitmed Change Start
+                childPartComponent.ParentSlot = partSlot;
+                Dirty(childPart, childPartComponent);
+                // Shitmed Change End
                 var cont = Containers.GetContainer(parentEntity, GetPartSlotContainerId(connection));
 
                 if (partSlot is null || !Containers.Insert(childPart, cont))
@@ -190,7 +254,7 @@ private void SetupOrgans(Entity<BodyPartComponent> ent, Dictionary<string, strin
     {
         foreach (var (organSlotId, organProto) in organs)
         {
-            var slot = CreateOrganSlot((ent, ent), organSlotId);
+            TryCreateOrganSlot(ent, organSlotId, out var slot); // Shitmed Change
             SpawnInContainerOrDrop(organProto, ent, GetOrganContainerId(organSlotId));
 
             if (slot is null)
@@ -234,6 +298,8 @@ private void SetupOrgans(Entity<BodyPartComponent> ent, Dictionary<string, strin
         if (id is null
             || !Resolve(id.Value, ref body, logMissing: false)
             || body.RootContainer.ContainedEntity is null
+            || body is null // Shitmed Change
+            || body.RootContainer == default // Shitmed Change
             || !Resolve(body.RootContainer.ContainedEntity.Value, ref rootPart))
         {
             yield break;
@@ -291,7 +357,10 @@ private void SetupOrgans(Entity<BodyPartComponent> ent, Dictionary<string, strin
         Vector2? splatDirection = null,
         float splatModifier = 1,
         Angle splatCone = default,
-        SoundSpecifier? gibSoundOverride = null)
+        SoundSpecifier? gibSoundOverride = null,
+        // Shitmed Change
+        GibType gib = GibType.Gib,
+        GibContentsOption contents = GibContentsOption.Drop)
     {
         var gibs = new HashSet<EntityUid>();
 
@@ -308,9 +377,9 @@ private void SetupOrgans(Entity<BodyPartComponent> ent, Dictionary<string, strin
         foreach (var part in parts)
         {
 
-            _gibbingSystem.TryGibEntityWithRef(bodyId, part.Id, GibType.Gib, GibContentsOption.Skip, ref gibs,
-                playAudio: false, launchGibs:true, launchDirection:splatDirection, launchImpulse: GibletLaunchImpulse * splatModifier,
-                launchImpulseVariance:GibletLaunchImpulseVariance, launchCone: splatCone);
+            _gibbingSystem.TryGibEntityWithRef(bodyId, part.Id, gib, contents, ref gibs, // Shitmed Change
+                playAudio: false, launchGibs: true, launchDirection: splatDirection, launchImpulse: GibletLaunchImpulse * splatModifier,
+                launchImpulseVariance: GibletLaunchImpulseVariance, launchCone: splatCone);
 
             if (!gibOrgans)
                 continue;
@@ -319,7 +388,7 @@ private void SetupOrgans(Entity<BodyPartComponent> ent, Dictionary<string, strin
             {
                 _gibbingSystem.TryGibEntityWithRef(bodyId, organ.Id, GibType.Drop, GibContentsOption.Skip,
                     ref gibs, playAudio: false, launchImpulse: GibletLaunchImpulse * splatModifier,
-                    launchImpulseVariance:GibletLaunchImpulseVariance, launchCone: splatCone);
+                    launchImpulseVariance: GibletLaunchImpulseVariance, launchCone: splatCone);
             }
         }
 
@@ -335,4 +404,132 @@ private void SetupOrgans(Entity<BodyPartComponent> ent, Dictionary<string, strin
         _audioSystem.PlayPredicted(gibSoundOverride, bodyTransform.Coordinates, null);
         return gibs;
     }
+
+    // Shitmed Change Start
+
+    public virtual HashSet<EntityUid> GibPart(
+        EntityUid partId,
+        BodyPartComponent? part = null,
+        bool launchGibs = true,
+        Vector2? splatDirection = null,
+        float splatModifier = 1,
+        Angle splatCone = default,
+        SoundSpecifier? gibSoundOverride = null)
+    {
+        var gibs = new HashSet<EntityUid>();
+
+        if (!Resolve(partId, ref part, logMissing: false))
+            return gibs;
+
+        if (part.Body is { } bodyEnt)
+        {
+            if (IsPartRoot(bodyEnt, partId, part: part) || !part.CanSever)
+                return gibs;
+
+            DropSlotContents((partId, part));
+            RemovePartChildren((partId, part), bodyEnt);
+            foreach (var organ in GetPartOrgans(partId, part))
+            {
+                _gibbingSystem.TryGibEntityWithRef(bodyEnt, organ.Id, GibType.Drop, GibContentsOption.Skip,
+                    ref gibs, playAudio: false, launchImpulse: GibletLaunchImpulse * splatModifier,
+                    launchImpulseVariance: GibletLaunchImpulseVariance, launchCone: splatCone);
+            }
+            var ev = new BodyPartDroppedEvent((partId, part));
+            RaiseLocalEvent(bodyEnt, ref ev);
+        }
+
+        _gibbingSystem.TryGibEntityWithRef(partId, partId, GibType.Gib, GibContentsOption.Drop, ref gibs,
+                playAudio: true, launchGibs: true, launchDirection: splatDirection, launchImpulse: GibletLaunchImpulse * splatModifier,
+                launchImpulseVariance: GibletLaunchImpulseVariance, launchCone: splatCone);
+
+
+        if (HasComp<InventoryComponent>(partId))
+        {
+            foreach (var item in _inventory.GetHandOrInventoryEntities(partId))
+            {
+                SharedTransform.AttachToGridOrMap(item);
+                gibs.Add(item);
+            }
+        }
+        _audioSystem.PlayPredicted(gibSoundOverride, Transform(partId).Coordinates, null);
+        return gibs;
+    }
+
+    public virtual bool BurnPart(EntityUid partId,
+        BodyPartComponent? part = null)
+    {
+        if (!Resolve(partId, ref part, logMissing: false))
+            return false;
+
+        if (part.Body is { } bodyEnt)
+        {
+            if (IsPartRoot(bodyEnt, partId, part: part))
+                return false;
+
+            var gibs = new HashSet<EntityUid>();
+            // Todo: Kill this in favor of husking.
+            DropSlotContents((partId, part));
+            RemovePartChildren((partId, part), bodyEnt);
+            foreach (var organ in GetPartOrgans(partId, part))
+                _gibbingSystem.TryGibEntityWithRef(bodyEnt, organ.Id, GibType.Drop, GibContentsOption.Skip,
+                    ref gibs, playAudio: false, launchImpulse: GibletLaunchImpulse, launchImpulseVariance: GibletLaunchImpulseVariance);
+
+            _gibbingSystem.TryGibEntityWithRef(partId, partId, GibType.Gib, GibContentsOption.Gib, ref gibs,
+                playAudio: false, launchGibs: true, launchImpulse: GibletLaunchImpulse, launchImpulseVariance: GibletLaunchImpulseVariance);
+
+            if (HasComp<InventoryComponent>(partId))
+                foreach (var item in _inventory.GetHandOrInventoryEntities(partId))
+                    SharedTransform.AttachToGridOrMap(item);
+
+            if (_net.IsServer) // Goob edit
+                QueueDel(partId);
+            return true;
+        }
+
+        return false;
+    }
+
+    private void OnProfileLoadFinished(EntityUid uid, BodyComponent component, ProfileLoadFinishedEvent args)
+    {
+        if (!HasComp<HumanoidAppearanceComponent>(uid)
+            || TerminatingOrDeleted(uid)
+            || !Initialized(uid)) // We do this last one for urists on test envs.
+            return;
+
+        foreach (var part in GetBodyChildren(uid, component))
+            EnsureComp<BodyPartAppearanceComponent>(part.Id);
+    }
+
+    private void OnStandAttempt(Entity<BodyComponent> ent, ref StandAttemptEvent args)
+    {
+        if (ent.Comp.LegEntities.Count < ent.Comp.RequiredLegs)
+            args.Cancel();
+    }
+
+    private void OnBeingEquippedAttempt(Entity<BodyComponent> ent, ref IsEquippingAttemptEvent args)
+    {
+        if (!TryComp(args.EquipTarget, out BodyComponent? targetBody)
+            || targetBody.Prototype == null
+            || HasComp<BorgChassisComponent>(args.EquipTarget))
+            return;
+
+        if (TryGetPartFromSlotContainer(args.Slot, out var bodyPart)
+            && bodyPart is not null)
+        {
+            var bodyPartString = bodyPart.Value.ToString().ToLower();
+            var prototype = Prototypes.Index(targetBody.Prototype.Value);
+            var hasPartConnection = prototype.Slots.Values.Any(slot =>
+                slot.Connections.Contains(bodyPartString));
+
+            if (hasPartConnection
+                && !GetBodyChildrenOfType(args.EquipTarget, bodyPart.Value).Any())
+            {
+                _popup.PopupClient(Loc.GetString("equip-part-missing-error",
+                    ("target", args.EquipTarget), ("part", bodyPartString)), args.Equipee, args.Equipee);
+                args.Cancel();
+            }
+        }
+    }
+
+    // Shitmed Change End
 }

+ 189 - 1
Content.Shared/Body/Systems/SharedBodySystem.Organs.cs

@@ -1,3 +1,85 @@
+// SPDX-FileCopyrightText: 2022 Jezithyr <Jezithyr@gmail.com>
+// SPDX-FileCopyrightText: 2023 Jezithyr <jezithyr@gmail.com>
+// SPDX-FileCopyrightText: 2023 TemporalOroboros <TemporalOroboros@gmail.com>
+// SPDX-FileCopyrightText: 2023 Visne <39844191+Visne@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 0x6273 <0x40@keemail.me>
+// SPDX-FileCopyrightText: 2024 Alzore <140123969+Blackern5000@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Brandon Hu <103440971+Brandon-Huu@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 CaasGit <87243814+CaasGit@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Chief-Engineer <119664036+Chief-Engineer@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Cojoke <83733158+Cojoke-dot@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 DrSmugleaf <DrSmugleaf@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Ed <96445749+TheShuEd@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Emisse <99158783+Emisse@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 EmoGarbage404 <retron404@gmail.com>
+// SPDX-FileCopyrightText: 2024 Eoin Mcloughlin <helloworld@eoinrul.es>
+// SPDX-FileCopyrightText: 2024 Errant <35878406+Errant-4@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Flareguy <78941145+Flareguy@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Hrosts <35345601+Hrosts@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 IProduceWidgets <107586145+IProduceWidgets@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Ian <ignaz.k@live.de>
+// SPDX-FileCopyrightText: 2024 Ilya246 <57039557+Ilya246@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Joel Zimmerman <JoelZimmerman@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 JustCone <141039037+JustCone14@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Killerqu00 <47712032+Killerqu00@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Ko4ergaPunk <62609550+Ko4ergaPunk@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Kukutis96513 <146854220+Kukutis96513@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Leon Friedrich <60421075+ElectroJr@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Lye <128915833+Lyroth001@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 MerrytheManokit <167581110+MerrytheManokit@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Mervill <mervills.email@gmail.com>
+// SPDX-FileCopyrightText: 2024 Mr. 27 <45323883+Dutch-VanDerLinde@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 MureixloI <132683811+MureixloI@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 NakataRin <45946146+NakataRin@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Nemanja <98561806+EmoGarbage404@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 OrangeMoronage9622 <whyteterry0092@gmail.com>
+// SPDX-FileCopyrightText: 2024 PJBot <pieterjan.briers+bot@gmail.com>
+// SPDX-FileCopyrightText: 2024 Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
+// SPDX-FileCopyrightText: 2024 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2024 Plykiya <58439124+Plykiya@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Preston Smith <92108534+thetolbean@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Psychpsyo <60073468+Psychpsyo@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Repo <47093363+Titian3@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 RiceMar1244 <138547931+RiceMar1244@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Simon <63975668+Simyon264@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Stalen <33173619+stalengd@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 TakoDragon <69509841+BackeTako@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Thomas <87614336+Aeshus@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 TsjipTsjip <19798667+TsjipTsjip@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Ubaser <134914314+UbaserB@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Unkn0wn_Gh0st <shadowstalkermll@gmail.com>
+// SPDX-FileCopyrightText: 2024 Vasilis <vasilis@pikachu.systems>
+// SPDX-FileCopyrightText: 2024 Vigers Ray <60344369+VigersRay@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 beck-thompson <107373427+beck-thompson@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 deathride58 <deathride58@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 dffdff2423 <dffdff2423@gmail.com>
+// SPDX-FileCopyrightText: 2024 eoineoineoin <github@eoinrul.es>
+// SPDX-FileCopyrightText: 2024 foboscheshir <156405958+foboscheshir@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 lzk <124214523+lzk228@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 metalgearsloth <comedian_vs_clown@hotmail.com>
+// SPDX-FileCopyrightText: 2024 nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 plykiya <plykiya@protonmail.com>
+// SPDX-FileCopyrightText: 2024 saintmuntzer <47153094+saintmuntzer@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 shamp <140359015+shampunj@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 slarticodefast <161409025+slarticodefast@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 strO0pwafel <153459934+strO0pwafel@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 stroopwafel <j.o.luijkx@student.tudelft.nl>
+// SPDX-FileCopyrightText: 2024 themias <89101928+themias@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 to4no_fix <156101927+chavonadelal@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 voidnull000 <18663194+voidnull000@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Aiden <aiden@djkraz.com>
+// SPDX-FileCopyrightText: 2025 deltanedas <39013340+deltanedas@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 deltanedas <@deltanedas:kde.org>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
 using System.Diagnostics.CodeAnalysis;
 using Content.Shared.Body.Components;
 using Content.Shared.Body.Events;
@@ -5,10 +87,32 @@
 using Content.Shared.Body.Part;
 using Robust.Shared.Containers;
 
+// Shitmed Change
+
+using Content.Shared.Damage;
+using Content.Shared._Shitmed.BodyEffects;
+using Content.Shared._Shitmed.Body.Organ;
+
 namespace Content.Shared.Body.Systems;
 
 public partial class SharedBodySystem
 {
+    // Shitmed Change Start
+
+    private void InitializeOrgans()
+    {
+        SubscribeLocalEvent<OrganComponent, MapInitEvent>(OnMapInit);
+        SubscribeLocalEvent<OrganComponent, OrganEnableChangedEvent>(OnOrganEnableChanged);
+    }
+
+    private void OnMapInit(Entity<OrganComponent> ent, ref MapInitEvent args)
+    {
+        if (ent.Comp.OnAdd is not null || ent.Comp.OnRemove is not null)
+            EnsureComp<OrganEffectComponent>(ent);
+    }
+
+    // Shitmed Change End
+
     private void AddOrgan(
         Entity<OrganComponent> organEnt,
         EntityUid bodyUid,
@@ -20,9 +124,13 @@ public partial class SharedBodySystem
 
         if (organEnt.Comp.Body is not null)
         {
+            // Shitmed Change Start
             var addedInBodyEv = new OrganAddedToBodyEvent(bodyUid, parentPartUid);
             RaiseLocalEvent(organEnt, ref addedInBodyEv);
+            var organEnabledEv = new OrganEnableChangedEvent(true);
+            RaiseLocalEvent(organEnt, ref organEnabledEv);
         }
+        // Shitmed Change End
 
         Dirty(organEnt, organEnt.Comp);
     }
@@ -34,10 +142,20 @@ private void RemoveOrgan(Entity<OrganComponent> organEnt, EntityUid parentPartUi
 
         if (organEnt.Comp.Body is { Valid: true } bodyUid)
         {
+            // Shitmed Change Start
+            organEnt.Comp.OriginalBody = organEnt.Comp.Body;
+            var organDisabledEv = new OrganEnableChangedEvent(false);
+            RaiseLocalEvent(organEnt, ref organDisabledEv);
+            // Shitmed Change End
             var removedInBodyEv = new OrganRemovedFromBodyEvent(bodyUid, parentPartUid);
             RaiseLocalEvent(organEnt, ref removedInBodyEv);
         }
 
+        if (parentPartUid is { Valid: true }
+            && TryComp(parentPartUid, out DamageableComponent? damageable)
+            && damageable.TotalDamage > 200)
+            TrySetOrganUsed(organEnt, true, organEnt.Comp);
+
         organEnt.Comp.Body = null;
         Dirty(organEnt, organEnt.Comp);
     }
@@ -51,6 +169,10 @@ private void RemoveOrgan(Entity<OrganComponent> organEnt, EntityUid parentPartUi
             return null;
 
         Containers.EnsureContainer<ContainerSlot>(parentEnt, GetOrganContainerId(slotId));
+        // Shitmed Change: Don't throw when a slot already exists
+        if (parentEnt.Comp.Organs.TryGetValue(slotId, out var existing))
+            return existing;
+
         var slot = new OrganSlot(slotId);
         parentEnt.Comp.Organs.Add(slotId, slot);
         return slot;
@@ -74,7 +196,14 @@ private void RemoveOrgan(Entity<OrganComponent> organEnt, EntityUid parentPartUi
 
         Containers.EnsureContainer<ContainerSlot>(parent.Value, GetOrganContainerId(slotId));
         slot = new OrganSlot(slotId);
-        return part.Organs.TryAdd(slotId, slot.Value);
+
+        // Shitmed Change Start
+        if (!part.Organs.ContainsKey(slotId)
+            && !part.Organs.TryAdd(slotId, slot.Value))
+            return false;
+
+        return true;
+        // Shitmed Change End
     }
 
     /// <summary>
@@ -209,4 +338,63 @@ public bool RemoveOrgan(EntityUid organId, OrganComponent? organ = null)
         comps = null;
         return false;
     }
+
+    // Shitmed Change Start
+
+    public bool TrySetOrganUsed(EntityUid organId, bool used, OrganComponent? organ = null)
+    {
+        if (!Resolve(organId, ref organ)
+            || organ.Used == used)
+            return false;
+
+        organ.Used = used;
+        Dirty(organId, organ);
+        return true;
+    }
+
+    private void OnOrganEnableChanged(Entity<OrganComponent> organEnt, ref OrganEnableChangedEvent args)
+    {
+        if (!organEnt.Comp.CanEnable && args.Enabled)
+            return;
+
+        organEnt.Comp.Enabled = args.Enabled;
+
+        if (args.Enabled)
+            EnableOrgan(organEnt);
+        else
+            DisableOrgan(organEnt);
+
+        if (organEnt.Comp.Body is { Valid: true } bodyEnt)
+            RaiseLocalEvent(organEnt, new OrganComponentsModifyEvent(bodyEnt, args.Enabled));
+
+        Dirty(organEnt, organEnt.Comp);
+    }
+
+    private void EnableOrgan(Entity<OrganComponent> organEnt)
+    {
+        if (!TryComp(organEnt.Comp.Body, out BodyComponent? body))
+            return;
+
+        // I hate having to hardcode these checks so much.
+        if (HasComp<EyesComponent>(organEnt))
+        {
+            var ev = new OrganEnabledEvent(organEnt);
+            RaiseLocalEvent(organEnt, ref ev);
+        }
+    }
+
+    private void DisableOrgan(Entity<OrganComponent> organEnt)
+    {
+        if (!TryComp(organEnt.Comp.Body, out BodyComponent? body))
+            return;
+
+        // I hate having to hardcode these checks so much.
+        if (HasComp<EyesComponent>(organEnt))
+        {
+            var ev = new OrganDisabledEvent(organEnt);
+            RaiseLocalEvent(organEnt, ref ev);
+        }
+    }
+
+    // Shitmed Change End
 }

+ 450 - 30
Content.Shared/Body/Systems/SharedBodySystem.Parts.cs

@@ -1,3 +1,85 @@
+// SPDX-FileCopyrightText: 2022 Jezithyr <Jezithyr@gmail.com>
+// SPDX-FileCopyrightText: 2023 Jezithyr <jezithyr@gmail.com>
+// SPDX-FileCopyrightText: 2023 TemporalOroboros <TemporalOroboros@gmail.com>
+// SPDX-FileCopyrightText: 2023 Visne <39844191+Visne@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 0x6273 <0x40@keemail.me>
+// SPDX-FileCopyrightText: 2024 Alzore <140123969+Blackern5000@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Brandon Hu <103440971+Brandon-Huu@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 CaasGit <87243814+CaasGit@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Chief-Engineer <119664036+Chief-Engineer@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Cojoke <83733158+Cojoke-dot@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 DrSmugleaf <DrSmugleaf@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Ed <96445749+TheShuEd@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Emisse <99158783+Emisse@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 EmoGarbage404 <retron404@gmail.com>
+// SPDX-FileCopyrightText: 2024 Eoin Mcloughlin <helloworld@eoinrul.es>
+// SPDX-FileCopyrightText: 2024 Errant <35878406+Errant-4@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Flareguy <78941145+Flareguy@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Hrosts <35345601+Hrosts@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 IProduceWidgets <107586145+IProduceWidgets@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Ian <ignaz.k@live.de>
+// SPDX-FileCopyrightText: 2024 Ilya246 <57039557+Ilya246@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Joel Zimmerman <JoelZimmerman@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 JustCone <141039037+JustCone14@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Killerqu00 <47712032+Killerqu00@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Ko4ergaPunk <62609550+Ko4ergaPunk@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Kukutis96513 <146854220+Kukutis96513@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Leon Friedrich <60421075+ElectroJr@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Lye <128915833+Lyroth001@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 MerrytheManokit <167581110+MerrytheManokit@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Mervill <mervills.email@gmail.com>
+// SPDX-FileCopyrightText: 2024 Mr. 27 <45323883+Dutch-VanDerLinde@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 MureixloI <132683811+MureixloI@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 NakataRin <45946146+NakataRin@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Nemanja <98561806+EmoGarbage404@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 OrangeMoronage9622 <whyteterry0092@gmail.com>
+// SPDX-FileCopyrightText: 2024 PJBot <pieterjan.briers+bot@gmail.com>
+// SPDX-FileCopyrightText: 2024 Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
+// SPDX-FileCopyrightText: 2024 Piras314 <p1r4s@proton.me>
+// SPDX-FileCopyrightText: 2024 Plykiya <58439124+Plykiya@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Preston Smith <92108534+thetolbean@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Psychpsyo <60073468+Psychpsyo@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Repo <47093363+Titian3@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 RiceMar1244 <138547931+RiceMar1244@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Simon <63975668+Simyon264@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Stalen <33173619+stalengd@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 TakoDragon <69509841+BackeTako@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Thomas <87614336+Aeshus@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 TsjipTsjip <19798667+TsjipTsjip@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Ubaser <134914314+UbaserB@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 Unkn0wn_Gh0st <shadowstalkermll@gmail.com>
+// SPDX-FileCopyrightText: 2024 Vasilis <vasilis@pikachu.systems>
+// SPDX-FileCopyrightText: 2024 Vigers Ray <60344369+VigersRay@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 beck-thompson <107373427+beck-thompson@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 deathride58 <deathride58@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 dffdff2423 <dffdff2423@gmail.com>
+// SPDX-FileCopyrightText: 2024 eoineoineoin <github@eoinrul.es>
+// SPDX-FileCopyrightText: 2024 foboscheshir <156405958+foboscheshir@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 lzk <124214523+lzk228@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 metalgearsloth <comedian_vs_clown@hotmail.com>
+// SPDX-FileCopyrightText: 2024 nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 plykiya <plykiya@protonmail.com>
+// SPDX-FileCopyrightText: 2024 saintmuntzer <47153094+saintmuntzer@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 shamp <140359015+shampunj@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 slarticodefast <161409025+slarticodefast@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 strO0pwafel <153459934+strO0pwafel@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 stroopwafel <j.o.luijkx@student.tudelft.nl>
+// SPDX-FileCopyrightText: 2024 themias <89101928+themias@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 to4no_fix <156101927+chavonadelal@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 voidnull000 <18663194+voidnull000@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Aiden <aiden@djkraz.com>
+// SPDX-FileCopyrightText: 2025 deltanedas <39013340+deltanedas@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 deltanedas <@deltanedas:kde.org>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using Content.Shared.Body.Components;
@@ -10,10 +92,21 @@
 using Robust.Shared.Containers;
 using Robust.Shared.Utility;
 
+// Shitmed Change Start
+using Content.Shared._Shitmed.Body.Events;
+using Content.Shared._Shitmed.Body.Part;
+using Content.Shared._Shitmed.BodyEffects;
+using Content.Shared.Humanoid;
+using Content.Shared.Inventory;
+using Content.Shared.Random;
+
 namespace Content.Shared.Body.Systems;
 
 public partial class SharedBodySystem
 {
+    [Dependency] private readonly RandomHelperSystem _randomHelper = default!; // Shitmed Change
+    [Dependency] private readonly InventorySystem _inventorySystem = default!; // Shitmed Change
+
     private void InitializeParts()
     {
         // TODO: This doesn't handle comp removal on child ents.
@@ -21,8 +114,192 @@ private void InitializeParts()
         // If you modify this also see the Body partial for root parts.
         SubscribeLocalEvent<BodyPartComponent, EntInsertedIntoContainerMessage>(OnBodyPartInserted);
         SubscribeLocalEvent<BodyPartComponent, EntRemovedFromContainerMessage>(OnBodyPartRemoved);
+
+        // Shitmed Change Start
+        SubscribeLocalEvent<BodyPartComponent, MapInitEvent>(OnMapInit);
+        SubscribeLocalEvent<BodyPartComponent, ComponentRemove>(OnBodyPartRemove);
+        SubscribeLocalEvent<BodyPartComponent, AmputateAttemptEvent>(OnAmputateAttempt);
+        SubscribeLocalEvent<BodyPartComponent, BodyPartEnableChangedEvent>(OnPartEnableChanged);
+    }
+
+    private void OnMapInit(Entity<BodyPartComponent> ent, ref MapInitEvent args)
+    {
+        if (ent.Comp.PartType == BodyPartType.Torso)
+        {
+            // For whatever reason this slot is initialized properly on the server, but not on the client.
+            // This seems to be an issue due to wiz-merge, on my old branch it was properly instantiating
+            // ItemInsertionSlot's container on both ends. It does show up properly on ItemSlotsComponent though.
+            _slots.AddItemSlot(ent, ent.Comp.ContainerName, ent.Comp.ItemInsertionSlot);
+            Dirty(ent, ent.Comp);
+        }
+
+        if (ent.Comp.OnAdd is not null || ent.Comp.OnRemove is not null)
+            EnsureComp<BodyPartEffectComponent>(ent);
+
+        foreach (var connection in ent.Comp.Children.Keys)
+        {
+            Containers.EnsureContainer<ContainerSlot>(ent, GetPartSlotContainerId(connection));
+        }
+
+        foreach (var organ in ent.Comp.Organs.Keys)
+        {
+            Containers.EnsureContainer<ContainerSlot>(ent, GetOrganContainerId(organ));
+        }
+    }
+
+    private void OnBodyPartRemove(Entity<BodyPartComponent> ent, ref ComponentRemove args)
+    {
+        if (ent.Comp.PartType == BodyPartType.Torso)
+            _slots.RemoveItemSlot(ent, ent.Comp.ItemInsertionSlot);
+    }
+
+    private void OnPartEnableChanged(Entity<BodyPartComponent> partEnt, ref BodyPartEnableChangedEvent args)
+    {
+        if (!partEnt.Comp.CanEnable && args.Enabled)
+            return;
+
+        partEnt.Comp.Enabled = args.Enabled;
+
+        if (args.Enabled)
+        {
+            EnablePart(partEnt);
+            if (partEnt.Comp.Body is { Valid: true } bodyEnt)
+                RaiseLocalEvent(partEnt, new BodyPartComponentsModifyEvent(bodyEnt, true));
+        }
+        else
+        {
+            DisablePart(partEnt);
+            if (partEnt.Comp.Body is { Valid: true } bodyEnt)
+                RaiseLocalEvent(partEnt, new BodyPartComponentsModifyEvent(bodyEnt, false));
+        }
+
+        Dirty(partEnt, partEnt.Comp);
+    }
+
+    private void EnablePart(Entity<BodyPartComponent> partEnt)
+    {
+        if (!TryComp(partEnt.Comp.Body, out BodyComponent? body))
+            return;
+
+        // I hate having to hardcode these checks so much.
+        if (partEnt.Comp.PartType == BodyPartType.Leg)
+            AddLeg(partEnt, (partEnt.Comp.Body.Value, body));
+
+        if (partEnt.Comp.PartType == BodyPartType.Arm)
+        {
+            var hand = GetBodyChildrenOfType(partEnt.Comp.Body.Value, BodyPartType.Hand, symmetry: partEnt.Comp.Symmetry).FirstOrDefault();
+            if (hand != default)
+            {
+                var ev = new BodyPartEnabledEvent(hand);
+                RaiseLocalEvent(partEnt.Comp.Body.Value, ref ev);
+            }
+        }
+
+        if (partEnt.Comp.PartType == BodyPartType.Hand)
+        {
+            var ev = new BodyPartEnabledEvent(partEnt);
+            RaiseLocalEvent(partEnt.Comp.Body.Value, ref ev);
+        }
+    }
+
+    /// <summary>
+    ///     Shitmed Change: This function handles dropping the items in an entity's slots if they lose all of a given part.
+    ///     Such as their hands, feet, head, etc.
+    /// </summary>
+    public void DropSlotContents(Entity<BodyPartComponent> partEnt)
+    {
+        if (partEnt.Comp.Body is not null
+            && TryComp<InventoryComponent>(partEnt.Comp.Body, out var inventory) // Prevent error for non-humanoids
+            && GetBodyPartCount(partEnt.Comp.Body.Value, partEnt.Comp.PartType) == 1
+            && TryGetPartSlotContainerName(partEnt.Comp.PartType, out var containerNames))
+        {
+            foreach (var containerName in containerNames)
+                _inventorySystem.DropSlotContents(partEnt.Comp.Body.Value, containerName, inventory);
+        }
+
+    }
+
+    private void DisablePart(Entity<BodyPartComponent> partEnt)
+    {
+        if (!TryComp(partEnt.Comp.Body, out BodyComponent? body))
+            return;
+
+        if (partEnt.Comp.PartType == BodyPartType.Leg)
+            RemoveLeg(partEnt, (partEnt.Comp.Body.Value, body));
+
+        if (partEnt.Comp.PartType == BodyPartType.Arm)
+        {
+            var hand = GetBodyChildrenOfType(partEnt.Comp.Body.Value, BodyPartType.Hand, symmetry: partEnt.Comp.Symmetry).FirstOrDefault();
+            if (hand != default)
+            {
+                var ev = new BodyPartDisabledEvent(hand);
+                RaiseLocalEvent(partEnt.Comp.Body.Value, ref ev);
+            }
+        }
+
+        if (partEnt.Comp.PartType == BodyPartType.Hand)
+        {
+            var ev = new BodyPartDisabledEvent(partEnt);
+            RaiseLocalEvent(partEnt.Comp.Body.Value, ref ev);
+        }
+    }
+
+    // TODO: Refactor this crap. I hate it so much.
+    private void RemovePartEffect(Entity<BodyPartComponent> partEnt, Entity<BodyComponent?> bodyEnt)
+    {
+        if (TerminatingOrDeleted(bodyEnt)
+            || !Resolve(bodyEnt, ref bodyEnt.Comp, logMissing: false))
+            return;
+
+        RemovePartChildren(partEnt, bodyEnt, bodyEnt.Comp);
+    }
+
+    protected void RemovePartChildren(Entity<BodyPartComponent> partEnt, EntityUid bodyEnt, BodyComponent? body = null)
+    {
+        if (!Resolve(bodyEnt, ref body, logMissing: false))
+            return;
+
+        if (partEnt.Comp.Children.Any())
+        {
+            foreach (var slotId in partEnt.Comp.Children.Keys)
+            {
+                if (Containers.TryGetContainer(partEnt, GetPartSlotContainerId(slotId), out var container) &&
+                    container is ContainerSlot slot &&
+                    slot.ContainedEntity is { } childEntity &&
+                    TryComp(childEntity, out BodyPartComponent? childPart))
+                {
+                    var ev = new BodyPartEnableChangedEvent(false);
+                    RaiseLocalEvent(childEntity, ref ev);
+                    DropPart((childEntity, childPart));
+                }
+            }
+
+            Dirty(bodyEnt, body);
+        }
+    }
+
+    protected virtual void DropPart(Entity<BodyPartComponent> partEnt)
+    {
+        DropSlotContents(partEnt);
+        // I don't know if this can cause issues, since any part that's being detached HAS to have a Body.
+        // though I really just want the compiler to shut the fuck up.
+        var body = partEnt.Comp.Body.GetValueOrDefault();
+        if (TryComp(partEnt, out TransformComponent? transform) && _gameTiming.IsFirstTimePredicted)
+        {
+            var enableEvent = new BodyPartEnableChangedEvent(false);
+            RaiseLocalEvent(partEnt, ref enableEvent);
+            var droppedEvent = new BodyPartDroppedEvent(partEnt);
+            RaiseLocalEvent(body, ref droppedEvent);
+            SharedTransform.AttachToGridOrMap(partEnt, transform);
+            _randomHelper.RandomOffset(partEnt, 0.5f);
+        }
+
     }
 
+    private void OnAmputateAttempt(Entity<BodyPartComponent> partEnt, ref AmputateAttemptEvent args) =>
+        DropPart(partEnt);
+
+    // Shitmed Change End
     private void OnBodyPartInserted(Entity<BodyPartComponent> ent, ref EntInsertedIntoContainerMessage args)
     {
         // Body part inserted into another body part.
@@ -32,14 +309,17 @@ private void OnBodyPartInserted(Entity<BodyPartComponent> ent, ref EntInsertedIn
         if (ent.Comp.Body is null)
             return;
 
-        if (TryComp(insertedUid, out BodyPartComponent? part))
+        if (TryComp(insertedUid, out BodyPartComponent? part) && slotId.Contains(PartSlotContainerIdPrefix + GetSlotFromBodyPart(part))) // Shitmed Change
         {
             AddPart(ent.Comp.Body.Value, (insertedUid, part), slotId);
             RecursiveBodyUpdate((insertedUid, part), ent.Comp.Body.Value);
+            CheckBodyPart((insertedUid, part), GetTargetBodyPart(part), false); // Shitmed Change
         }
 
-        if (TryComp(insertedUid, out OrganComponent? organ))
+        if (TryComp(insertedUid, out OrganComponent? organ) && slotId.Contains(OrganSlotContainerIdPrefix + organ.SlotId)) // Shitmed Change
+        {
             AddOrgan((insertedUid, organ), ent.Comp.Body.Value, ent);
+        }
     }
 
     private void OnBodyPartRemoved(Entity<BodyPartComponent> ent, ref EntRemovedFromContainerMessage args)
@@ -48,17 +328,32 @@ private void OnBodyPartRemoved(Entity<BodyPartComponent> ent, ref EntRemovedFrom
         var removedUid = args.Entity;
         var slotId = args.Container.ID;
 
-        DebugTools.Assert(!TryComp(removedUid, out BodyPartComponent? b) || b.Body == ent.Comp.Body);
-        DebugTools.Assert(!TryComp(removedUid, out OrganComponent? o) || o.Body == ent.Comp.Body);
-
-        if (TryComp(removedUid, out BodyPartComponent? part) && part.Body is not null)
+        // Shitmed Change Start
+        if (TryComp(removedUid, out BodyPartComponent? part))
         {
-            RemovePart(part.Body.Value, (removedUid, part), slotId);
-            RecursiveBodyUpdate((removedUid, part), null);
+            if (!slotId.Contains(PartSlotContainerIdPrefix + GetSlotFromBodyPart(part)))
+                return;
+
+            DebugTools.Assert(part.Body == ent.Comp.Body);
+
+            if (part.Body is not null)
+            {
+                CheckBodyPart((removedUid, part), GetTargetBodyPart(part), true);
+                RemovePart(part.Body.Value, (removedUid, part), slotId);
+                RecursiveBodyUpdate((removedUid, part), null);
+            }
         }
 
         if (TryComp(removedUid, out OrganComponent? organ))
+        {
+            if (!slotId.Contains(OrganSlotContainerIdPrefix + organ.SlotId))
+                return;
+
+            DebugTools.Assert(organ.Body == ent.Comp.Body);
+
             RemoveOrgan((removedUid, organ), ent);
+        }
+        // Shitmed Change End
     }
 
     private void RecursiveBodyUpdate(Entity<BodyPartComponent> ent, EntityUid? bodyUid)
@@ -93,6 +388,8 @@ private void RecursiveBodyUpdate(Entity<BodyPartComponent> ent, EntityUid? bodyU
             }
         }
 
+        // The code for RemovePartEffect() should live here, because it literally is the point of this recursive function.
+        // But the debug asserts at the top plus existing tests need refactoring for this. So we'll be lazy.
         foreach (var slotId in ent.Comp.Children.Keys)
         {
             if (!Containers.TryGetContainer(ent, GetPartSlotContainerId(slotId), out var container))
@@ -114,6 +411,9 @@ private void RecursiveBodyUpdate(Entity<BodyPartComponent> ent, EntityUid? bodyU
         Dirty(partEnt, partEnt.Comp);
         partEnt.Comp.Body = bodyEnt;
 
+        if (partEnt.Comp.Enabled && partEnt.Comp.Body is { Valid: true } body) // Shitmed Change
+            RaiseLocalEvent(partEnt, new BodyPartComponentsModifyEvent(body, true));
+
         var ev = new BodyPartAddedEvent(slotId, partEnt);
         RaiseLocalEvent(bodyEnt, ref ev);
 
@@ -127,12 +427,18 @@ private void RecursiveBodyUpdate(Entity<BodyPartComponent> ent, EntityUid? bodyU
     {
         Resolve(bodyEnt, ref bodyEnt.Comp, logMissing: false);
         Dirty(partEnt, partEnt.Comp);
-        partEnt.Comp.Body = null;
+
+        // Shitmed Change Start
+        if (partEnt.Comp.Body is { Valid: true } body)
+            RaiseLocalEvent(partEnt, new BodyPartComponentsModifyEvent(body, false));
+        partEnt.Comp.ParentSlot = null;
+        // Shitmed Change End
 
         var ev = new BodyPartRemovedEvent(slotId, partEnt);
         RaiseLocalEvent(bodyEnt, ref ev);
 
         RemoveLeg(partEnt, bodyEnt);
+        RemovePartEffect(partEnt, bodyEnt); // Shitmed Change
         PartRemoveDamage(bodyEnt, partEnt);
     }
 
@@ -159,28 +465,13 @@ private void RemoveLeg(Entity<BodyPartComponent> legEnt, Entity<BodyComponent?>
             bodyEnt.Comp.LegEntities.Remove(legEnt);
             UpdateMovementSpeed(bodyEnt);
             Dirty(bodyEnt, bodyEnt.Comp);
-
-            if (!bodyEnt.Comp.LegEntities.Any())
-            {
-                Standing.Down(bodyEnt);
-            }
+            Standing.Down(bodyEnt); // Shitmed Change
         }
     }
 
-    private void PartRemoveDamage(Entity<BodyComponent?> bodyEnt, Entity<BodyPartComponent> partEnt)
+    // Shitmed Change: made virtual, bleeding damage is done on server
+    protected virtual void PartRemoveDamage(Entity<BodyComponent?> bodyEnt, Entity<BodyPartComponent> partEnt)
     {
-        if (!Resolve(bodyEnt, ref bodyEnt.Comp, logMissing: false))
-            return;
-
-        if (!_timing.ApplyingState
-            && partEnt.Comp.IsVital
-            && !GetBodyChildrenOfType(bodyEnt, partEnt.Comp.PartType, bodyEnt.Comp).Any()
-        )
-        {
-            // TODO BODY SYSTEM KILL : remove this when wounding and required parts are implemented properly
-            var damage = new DamageSpecifier(Prototypes.Index<DamageTypePrototype>("Bloodloss"), 300);
-            Damageable.TryChangeDamage(bodyEnt, damage);
-        }
     }
 
     /// <summary>
@@ -260,6 +551,10 @@ private void PartRemoveDamage(Entity<BodyComponent?> bodyEnt, Entity<BodyPartCom
             return null;
 
         Containers.EnsureContainer<ContainerSlot>(partUid, GetPartSlotContainerId(slotId));
+        // Shitmed Change: Don't throw if the slot already exists
+        if (part.Children.TryGetValue(slotId, out var existing))
+            return existing;
+
         var partSlot = new BodyPartSlot(slotId, partType);
         part.Children.Add(slotId, partSlot);
         Dirty(partUid, part);
@@ -288,7 +583,8 @@ private void PartRemoveDamage(Entity<BodyComponent?> bodyEnt, Entity<BodyPartCom
         Containers.EnsureContainer<ContainerSlot>(partId.Value, GetPartSlotContainerId(slotId));
         slot = new BodyPartSlot(slotId, partType);
 
-        if (!part.Children.TryAdd(slotId, slot.Value))
+        if (!part.Children.ContainsKey(slotId) // Shitmed Change
+            && !part.Children.TryAdd(slotId, slot.Value))
             return false;
 
         Dirty(partId.Value, part);
@@ -389,6 +685,18 @@ private void PartRemoveDamage(Entity<BodyComponent?> bodyEnt, Entity<BodyPartCom
             && Containers.CanInsert(partId, container);
     }
 
+    /// <summary>
+    /// Shitmed Change: Returns true if this parentId supports attaching a new part to the specified slot.
+    /// </summary>
+    public bool CanAttachToSlot(
+        EntityUid parentId,
+        string slotId,
+        BodyPartComponent? parentPart = null)
+    {
+        return Resolve(parentId, ref parentPart, logMissing: false)
+            && parentPart.Children.ContainsKey(slotId);
+    }
+
     public bool AttachPartToRoot(
         EntityUid bodyId,
         EntityUid partId,
@@ -444,6 +752,14 @@ private void PartRemoveDamage(Entity<BodyComponent?> bodyEnt, Entity<BodyPartCom
             return false;
         }
 
+        part.ParentSlot = slot;
+
+        if (TryComp(parentPart.Body, out HumanoidAppearanceComponent? bodyAppearance)
+            && !HasComp<BodyPartAppearanceComponent>(partId)
+            && !TerminatingOrDeleted(parentPartId)
+            && !TerminatingOrDeleted(partId)) // Saw some exceptions involving these due to the spawn menu.
+            EnsureComp<BodyPartAppearanceComponent>(partId);
+
         return Containers.Insert(partId, container);
     }
 
@@ -656,11 +972,13 @@ public IEnumerable<BaseContainer> GetPartContainers(EntityUid id, BodyPartCompon
     public IEnumerable<(EntityUid Id, BodyPartComponent Component)> GetBodyChildrenOfType(
         EntityUid bodyId,
         BodyPartType type,
-        BodyComponent? body = null)
+        BodyComponent? body = null,
+        // Shitmed Change
+        BodyPartSymmetry? symmetry = null)
     {
         foreach (var part in GetBodyChildren(bodyId, body))
         {
-            if (part.Component.PartType == type)
+            if (part.Component.PartType == type && (symmetry == null || part.Component.Symmetry == symmetry)) // Shitmed Change
                 yield return part;
         }
     }
@@ -722,6 +1040,108 @@ public IEnumerable<BaseContainer> GetPartContainers(EntityUid id, BodyPartCompon
         return false;
     }
 
+    // Shitmed Change Start
+    /// <summary>
+    ///     Tries to get a list of ValueTuples of EntityUid and OrganComponent on each organ
+    ///     in the given part.
+    /// </summary>
+    /// <param name="uid">The part entity id to check on.</param>
+    /// <param name="type">The type of component to check for.</param>
+    /// <param name="part">The part to check for organs on.</param>
+    /// <param name="organs">The organs found on the body part.</param>
+    /// <returns>Whether any were found.</returns>
+    /// <remarks>
+    ///     This method is somewhat of a copout to the fact that we can't use reflection to generically
+    ///     get the type of component on runtime due to sandboxing. So we simply do a HasComp check for each organ.
+    /// </remarks>
+    public bool TryGetBodyPartOrgans(
+        EntityUid uid,
+        Type type,
+        [NotNullWhen(true)] out List<(EntityUid Id, OrganComponent Organ)>? organs,
+        BodyPartComponent? part = null)
+    {
+        if (!Resolve(uid, ref part))
+        {
+            organs = null;
+            return false;
+        }
+
+        var list = new List<(EntityUid Id, OrganComponent Organ)>();
+
+        foreach (var organ in GetPartOrgans(uid, part))
+        {
+            if (HasComp(organ.Id, type))
+                list.Add((organ.Id, organ.Component));
+        }
+
+        if (list.Count != 0)
+        {
+            organs = list;
+            return true;
+        }
+
+        organs = null;
+        return false;
+    }
+
+    private bool TryGetPartSlotContainerName(BodyPartType partType, out HashSet<string> containerNames)
+    {
+        containerNames = partType switch
+        {
+            BodyPartType.Hand => new() { "gloves" },
+            BodyPartType.Foot => new() { "shoes" },
+            BodyPartType.Head => new() { "eyes", "ears", "head", "mask" },
+            _ => new()
+        };
+        return containerNames.Count > 0;
+    }
+
+    private bool TryGetPartFromSlotContainer(string slot, out BodyPartType? partType)
+    {
+        partType = slot switch
+        {
+            "gloves" => BodyPartType.Hand,
+            "shoes" => BodyPartType.Foot,
+            "eyes" or "ears" or "head" or "mask" => BodyPartType.Head,
+            _ => null
+        };
+        return partType is not null;
+    }
+
+    public int GetBodyPartCount(EntityUid bodyId, BodyPartType partType, BodyComponent? body = null)
+    {
+        if (!Resolve(bodyId, ref body, logMissing: false))
+            return 0;
+
+        int count = 0;
+        foreach (var part in GetBodyChildren(bodyId, body))
+        {
+            if (part.Component.PartType == partType)
+                count++;
+        }
+        return count;
+    }
+
+    public string GetSlotFromBodyPart(BodyPartComponent? part)
+    {
+        var slotName = "";
+
+        if (part is null)
+            return slotName;
+
+        if (part.SlotId != "")
+            slotName = part.SlotId;
+        else
+            slotName = part.PartType.ToString().ToLower();
+
+        if (part.Symmetry != BodyPartSymmetry.None)
+            return $"{part.Symmetry.ToString().ToLower()} {slotName}";
+        else
+            return slotName;
+    }
+
+    // Shitmed Change End
+
     /// <summary>
     /// Gets the parent body part and all immediate child body parts for the partId.
     /// </summary>

+ 24 - 1
Content.Shared/Body/Systems/SharedBodySystem.cs

@@ -1,3 +1,19 @@
+// SPDX-FileCopyrightText: 2022 DrSmugleaf <DrSmugleaf@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2022 Jezithyr <Jezithyr@gmail.com>
+// SPDX-FileCopyrightText: 2022 Nemanja <98561806+EmoGarbage404@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2022 metalgearsloth <metalgearsloth@gmail.com>
+// SPDX-FileCopyrightText: 2023 Jezithyr <jezithyr@gmail.com>
+// SPDX-FileCopyrightText: 2023 Psychpsyo <60073468+Psychpsyo@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2023 Visne <39844191+Visne@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2023 metalgearsloth <comedian_vs_clown@hotmail.com>
+// SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2024 metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Aviu00 <93730715+Aviu00@users.noreply.github.com>
+// SPDX-FileCopyrightText: 2025 Piras314 <p1r4s@proton.me>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
 using Content.Shared.Damage;
 using Content.Shared.Movement.Systems;
 using Content.Shared.Standing;
@@ -28,7 +44,7 @@ public abstract partial class SharedBodySystem : EntitySystem
     /// </summary>
     public const string OrganSlotContainerIdPrefix = "body_organ_slot_";
 
-    [Dependency] private   readonly IGameTiming _timing = default!;
+    [Dependency] private readonly IGameTiming _timing = default!;
     [Dependency] protected readonly IPrototypeManager Prototypes = default!;
     [Dependency] protected readonly DamageableSystem Damageable = default!;
     [Dependency] protected readonly MovementSpeedModifierSystem Movement = default!;
@@ -42,6 +58,13 @@ public override void Initialize()
 
         InitializeBody();
         InitializeParts();
+        InitializeOrgans();
+        // Shitmed Change Start
+        // To try and mitigate the server load due to integrity checks, we set up a Job Queue.
+        InitializeIntegrityQueue();
+        InitializePartAppearances();
+        InitializeRelay();
+        // Shitmed Change End
     }
 
     /// <summary>

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů