Files
ss14-wega/Content.Server/_Wega/Surgery/SurgerySystem.Operation.cs
T
2026-06-07 20:46:45 +03:00

580 lines
23 KiB
C#

using System.Linq;
using Content.Server.Body.Systems;
using Content.Shared.Bed.Sleep;
using Content.Shared.Body;
using Content.Shared.Body.Components;
using Content.Shared.Chemistry.Components;
using Content.Shared.Damage;
using Content.Shared.Damage.Components;
using Content.Shared.Database;
using Content.Shared.DirtVisuals;
using Content.Shared.FixedPoint;
using Content.Shared.IdentityManagement;
using Content.Shared.Implants;
using Content.Shared.Implants.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.Popups;
using Content.Shared.Random.Helpers;
using Content.Shared.Silicons.Borgs.Components;
using Content.Shared.Surgery;
using Content.Shared.Surgery.Components;
using Content.Shared.Traits.Assorted;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.Surgery;
public sealed partial class SurgerySystem
{
[Dependency] private BloodstreamSystem _bloodstream = default!;
[Dependency] private SharedDirtSystem _dirt = default!;
[Dependency] private SharedSubdermalImplantSystem _implant = default!;
[Dependency] private SharedInternalStorageSystem _internal = default!;
private void PerformSurgeryEffect(SurgeryActionType action, string? requiredPart, ProtoId<InternalDamagePrototype>? damageType, float successChance,
List<SurgeryFailedType>? failureEffect, EntityUid patient, EntityUid? item)
{
if (!TryComp<OperatedComponent>(patient, out var comp))
return;
switch (action)
{
// Organic Start
case SurgeryActionType.Cut:
PerformCut((patient, comp), successChance, failureEffect);
break;
case SurgeryActionType.Retract:
PerformRetract((patient, comp), successChance, failureEffect);
break;
case SurgeryActionType.ClampBleeding:
PerformClamp((patient, comp), successChance, failureEffect);
break;
case SurgeryActionType.DrillThrough:
PerformDrill((patient, comp), successChance, failureEffect);
break;
case SurgeryActionType.HealInternalDamage:
PerformHealInternalDamage((patient, comp), requiredPart, damageType, successChance, failureEffect);
break;
case SurgeryActionType.RemoveOrgan:
PerformRemoveOrgan((patient, comp), requiredPart, successChance, failureEffect);
break;
case SurgeryActionType.InsertOrgan:
PerformInsertOrgan((patient, comp), item, requiredPart, successChance, failureEffect);
break;
case SurgeryActionType.Implanting:
PerformImplant((patient, comp), item, requiredPart, successChance, failureEffect);
break;
case SurgeryActionType.RemoveImplant:
PerformRemoveImplant((patient, comp), requiredPart, successChance, failureEffect);
break;
case SurgeryActionType.StoreItem:
PerformStoreItem((patient, comp), item, requiredPart, successChance, failureEffect);
break;
case SurgeryActionType.RetrieveItems:
PerformRetrieveItems((patient, comp), requiredPart, successChance, failureEffect);
break;
// Organic End
// Synthetic Start
case SurgeryActionType.Unscrew:
PerformUnscrew((patient, comp));
break;
case SurgeryActionType.Screw:
PerformScrew((patient, comp));
break;
case SurgeryActionType.Pulse:
PerformPulse((patient, comp));
break;
case SurgeryActionType.Weld:
PerformWeld((patient, comp));
break;
case SurgeryActionType.CutWire:
PerformCutWire((patient, comp));
break;
case SurgeryActionType.StripWire:
PerformStripWire((patient, comp));
break;
case SurgeryActionType.MendWire:
PerformMendWire((patient, comp));
break;
case SurgeryActionType.Pry:
PerformPry((patient, comp));
break;
case SurgeryActionType.Anchor:
PerformAnchor((patient, comp));
break;
case SurgeryActionType.Unanchor:
PerformUnanchor((patient, comp));
break;
// Synthetic End
default: return;
}
ApplySterilityConsequences((patient, comp));
// Any action without anesthesia will cause pain.
if (!HasComp<SleepingComponent>(patient) && !HasComp<PainNumbnessStatusEffectComponent>(patient) && !comp.OperatedPart
&& !_mobState.IsDead(patient) && !HasComp<SyntheticOperatedComponent>(patient))
_chat.TryEmoteWithoutChat(patient, _proto.Index(Scream), true);
}
#region Organic
private void PerformCut(Entity<OperatedComponent> patient, float successChance, List<SurgeryFailedType>? failureEffect)
{
if (patient.Comp.Surgeon == null)
return;
if (!RollSuccess(patient, patient.Comp.Surgeon.Value, successChance))
HandleFailure(patient, failureEffect);
if (!TryComp<BloodstreamComponent>(patient, out var bloodstream) || HasComp<SyntheticOperatedComponent>(patient))
return;
string? bloodReagentId = null;
if (bloodstream.BloodReferenceSolution.Contents.Count > 0)
{
var reagentQuantity = bloodstream.BloodReferenceSolution.Contents[0];
bloodReagentId = reagentQuantity.Reagent.Prototype;
}
ApplyBloodToClothing(patient.Comp.Surgeon.Value, bloodReagentId, 2f * SharedDirtSystem.MaxDirtLevel);
_bloodstream.TryModifyBleedAmount(patient.Owner, 2f);
}
private void PerformRetract(Entity<OperatedComponent> patient, float successChance, List<SurgeryFailedType>? failureEffect)
{
if (patient.Comp.Surgeon == null)
return;
if (!RollSuccess(patient, patient.Comp.Surgeon.Value, successChance))
HandleFailure(patient, failureEffect);
}
private void PerformClamp(Entity<OperatedComponent> patient, float successChance, List<SurgeryFailedType>? failureEffect)
{
if (patient.Comp.Surgeon == null)
return;
if (!RollSuccess(patient, patient.Comp.Surgeon.Value, successChance))
HandleFailure(patient, failureEffect);
if (!HasComp<BloodstreamComponent>(patient) || HasComp<SyntheticOperatedComponent>(patient))
return;
_bloodstream.TryModifyBleedAmount(patient.Owner, -10f);
}
private void PerformDrill(Entity<OperatedComponent> patient, float successChance, List<SurgeryFailedType>? failureEffect)
{
if (patient.Comp.Surgeon == null)
return;
if (!RollSuccess(patient, patient.Comp.Surgeon.Value, successChance))
HandleFailure(patient, failureEffect);
_damage.TryChangeDamage(patient.Owner, new DamageSpecifier { DamageDict = { { PiercingDamage, 2.5 } } }, true);
}
private void PerformHealInternalDamage(Entity<OperatedComponent> patient, string? requiredPart, ProtoId<InternalDamagePrototype>? damageType, float successChance, List<SurgeryFailedType>? failureEffect)
{
if (patient.Comp.Surgeon == null || string.IsNullOrEmpty(requiredPart) || damageType == null)
return;
if (!RollSuccess(patient, patient.Comp.Surgeon.Value, successChance))
HandleFailure(patient, failureEffect, requiredPart);
TryRemoveInternalDamage(patient, damageType, requiredPart);
}
private void PerformRemoveOrgan(Entity<OperatedComponent> patient, string? requiredOrgan, float successChance, List<SurgeryFailedType>? failureEffect)
{
if (patient.Comp.Surgeon == null || string.IsNullOrEmpty(requiredOrgan))
return;
if (!RollSuccess(patient, patient.Comp.Surgeon.Value, successChance))
HandleFailure(patient, failureEffect);
if (!TryComp<BodyComponent>(patient, out var body) || body.Organs == null)
return;
EntityUid? organToRemove = null;
foreach (var organ in body.Organs.ContainedEntities)
{
if (TryComp<OrganComponent>(organ, out var organComp) && organComp.Category == requiredOrgan)
{
organToRemove = organ;
break;
}
}
if (organToRemove == null)
return;
_container.Remove(organToRemove.Value, body.Organs);
_popup.PopupEntity(Loc.GetString("surgery-organ-removed"), patient);
_hands.TryPickupAnyHand(patient.Comp.Surgeon.Value, organToRemove.Value);
if (TryComp<BloodstreamComponent>(patient, out var bloodstream) && !HasComp<SyntheticOperatedComponent>(patient))
{
string? bloodReagentId = null;
if (bloodstream.BloodReferenceSolution.Contents.Count > 0)
{
var reagentQuantity = bloodstream.BloodReferenceSolution.Contents[0];
bloodReagentId = reagentQuantity.Reagent.Prototype;
}
ApplyBloodToClothing(patient.Comp.Surgeon.Value, bloodReagentId, 2f * SharedDirtSystem.MaxDirtLevel);
_bloodstream.TryModifyBleedAmount(patient.Owner, 2f);
}
}
private void PerformInsertOrgan(Entity<OperatedComponent> patient, EntityUid? item, string? requiredOrgan, float successChance, List<SurgeryFailedType>? failureEffect)
{
if (patient.Comp.Surgeon == null || item == null || string.IsNullOrEmpty(requiredOrgan))
return;
if (!RollSuccess(patient, patient.Comp.Surgeon.Value, successChance))
HandleFailure(patient, failureEffect);
if (!TryComp<BodyComponent>(patient, out var body) || body.Organs == null)
return;
_container.Insert(item.Value, body.Organs);
if (!HasComp<SterileComponent>(item.Value) && _random.Prob(0.4f))
_disease.TryAddDisease(patient, "SurgicalSepsis");
_popup.PopupEntity(Loc.GetString("surgery-organ-inserted"), patient);
}
private void PerformImplant(Entity<OperatedComponent> patient, EntityUid? item, string? requiredPart, float successChance, List<SurgeryFailedType>? failureEffect)
{
if (patient.Comp.Surgeon == null || item == null)
return;
if (!RollSuccess(patient, patient.Comp.Surgeon.Value, successChance))
HandleFailure(patient, failureEffect, requiredPart);
if (!HasComp<SubdermalImplantComponent>(item.Value))
return;
_implant.ForceImplant(patient.Owner, item.Value);
_admin.Add(LogType.Action, LogImpact.Medium,
$"{ToPrettyString(patient.Comp.Surgeon.Value):user} successfully implanted {ToPrettyString(item.Value):implant} into {ToPrettyString(patient):target}");
}
private void PerformRemoveImplant(Entity<OperatedComponent> patient, string? requiredImplant, float successChance, List<SurgeryFailedType>? failureEffect)
{
if (patient.Comp.Surgeon == null || string.IsNullOrEmpty(requiredImplant))
return;
if (!RollSuccess(patient, patient.Comp.Surgeon.Value, successChance))
HandleFailure(patient, failureEffect, requiredImplant);
if (!TryComp<ImplantedComponent>(patient, out var implanted))
return;
foreach (var implant in implanted.ImplantContainer.ContainedEntities.ToArray())
{
var proto = MetaData(implant).EntityPrototype;
if (proto != null && proto.ID == requiredImplant)
{
if (TryComp<SubdermalImplantComponent>(implant, out var implantComp) && implantComp.Permanent)
return;
_implant.ForceRemove(patient.Owner, implant);
_hands.TryPickupAnyHand(patient.Comp.Surgeon.Value, implant);
var ev = new ImplantRemovedEvent(implant, patient.Owner);
RaiseLocalEvent(patient.Owner, ref ev);
_admin.Add(LogType.Action, LogImpact.Medium,
$"{ToPrettyString(patient.Comp.Surgeon.Value):user} successfully removed {ToPrettyString(implant):implant} from {ToPrettyString(patient):target}");
break;
}
}
}
private void PerformStoreItem(Entity<OperatedComponent> patient, EntityUid? item, string? requiredPart, float successChance, List<SurgeryFailedType>? failureEffect)
{
if (patient.Comp.Surgeon == null || item == null || string.IsNullOrEmpty(requiredPart) || HasComp<BorgChassisComponent>(patient.Comp.Surgeon))
return;
if (!RollSuccess(patient, patient.Comp.Surgeon.Value, successChance))
HandleFailure(patient, failureEffect);
// You can't perform a mobs, body part, organ, or implant in this way
if (HasComp<BodyComponent>(item) || HasComp<OrganComponent>(item)
|| HasComp<SubdermalImplantComponent>(item) || !_internal.TryStoreItem(patient.Owner, item.Value, requiredPart))
_popup.PopupEntity(Loc.GetString("surgery-store-item-failed"), patient);
else
_admin.Add(LogType.Action, LogImpact.Medium,
$"{ToPrettyString(patient.Comp.Surgeon.Value):user} stored {ToPrettyString(item.Value):item} in {requiredPart} of {ToPrettyString(patient):target}");
}
private void PerformRetrieveItems(Entity<OperatedComponent> patient, string? requiredPart, float successChance, List<SurgeryFailedType>? failureEffect)
{
if (patient.Comp.Surgeon == null || string.IsNullOrEmpty(requiredPart))
return;
if (!RollSuccess(patient, patient.Comp.Surgeon.Value, successChance))
HandleFailure(patient, failureEffect);
if (!_internal.TryRemoveItems(patient, requiredPart))
_popup.PopupEntity(Loc.GetString("surgery-retrieve-items-failed"), patient);
else
_admin.Add(LogType.Action, LogImpact.Medium,
$"{ToPrettyString(patient.Comp.Surgeon.Value):user} retrieved items from {requiredPart} of {ToPrettyString(patient):target}");
}
#endregion Organic
#region Synthetic
private void PerformUnscrew(Entity<OperatedComponent> patient)
{
if (patient.Comp.Surgeon == null || !HasComp<SyntheticOperatedComponent>(patient))
return;
_damage.TryChangeDamage(patient.Owner, new DamageSpecifier { DamageDict = { { BluntDamage, 2f } } }, true);
if (TryComp<DamageableComponent>(patient, out var damageable))
{
var totalDamage = _damage.GetTotalDamage((patient, damageable));
if (totalDamage >= 50)
{
_audio.PlayPvs(new SoundCollectionSpecifier("sparks"), patient.Owner);
}
}
}
private void PerformScrew(Entity<OperatedComponent> patient)
{
if (patient.Comp.Surgeon == null || !HasComp<SyntheticOperatedComponent>(patient))
return;
_damage.TryChangeDamage(patient.Owner, new DamageSpecifier { DamageDict = { { BluntDamage, -2f } } }, true);
}
private void PerformPulse(Entity<OperatedComponent> patient)
{
if (patient.Comp.Surgeon == null || !HasComp<SyntheticOperatedComponent>(patient))
return;
_stun.TryAddStunDuration(patient.Owner, TimeSpan.FromSeconds(5));
}
private void PerformWeld(Entity<OperatedComponent> patient)
{
if (patient.Comp.Surgeon == null || !HasComp<SyntheticOperatedComponent>(patient))
return;
var healAmount = new DamageSpecifier { DamageDict = { { BluntDamage, -5f }, { SlashDamage, -5f } } };
_damage.TryChangeDamage(patient.Owner, healAmount, true);
if (HasComp<BloodstreamComponent>(patient))
_bloodstream.TryModifyBleedAmount(patient.Owner, -10f);
}
private void PerformCutWire(Entity<OperatedComponent> patient)
{
if (patient.Comp.Surgeon == null || !HasComp<SyntheticOperatedComponent>(patient))
return;
_audio.PlayPvs(new SoundCollectionSpecifier("sparks"), patient.Owner);
_stun.TryAddStunDuration(patient.Owner, TimeSpan.FromSeconds(1));
}
private void PerformStripWire(Entity<OperatedComponent> patient)
{
if (patient.Comp.Surgeon == null || !HasComp<SyntheticOperatedComponent>(patient))
return;
_damage.TryChangeDamage(patient.Owner, new DamageSpecifier { DamageDict = { { SlashDamage, -2f } } }, true);
}
private void PerformMendWire(Entity<OperatedComponent> patient)
{
if (patient.Comp.Surgeon == null || !HasComp<SyntheticOperatedComponent>(patient))
return;
var healAmount = new DamageSpecifier
{
DamageDict = { { BluntDamage, -6f }, { SlashDamage, -6f }, { PiercingDamage, -6f }, { HeatDamage, -6f } }
};
_damage.TryChangeDamage(patient.Owner, healAmount, true);
}
private void PerformPry(Entity<OperatedComponent> patient)
{
if (patient.Comp.Surgeon == null || !HasComp<SyntheticOperatedComponent>(patient))
return;
_damage.TryChangeDamage(patient.Owner, new DamageSpecifier { DamageDict = { { BluntDamage, 5f } } }, true);
}
private void PerformAnchor(Entity<OperatedComponent> patient)
{
if (patient.Comp.Surgeon == null || !HasComp<SyntheticOperatedComponent>(patient))
return;
_damage.TryChangeDamage(patient.Owner, new DamageSpecifier { DamageDict = { { BluntDamage, -8f } } }, true);
}
private void PerformUnanchor(Entity<OperatedComponent> patient)
{
if (patient.Comp.Surgeon == null || !HasComp<SyntheticOperatedComponent>(patient))
return;
_damage.TryChangeDamage(patient.Owner, new DamageSpecifier { DamageDict = { { BluntDamage, 4f } } }, true);
if (_random.Prob(0.3f))
_audio.PlayPvs(new SoundCollectionSpecifier("sparks"), patient.Owner);
}
#endregion Synthetic
#region Other Logic
private bool RollSuccess(Entity<OperatedComponent> ent, EntityUid surgeon, float baseChance)
{
var item = _hands.GetActiveItemOrSelf(surgeon);
if (HasComp<SurgicalSkillComponent>(surgeon) && SurgeryTools.Any(tool => _tool.HasQuality(item, tool))
|| Organs.Any(tag => _tag.HasTag(item, tag)) || Parts.Any(tag => _tag.HasTag(item, tag)))
{
return true;
}
if (TryGetOperatingTable(ent, out var tableModifier))
baseChance *= tableModifier;
return _random.Prob(baseChance);
}
private void ApplySterilityConsequences(Entity<OperatedComponent> patient)
{
if (patient.Comp.Surgeon == null || HasComp<SyntheticOperatedComponent>(patient))
return;
var sterility = patient.Comp.Sterility;
var item = _hands.GetActiveItemOrSelf(patient.Comp.Surgeon.Value);
if (sterility >= 0.7f && HasComp<SterileComponent>(item))
return;
if (sterility >= 0.5f)
{
var painChance = 0.3f + (0.75f - sterility) / 0.25f * 0.4f;
if (_random.Prob(painChance))
{
if (!HasComp<SleepingComponent>(patient) && !HasComp<PainNumbnessStatusEffectComponent>(patient)
&& !patient.Comp.OperatedPart && !_mobState.IsDead(patient) && !HasComp<SyntheticOperatedComponent>(patient))
_chat.TryEmoteWithoutChat(patient, _proto.Index(Scream), true);
_jittering.DoJitter(patient, TimeSpan.FromSeconds(4), true);
}
var slowChance = 0.15f + (0.75f - sterility) / 0.25f * 0.3f;
if (_random.Prob(slowChance))
{
_movementMod.TryUpdateMovementSpeedModDuration(patient, MovementModStatusSystem.Slowdown,
TimeSpan.FromSeconds(30), 0.85f, 0.85f);
}
return;
}
var painChanceLow = 0.7f + (0.5f - sterility) / 0.3f * 0.2f;
if (_random.Prob(painChanceLow))
{
if (!HasComp<SleepingComponent>(patient) && !HasComp<PainNumbnessStatusEffectComponent>(patient)
&& !patient.Comp.OperatedPart && !_mobState.IsDead(patient) && !HasComp<SyntheticOperatedComponent>(patient))
_chat.TryEmoteWithoutChat(patient, _proto.Index(Scream), true);
_jittering.DoJitter(patient, TimeSpan.FromSeconds(6), true);
}
var slowChanceLow = 0.45f + (0.5f - sterility) / 0.3f * 0.25f;
if (_random.Prob(slowChanceLow))
{
_movementMod.TryUpdateMovementSpeedModDuration(patient, MovementModStatusSystem.Slowdown,
TimeSpan.FromSeconds(45), 0.6f, 0.6f);
}
var sepsisChanceLow = 0.05f + (0.5f - sterility) / 0.3f * 0.05f;
if (_random.Prob(sepsisChanceLow))
{
_disease.TryAddDisease(patient, "SurgicalSepsis");
}
}
public void ApplyBloodToClothing(EntityUid surgeon, string? bloodReagentId, float bloodAmount)
{
if (bloodReagentId == null)
return;
var bloodSolution = new Solution();
bloodSolution.AddReagent(bloodReagentId, FixedPoint2.New(bloodAmount));
var clothingSlots = new[] { "outerClothing", "jumpsuit", "gloves" };
_dirt.ApplyDirtToClothing(surgeon, bloodSolution, _random.Pick(clothingSlots));
}
private void HandleFailure(Entity<OperatedComponent> patient, List<SurgeryFailedType>? failureEffect, string? bodyPart = null)
{
if (failureEffect == null || failureEffect.Count == 0)
return;
var effect = _random.Pick(failureEffect);
switch (effect)
{
case SurgeryFailedType.Empty: return;
case SurgeryFailedType.Cut:
_damage.TryChangeDamage(patient.Owner, new DamageSpecifier { DamageDict = { { SlashDamage, 5 } } }, true);
break;
case SurgeryFailedType.Bleeding:
TryAddInternalDamage(patient, "ArterialBleeding", bodyPart: bodyPart);
break;
case SurgeryFailedType.Burn:
_damage.TryChangeDamage(patient.Owner, new DamageSpecifier { DamageDict = { { HeatDamage, 5 } } }, true);
break;
case SurgeryFailedType.Fracture:
TryAddInternalDamage(patient, "BoneFracture", bodyPart: bodyPart);
break;
case SurgeryFailedType.Pain:
{
if (!HasComp<SleepingComponent>(patient) && !HasComp<PainNumbnessStatusEffectComponent>(patient) && !patient.Comp.OperatedPart
&& !_mobState.IsDead(patient) && !HasComp<SyntheticOperatedComponent>(patient))
_chat.TryEmoteWithoutChat(patient, _proto.Index(Scream), true);
_jittering.DoJitter(patient, TimeSpan.FromSeconds(5), true);
break;
}
}
if (effect != SurgeryFailedType.Empty && !_mobState.IsDead(patient))
_popup.PopupPredicted(Loc.GetString($"surgery-handle-failed-{effect.ToString().ToLower()}", ("patient", Identity.Entity(patient, EntityManager))),
patient, null, PopupType.MediumCaution);
}
#endregion
}