Skip to content

Commit

Permalink
[Port] Surgery Fixes: Popup Walker (#923)
Browse files Browse the repository at this point in the history
* Megasquached Surgery Updates

Megasquached Surgery Updates

Quick patch to see if tests shut up

Please do not cherrypick this one yet.

Refactored body parts to use damageablecomponent

newmed health analyzer real, also refactors and some bugfixes

* added solidus's comments, pending removal of namespace thingies for this repo, also fixed some healing thingies

* fixes?

* fix gib torso

* fix prototypes

* oops

* Shitmed Update 1 (#1240)

First in a series of PRs to introduce bugfixes and updates to Shitmed,
this will generally feature PRs from Goobstation or Backmen as well
since they are actively helping me maintain the code.

Usual Shoutouts:
Deltanedas: Goob-Station/Goob-Station#882

---

:cl: Mocho, Deltanedas
- add: You can now perform surgery as a monke. Rejoice.
- add: You can perform surgery on a lot of animals now, I missed a lot
of them so just ask if you want any particular critter to get it.
- tweak: Entities now perish after 60 seconds of losing their heart
and/or brain.
- fix: Entities properly take asphyxiation damage after losing their
brain.
- fix: Torsos being gibbable, which would break surgery or just about
anything.
- fix: Items not being removed from their respective slots if the parts
were gibbed rather than dropped.
- fix: Animal organs not being usable properly in surgeries
- fix: Cyborg limbs are now usable as pseudo-peg arm/legs.

---------

Signed-off-by: gluesniffler <[email protected]>
Co-authored-by: FoxxoTrystan <[email protected]>
Co-authored-by: goet <[email protected]>
Co-authored-by: Saphire Lattice <[email protected]>

* Shitmed Surgery Popups (#1241)

Adds popups for surgery steps in Shitmed that every player within PVS
range can see. This allows other players to see if the correct procedure
is being performed. This PR also includes locale text for the new
procedures and steps in #1240.

**Remove Brain / Insert Brain**

https://github.com/user-attachments/assets/ac20afa1-df74-48ab-b1d5-2e9a273dfba2

<details><summary>See more</summary>

**Amputate Right Arm**

https://github.com/user-attachments/assets/17f78683-6d3b-44ee-aea3-bb6987844fdc

**Attach Right Arm**

https://github.com/user-attachments/assets/584d4da2-d8b0-4c82-a323-26636e7fa4b8

</details>

:cl: Skubman
- add: Surgery step descriptions (like making an incision,
removing/attaching limbs and organs) are now shown as popups to everyone
in range upon the start of the step. This makes it clear which surgical
procedure is being done and to which body part. No more stealthy
brain-stealing in front of everyone!

* fix the things + locale

---------

Signed-off-by: gluesniffler <[email protected]>
Co-authored-by: gluesniffler <[email protected]>
Co-authored-by: Zack Backmen <[email protected]>
Co-authored-by: gluesniffler <[email protected]>
Co-authored-by: FoxxoTrystan <[email protected]>
Co-authored-by: goet <[email protected]>
Co-authored-by: Saphire Lattice <[email protected]>
Co-authored-by: Skubman <[email protected]>
  • Loading branch information
8 people authored Nov 19, 2024
1 parent c26f254 commit 0327a35
Show file tree
Hide file tree
Showing 82 changed files with 857 additions and 112 deletions.
40 changes: 40 additions & 0 deletions Content.Server/Backmen/Body/Systems/HeartSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Content.Shared.Body.Components;
using Content.Shared.Body.Systems;
using Content.Shared.Body.Events;
using Content.Shared.Body.Organ;
using Content.Server.Backmen.DelayedDeath;
using Content.Server.Body.Components;
using Content.Shared.Backmen.Surgery.Body.Organs;

namespace Content.Server.Backmen.Body.Systems;

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
}
16 changes: 16 additions & 0 deletions Content.Server/Backmen/DelayedDeath/DelayedDeathComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Content.Server.Backmen.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;
}
32 changes: 32 additions & 0 deletions Content.Server/Backmen/DelayedDeath/DelayedDeathSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Content.Shared.Body.Organ;
using Content.Shared.Body.Events;
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
using Content.Shared.Mobs.Systems;
using Robust.Shared.Timing;
using Robust.Shared.Prototypes;

namespace Content.Server.Backmen.DelayedDeath;

public partial class DelayedDeathSystem : EntitySystem
{
[Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly IPrototypeManager _prototypes = default!;
public override void Update(float frameTime)
{
base.Update(frameTime);

using var query = EntityQueryEnumerator<DelayedDeathComponent>();
while (query.MoveNext(out var ent, out var component))
{
component.DeathTimer += frameTime;

if (component.DeathTimer >= component.DeathTime && !_mobState.IsDead(ent))
{
var damage = new DamageSpecifier(_prototypes.Index<DamageTypePrototype>("Bloodloss"), 150);
_damageable.TryChangeDamage(ent, damage, partMultiplier: 0f);
}
}
}
}
39 changes: 15 additions & 24 deletions Content.Server/Backmen/Surgery/SurgerySystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using Content.Shared.Backmen.Surgery.Effects.Step;
using Content.Shared.Backmen.Surgery.Tools;
using Content.Shared.Bed.Sleep;
using Content.Shared.Medical.Surgery;

namespace Content.Server.Backmen.Surgery;

Expand All @@ -43,8 +44,10 @@ public override void Initialize()

SubscribeLocalEvent<SurgeryToolComponent, AfterInteractEvent>(OnToolAfterInteract);
SubscribeLocalEvent<SurgeryTargetComponent, SurgeryStepDamageEvent>(OnSurgeryStepDamage);
SubscribeLocalEvent<SurgeryDamageChangeEffectComponent, SurgeryStepEvent>(OnSurgeryDamageChange);
SubscribeLocalEvent<SurgerySpecialDamageChangeEffectComponent, SurgeryStepEvent>(OnSurgerySpecialDamageChange);
// 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);
SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnPrototypesReloaded);
Expand Down Expand Up @@ -127,37 +130,25 @@ private void OnToolAfterInteract(Entity<SurgeryToolComponent> ent, ref AfterInte
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 SurgeryStepEvent args)
private void OnSurgerySpecialDamageChange(Entity<SurgerySpecialDamageChangeEffectComponent> ent, ref SurgeryStepDamageChangeEvent args)
{
// This unintentionally punishes the user if they have an organ in another hand that is already used.
// Imo surgery shouldn't let you automatically pick tools on both hands anyway, it should only use the one you've got in your selected hand.
if (ent.Comp.IsConsumable
&& args.Tools.Where(tool => TryComp<OrganComponent>(tool, out var organComp)
&& !_body.TrySetOrganUsed(tool, true, organComp)).Any())
return;
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 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 SurgeryStepEvent args)
{
if (ent.Comp.IsConsumable
&& args.Tools.Where(tool => TryComp<OrganComponent>(tool, out var organComp)
&& !_body.TrySetOrganUsed(tool, true, organComp)).Any())
return;

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(args.Body, 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))
Expand Down
6 changes: 3 additions & 3 deletions Content.Server/Body/Systems/BodySystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,8 @@ protected override void AddPart(
var layer = partEnt.Comp.ToHumanoidLayers();
if (layer != null)
{
var layers = HumanoidVisualLayersExtension.Sublayers(layer.Value);
_humanoidSystem.SetLayersVisibility(
bodyEnt, layers, visible: true, permanent: true, humanoid);
bodyEnt, new[] { layer.Value }, visible: true, permanent: true, humanoid);
}
}
}
Expand Down Expand Up @@ -172,7 +171,8 @@ public override HashSet<EntityUid> GibPart(
var ev = new BeingGibbedEvent(gibs);
RaiseLocalEvent(partId, ref ev);

QueueDel(partId);
if (gibs.Any())
QueueDel(partId);

return gibs;
}
Expand Down
11 changes: 9 additions & 2 deletions Content.Server/Body/Systems/BrainSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
using Content.Server.Ghost.Components;
using Content.Shared.Backmen.Surgery.Body;
using Content.Shared.Body.Components;
using Content.Shared.Body.Systems;
using Content.Shared.Body.Events;
using Content.Shared.Body.Organ;
using Content.Server.Backmen.DelayedDeath;
using Content.Shared.Backmen.Surgery.Body.Organs;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Pointing;
Expand All @@ -13,8 +16,8 @@ namespace Content.Server.Body.Systems
public sealed class BrainSystem : EntitySystem
{
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
[Dependency] private readonly SharedBodySystem _bodySystem = default!;

// Shitmed-Start
public override void Initialize()
{
base.Initialize();
Expand All @@ -31,17 +34,21 @@ private void HandleRemoval(EntityUid uid, BrainComponent _, ref OrganRemovedFrom

// Prevents revival, should kill the user within a given timespan too.
EnsureComp<DebrainedComponent>(args.OldBody);
EnsureComp<DelayedDeathComponent>(args.OldBody);
HandleMind(uid, args.OldBody);
}

private void HandleAddition(EntityUid uid, BrainComponent _, ref OrganAddedToBodyEvent args)
{
if (TerminatingOrDeleted(uid) || TerminatingOrDeleted(args.Body))
return;

RemComp<DebrainedComponent>(args.Body);
if (_bodySystem.TryGetBodyOrganEntityComps<HeartComponent>(args.Body, out var _))
RemComp<DelayedDeathComponent>(args.Body);
HandleMind(args.Body, uid);
}
// Shitmed-End

private void HandleMind(EntityUid newEntity, EntityUid oldEntity)
{
if (TerminatingOrDeleted(newEntity) || TerminatingOrDeleted(oldEntity))
Expand Down
4 changes: 2 additions & 2 deletions Content.Server/Body/Systems/RespiratorSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public override void Update(float frameTime)

UpdateSaturation(uid, -(float) respirator.UpdateInterval.TotalSeconds, respirator);

if (!_mobState.IsIncapacitated(uid) || HasComp<DebrainedComponent>(uid)) // Shitmed: cannot breathe in crit or when no brain.
if (!_mobState.IsIncapacitated(uid) && !HasComp<DebrainedComponent>(uid)) // Shitmed: cannot breathe in crit or when no brain.
{
switch (respirator.Status)
{
Expand Down Expand Up @@ -310,7 +310,7 @@ private void TakeSuffocationDamage(Entity<RespiratorComponent> ent)
RaiseLocalEvent(ent, new MoodEffectEvent("Suffocating")); // backmen: mood
}

_damageableSys.TryChangeDamage(ent, ent.Comp.Damage, interruptsDoAfters: false);
_damageableSys.TryChangeDamage(ent, HasComp<DebrainedComponent>(ent) ? ent.Comp.Damage * 4.5f : ent.Comp.Damage, interruptsDoAfters: false);
}

private void StopSuffocation(Entity<RespiratorComponent> ent)
Expand Down
1 change: 0 additions & 1 deletion Content.Shared/Backmen/Surgery/Body/DebrainedComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,3 @@ namespace Content.Shared.Backmen.Surgery.Body;

[RegisterComponent]
public sealed partial class DebrainedComponent : Component;
// TODO: Add a timer to kill the entity if they don't get a new brain in time.
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,18 @@ private void InitializePartAppearances()
private void OnPartAppearanceStartup(EntityUid uid, BodyPartAppearanceComponent component, ComponentStartup args)
{
if (!TryComp(uid, out BodyPartComponent? part)
|| part.OriginalBody == null
|| TerminatingOrDeleted(part.OriginalBody.Value)
|| !TryComp(part.OriginalBody.Value, out HumanoidAppearanceComponent? bodyAppearance)
|| part.ToHumanoidLayers() is not { } relevantLayer)
return;

if (part.OriginalBody == null
|| TerminatingOrDeleted(part.OriginalBody.Value)
|| !TryComp(part.OriginalBody.Value, out HumanoidAppearanceComponent? bodyAppearance))
{
//component.ID = part.BaseLayerId;
component.Type = relevantLayer;
return;
}

var customLayers = bodyAppearance.CustomBaseLayers;
var spriteLayers = bodyAppearance.BaseLayers;
component.Type = relevantLayer;
Expand Down Expand Up @@ -159,7 +165,7 @@ protected void UpdateAppearance(EntityUid target,
_humanoid.SetBaseLayerColor(target, component.Type, component.Color, true, bodyAppearance);

_humanoid.SetLayerVisibility(target, component.Type, true, true, bodyAppearance);

foreach (var (visualLayer, markingList) in component.Markings)
{
_humanoid.SetLayerVisibility(target, visualLayer, true, true, bodyAppearance);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@ namespace Content.Shared.Medical.Surgery.Conditions;
[RegisterComponent, NetworkedComponent]
public sealed partial class SurgeryPartRemovedConditionComponent : Component
{
/// <summary>
/// Requires that the parent part can attach a new part to this slot.
/// </summary>
[DataField(required: true)]
public string Connection = string.Empty;

[DataField]
public BodyPartType Part;

[DataField]
public BodyPartSymmetry? Symmetry;
}
}
24 changes: 23 additions & 1 deletion Content.Shared/Backmen/Surgery/SharedSurgerySystem.Steps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
using Content.Shared.DoAfter;
using Content.Shared.IdentityManagement;
using Content.Shared.Inventory;
using Content.Shared.Item;
using Content.Shared.Popups;
Expand All @@ -22,6 +23,7 @@
using Content.Shared.Backmen.Surgery.Steps.Parts;
using Content.Shared.Backmen.Surgery.Tools;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Medical.Surgery;

namespace Content.Shared.Backmen.Surgery;

Expand Down Expand Up @@ -420,6 +422,12 @@ private void OnAddOrganStep(Entity<SurgeryAddOrganStepComponent> ent, ref Surger
&& _body.InsertOrgan(args.Part, tool, insertedOrgan.SlotId, partComp, insertedOrgan))
{
EnsureComp<OrganReattachedComponent>(tool);
if (_body.TrySetOrganUsed(tool, true, insertedOrgan)
&& insertedOrgan.OriginalBody != args.Body)
{
var ev = new SurgeryStepDamageChangeEvent(args.User, args.Body, args.Part, ent);
RaiseLocalEvent(ent, ref ev);
}
break;
}
}
Expand Down Expand Up @@ -621,7 +629,21 @@ private void OnSurgeryTargetStepChosen(Entity<SurgeryTargetComponent> ent, ref S
BreakOnHandChange = true,
};

_doAfter.TryStartDoAfter(doAfter);
if (_doAfter.TryStartDoAfter(doAfter))
{
var userName = Identity.Entity(user, EntityManager);
var targetName = Identity.Entity(ent.Owner, EntityManager);

var locName = $"surgery-popup-procedure-{args.Surgery}-step-{args.Step}";
var locResult = Loc.GetString(locName,
("user", userName), ("target", targetName), ("part", part));

if (locResult == locName)
locResult = Loc.GetString($"surgery-popup-step-{args.Step}",
("user", userName), ("target", targetName), ("part", part));

_popup.PopupEntity(locResult, user);
}
}

private (Entity<SurgeryComponent> Surgery, int Step)? GetNextStep(EntityUid body, EntityUid part, Entity<SurgeryComponent?> surgery, List<EntityUid> requirements)
Expand Down
6 changes: 6 additions & 0 deletions Content.Shared/Backmen/Surgery/SharedSurgerySystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,12 @@ private void OnOrganConditionValid(Entity<SurgeryOrganConditionComponent> ent, r

private void OnPartRemovedConditionValid(Entity<SurgeryPartRemovedConditionComponent> ent, ref SurgeryValidEvent args)
{
if (!_body.CanAttachToSlot(args.Part, ent.Comp.Connection))
{
args.Cancelled = true;
return;
}

var results = _body.GetBodyChildrenOfType(args.Body, ent.Comp.Part, symmetry: ent.Comp.Symmetry);
if (results is not { } || !results.Any())
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Content.Shared.Damage;

namespace Content.Shared.Medical.Surgery;

/// <summary>
/// Raised on the target entity.
/// </summary>
[ByRefEvent]
public record struct SurgeryStepDamageChangeEvent(EntityUid User, EntityUid Body, EntityUid Part, EntityUid Step);
7 changes: 7 additions & 0 deletions Content.Shared/Body/Organ/OrganComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ public sealed partial class OrganComponent : Component, ISurgeryToolComponent
[DataField, AutoNetworkedField]
public EntityUid? Body;

/// <summary>
/// Relevant body this organ originally belonged to.
/// /// FOR WHATEVER FUCKING REASON AUTONETWORKING THIS CRASHES GIBTEST AAAAAAAAAAAAAAA
/// </summary>
[DataField]
public EntityUid? OriginalBody;

/// <summary>
/// Shitcodey solution to not being able to know what name corresponds to each organ's slot ID
/// without referencing the prototype or hardcoding.
Expand Down
Loading

0 comments on commit 0327a35

Please sign in to comment.