// SPDX-FileCopyrightText: 2024 Piras314 // 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 // SPDX-FileCopyrightText: 2025 gus // // 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>(OnUtilityVerb); SubscribeLocalEvent(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(OnSurgerySpecialDamageChange); SubscribeLocalEvent(OnSurgeryDamageChange); SubscribeLocalEvent(OnStepScreamComplete); SubscribeLocalEvent(OnStepSpawnComplete); } protected override void RefreshUI(EntityUid body) { var surgeries = new Dictionary>(); 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(part, out var partComp)) return; _damageable.TryChangeDamage(body, damage, true, origin: user, canSever: false, partMultiplier: partMultiplier, targetPart: _body.GetTargetBodyPart(partComp)); } private void AttemptStartSurgery(Entity 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 ent, ref GetVerbsEvent args) { if (!args.CanInteract || !args.CanAccess || !HasComp(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 ent, ref SurgeryStepDamageEvent args) => SetDamage(args.Body, args.Damage, args.PartMultiplier, args.User, args.Part); private void OnSurgeryDamageChange(Entity ent, ref SurgeryStepDamageChangeEvent args) { var damageChange = ent.Comp.Damage; if (HasComp(args.Body)) damageChange = damageChange * ent.Comp.SleepModifier; SetDamage(args.Body, damageChange, 0.5f, args.User, args.Part); } private void OnSurgerySpecialDamageChange(Entity 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 ent, ref SurgeryStepEvent args) { if (HasComp(args.Body)) return; _chat.TryEmoteWithChat(args.Body, ent.Comp.Emote); } private void OnStepSpawnComplete(Entity ent, ref SurgeryStepEvent args) => SpawnAtPosition(ent.Comp.Entity, Transform(args.Body).Coordinates); }