using System.Linq;
using Content.Shared._Mono.CorticalBorer; // Mono
using Content.Shared._Shitmed.Medical.Surgery.Conditions;
using Content.Shared._Shitmed.Medical.Surgery.Effects.Complete;
using Content.Shared.Body.Systems;
using Content.Shared._Shitmed.Medical.Surgery.Steps;
using Content.Shared._Shitmed.Medical.Surgery.Steps.Parts;
//using Content.Shared._RMC14.Xenonids.Parasite;
using Content.Shared.Body.Part;
using Content.Shared.Damage.Systems;
using Content.Shared.Damage.Components;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Body.Components;
using Content.Shared.Buckle.Components;
using Content.Shared.DoAfter;
using Content.Shared.Mobs.Systems;
using Content.Shared.GameTicking;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Markings;
using Content.Shared.Interaction;
using Content.Shared.Inventory;
using Content.Shared.Popups;
using Content.Shared.Prototypes;
using Content.Shared.Standing;
using Content.Shared.Tag; // DeltaV: surgery can operate through some clothing
using Robust.Shared.Audio.Systems;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Content.Shared.Body.Organ;
namespace Content.Shared._Shitmed.Medical.Surgery;
public abstract partial class SharedSurgerySystem : EntitySystem
{
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly IComponentFactory _compFactory = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedBodySystem _body = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
[Dependency] protected readonly MobStateSystem _mobState = default!; // DeltaV - made protected
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly IPrototypeManager _prototypes = default!;
[Dependency] private readonly RotateToFaceSystem _rotateToFace = default!;
[Dependency] private readonly StandingStateSystem _standing = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly TagSystem _tagSystem = default!; // DeltaV: surgery can operate through some clothing
[Dependency] private readonly SharedCorticalBorerSystem _corticalBorer = default!; // Mono
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
///
/// Cache of all surgery prototypes' singleton entities.
/// Cleared after a prototype reload.
///
private readonly Dictionary _surgeries = new();
private readonly List _allSurgeries = new();
///
/// Every surgery entity prototype id.
/// Kept in sync with prototype reloads.
///
public IReadOnlyList AllSurgeries => _allSurgeries;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent(OnRoundRestartCleanup);
SubscribeLocalEvent(OnTargetDoAfter);
SubscribeLocalEvent(OnCloseIncisionValid);
//SubscribeLocalEvent(OnLarvaValid);
SubscribeLocalEvent(OnHasBodyConditionValid);
SubscribeLocalEvent(OnPartConditionValid);
SubscribeLocalEvent(OnOrganConditionValid);
SubscribeLocalEvent(OnWoundedValid);
SubscribeLocalEvent(OnPartRemovedConditionValid);
SubscribeLocalEvent(OnBodyConditionValid);
SubscribeLocalEvent(OnOrganSlotConditionValid);
SubscribeLocalEvent(OnPartPresentConditionValid);
SubscribeLocalEvent(OnMarkingPresentValid);
SubscribeLocalEvent(OnBodyComponentConditionValid);
SubscribeLocalEvent(OnPartComponentConditionValid);
SubscribeLocalEvent(OnOrganOnAddConditionValid);
//SubscribeLocalEvent(OnRemoveLarva);
SubscribeLocalEvent(OnCorticalBorerValid); // Mono
SubscribeLocalEvent(OnPrototypesReloaded);
SubscribeLocalEvent(OnBodyPartAccessibleOverride); // Upstream Mege Changes
InitializeSteps();
LoadPrototypes();
}
private void OnRoundRestartCleanup(RoundRestartCleanupEvent ev)
{
_surgeries.Clear();
}
private void OnTargetDoAfter(Entity ent, ref SurgeryDoAfterEvent args)
{
if (!_timing.IsFirstTimePredicted)
return;
if (args.Cancelled)
{
var failEv = new SurgeryStepFailedEvent(args.User, ent, args.Surgery, args.Step);
RaiseLocalEvent(args.User, ref failEv);
return;
}
if (args.Handled
|| args.Target is not { } target
|| !IsSurgeryValid(ent, target, args.Surgery, args.Step, args.User, out var surgery, out var part, out var step)
|| !PreviousStepsComplete(ent, part, surgery, args.Step)
|| !CanPerformStep(args.User, ent, part, step, false))
{
Log.Warning($"{ToPrettyString(args.User)} tried to start invalid surgery.");
return;
}
var complete = IsStepComplete(ent, part, args.Step, surgery);
args.Repeat = HasComp(step) && !complete;
var ev = new SurgeryStepEvent(args.User, ent, part, GetTools(args.User), surgery, step, complete);
RaiseLocalEvent(step, ref ev);
RaiseLocalEvent(args.User, ref ev);
RefreshUI(ent);
}
private void OnCloseIncisionValid(Entity ent, ref SurgeryValidEvent args)
{
if (!HasComp(args.Part) ||
!HasComp(args.Part) ||
!HasComp(args.Part) ||
!HasComp(args.Part) ||
!HasComp(args.Part))
{
args.Cancelled = true;
}
}
private void OnWoundedValid(Entity ent, ref SurgeryValidEvent args)
{
if (!TryComp(args.Body, out DamageableComponent? damageable)
|| !TryComp(args.Part, out DamageableComponent? partDamageable)
|| damageable.TotalDamage <= 0
&& partDamageable.TotalDamage <= 0
&& !HasComp(args.Part))
args.Cancelled = true;
}
/*private void OnLarvaValid(Entity ent, ref SurgeryValidEvent args)
{
if (!TryComp(args.Body, out VictimInfectedComponent? infected))
args.Cancelled = true;
// The larva has fully developed and surgery is now impossible
if (infected != null && infected.SpawnedLarva != null)
args.Cancelled = true;
}*/
// Begin Mono Changes - borer
private void OnCorticalBorerValid(Entity ent, ref SurgeryValidEvent args)
{
if (!HasComp(args.Body) ||
!HasComp(args.Part))
args.Cancelled = true;
}
// End Mono Changes - borer
private void OnBodyComponentConditionValid(Entity ent, ref SurgeryValidEvent args)
{
var present = true;
foreach (var reg in ent.Comp.Components.Values)
{
var compType = reg.Component.GetType();
if (!HasComp(args.Body, compType))
present = false;
}
if (ent.Comp.Inverse ? present : !present)
args.Cancelled = true;
}
private void OnPartComponentConditionValid(Entity ent, ref SurgeryValidEvent args)
{
var present = true;
foreach (var reg in ent.Comp.Components.Values)
{
var compType = reg.Component.GetType();
if (!HasComp(args.Part, compType))
present = false;
}
if (ent.Comp.Inverse ? present : !present)
args.Cancelled = true;
}
// This is literally a duplicate of the checks in OnToolCheck for SurgeryStepComponent.AddOrganOnAdd
private void OnOrganOnAddConditionValid(Entity ent, ref SurgeryValidEvent args)
{
if (!TryComp(args.Part, out var part)
|| part.Body != args.Body)
{
args.Cancelled = true;
return;
}
var organSlotIdToOrgan = _body.GetPartOrgans(args.Part, part).ToDictionary(o => o.Item2.SlotId, o => o.Item2);
var allOnAddFound = true;
var zeroOnAddFound = true;
foreach (var (organSlotId, components) in ent.Comp.Components)
{
if (!organSlotIdToOrgan.TryGetValue(organSlotId, out var organ))
continue;
if (organ.OnAdd == null)
{
allOnAddFound = false;
continue;
}
foreach (var key in components.Keys)
{
if (!organ.OnAdd.ContainsKey(key))
allOnAddFound = false;
else
zeroOnAddFound = false;
}
}
if (ent.Comp.Inverse ? allOnAddFound : zeroOnAddFound)
args.Cancelled = true;
}
private void OnHasBodyConditionValid(Entity ent, ref SurgeryValidEvent args)
{
if (CompOrNull(args.Part)?.Body == null)
args.Cancelled = true;
}
private void OnPartConditionValid(Entity ent, ref SurgeryValidEvent args)
{
if (!TryComp(args.Part, out var part))
{
args.Cancelled = true;
return;
}
var typeMatch = part.PartType == ent.Comp.Part;
var symmetryMatch = ent.Comp.Symmetry == null || part.Symmetry == ent.Comp.Symmetry;
var valid = typeMatch && symmetryMatch;
if (ent.Comp.Inverse ? valid : !valid)
args.Cancelled = true;
}
private void OnOrganConditionValid(Entity ent, ref SurgeryValidEvent args)
{
if (!TryComp(args.Part, out var partComp)
|| partComp.Body != args.Body
|| ent.Comp.Organ == null)
{
args.Cancelled = true;
return;
}
foreach (var reg in ent.Comp.Organ.Values)
{
if (_body.TryGetBodyPartOrgans(args.Part, reg.Component.GetType(), out var organs)
&& organs.Count > 0)
{
if (ent.Comp.Inverse
&& (!ent.Comp.Reattaching
|| ent.Comp.Reattaching
&& !organs.Any(organ => HasComp(organ.Id))))
args.Cancelled = true;
// Start of DeltaV Additions - Checks if any organ has the removable component set to true, hiding it from the surgery UI
if (!organs.Any(organ => !TryComp(organ.Id, out var organComp)
|| organComp.Removable))
args.Cancelled = true;
// End of DeltaV Additions
}
else if (!ent.Comp.Inverse)
args.Cancelled = true;
}
}
private void OnBodyConditionValid(Entity ent, ref SurgeryValidEvent args)
{
if (TryComp(args.Body, out var body) && body.Prototype is {} bodyId)
args.Cancelled |= ent.Comp.Accepted.Contains(bodyId) ^ !ent.Comp.Inverse;
}
private void OnOrganSlotConditionValid(Entity ent, ref SurgeryValidEvent args)
{
args.Cancelled |= _body.CanInsertOrgan(args.Part, ent.Comp.OrganSlot) ^ !ent.Comp.Inverse;
}
private void OnPartRemovedConditionValid(Entity 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).ToList();
if (results is not { } || !results.Any())
return;
if (!results.Any(part => HasComp(part.Id)))
args.Cancelled = true;
}
private void OnPartPresentConditionValid(Entity ent, ref SurgeryValidEvent args)
{
if (args.Part == EntityUid.Invalid
|| !HasComp(args.Part))
args.Cancelled = true;
}
private void OnMarkingPresentValid(Entity ent, ref SurgeryValidEvent args)
{
var markingCategory = MarkingCategoriesConversion.FromHumanoidVisualLayers(ent.Comp.MarkingCategory);
var hasMarking = TryComp(args.Body, out HumanoidAppearanceComponent? bodyAppearance)
&& bodyAppearance.MarkingSet.Markings.TryGetValue(markingCategory, out var markingList)
&& markingList.Any(marking => marking.MarkingId.Contains(ent.Comp.MatchString));
if ((!ent.Comp.Inverse && hasMarking) || (ent.Comp.Inverse && !hasMarking))
args.Cancelled = true;
}
/*private void OnRemoveLarva(Entity ent, ref SurgeryCompletedEvent args)
{
RemCompDeferred(ent);
}*/
///
/// DektaV - This is needed because https://github.com/space-wizards/space-station-14/pull/40299 added in
/// checking if the target is accessible but the target is a contained within the mob entity, so it
/// is technically not accessible, so we need to add an override to tell the interaction system that
/// it is accessible.
///
/// The body part entity that is being operated on.
///
private void OnBodyPartAccessibleOverride(Entity ent, ref AccessibleOverrideEvent args)
{
if (args.Accessible)
return;
var xform = Transform(ent);
if (!_interaction.InRangeUnobstructed(args.User, xform.ParentUid))
return;
args.Accessible = true;
args.Handled = true;
}
protected bool IsSurgeryValid(EntityUid body, EntityUid targetPart, EntProtoId surgery, EntProtoId stepId,
EntityUid user, out Entity surgeryEnt, out EntityUid part, out EntityUid step)
{
surgeryEnt = default;
part = default;
step = default;
if (!HasComp(body) ||
!IsLyingDown(body, user) ||
GetSingleton(surgery) is not { } surgeryEntId ||
!TryComp(surgeryEntId, out SurgeryComponent? surgeryComp) ||
!surgeryComp.Steps.Contains(stepId) ||
GetSingleton(stepId) is not { } stepEnt
|| !HasComp(targetPart)
&& !HasComp(targetPart))
return false;
var ev = new SurgeryValidEvent(body, targetPart);
if (_timing.IsFirstTimePredicted)
{
RaiseLocalEvent(stepEnt, ref ev);
RaiseLocalEvent(surgeryEntId, ref ev);
}
if (ev.Cancelled)
return false;
surgeryEnt = (surgeryEntId, surgeryComp);
part = targetPart;
step = stepEnt;
return true;
}
public EntityUid? GetSingleton(EntProtoId surgeryOrStep)
{
if (!_prototypes.HasIndex(surgeryOrStep))
return null;
// This (for now) assumes that surgery entity data remains unchanged between client
// and server
// if it does not you get the bullet
if (!_surgeries.TryGetValue(surgeryOrStep, out var ent) || TerminatingOrDeleted(ent))
{
ent = Spawn(surgeryOrStep, MapCoordinates.Nullspace);
_surgeries[surgeryOrStep] = ent;
}
return ent;
}
private List GetTools(EntityUid surgeon)
{
return _hands.EnumerateHeld(surgeon).ToList();
}
public bool IsLyingDown(EntityUid entity, EntityUid user)
{
if (_standing.IsDown(entity))
return true;
if (TryComp(entity, out BuckleComponent? buckle) &&
TryComp(buckle.BuckledTo, out StrapComponent? strap))
{
var rotation = strap.Rotation;
if (rotation.GetCardinalDir() is Direction.West or Direction.East)
return true;
}
_popup.PopupEntity(Loc.GetString("surgery-error-laying"), user, user);
return false;
}
protected virtual void RefreshUI(EntityUid body)
{
}
private void OnPrototypesReloaded(PrototypesReloadedEventArgs args)
{
if (!args.WasModified())
return;
LoadPrototypes();
}
private void LoadPrototypes()
{
// Cache is probably invalid so delete it
foreach (var uid in _surgeries.Values)
{
Del(uid);
}
_surgeries.Clear();
_allSurgeries.Clear();
foreach (var entity in _prototypes.EnumeratePrototypes())
if (entity.HasComponent())
_allSurgeries.Add(new EntProtoId(entity.ID));
}
}