SharedSurgerySystem.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. // SPDX-FileCopyrightText: 2024 Piras314 <p1r4s@proton.me>
  2. // SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
  3. // SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
  4. // SPDX-FileCopyrightText: 2025 Janet Blackquill <uhhadd@gmail.com>
  5. // SPDX-FileCopyrightText: 2025 deltanedas <39013340+deltanedas@users.noreply.github.com>
  6. // SPDX-FileCopyrightText: 2025 deltanedas <@deltanedas:kde.org>
  7. //
  8. // SPDX-License-Identifier: AGPL-3.0-or-later
  9. using System.Linq;
  10. using Content.Shared._Shitmed.Medical.Surgery.Conditions;
  11. using Content.Shared.Body.Systems;
  12. using Content.Shared._Shitmed.Medical.Surgery.Steps;
  13. using Content.Shared._Shitmed.Medical.Surgery.Steps.Parts;
  14. //using Content.Shared._RMC14.Xenonids.Parasite;
  15. using Content.Shared.Body.Part;
  16. using Content.Shared.Damage;
  17. using Content.Shared.Containers.ItemSlots;
  18. using Content.Shared.Body.Components;
  19. using Content.Shared.Buckle.Components;
  20. using Content.Shared.DoAfter;
  21. using Content.Shared.Mobs.Systems;
  22. using Content.Shared.GameTicking;
  23. using Content.Shared.Hands.EntitySystems;
  24. using Content.Shared.Humanoid;
  25. using Content.Shared.Humanoid.Markings;
  26. using Content.Shared.Interaction;
  27. using Content.Shared.Inventory;
  28. using Content.Shared.Popups;
  29. using Content.Shared.Prototypes;
  30. using Content.Shared.Standing;
  31. using Robust.Shared.Audio.Systems;
  32. using Robust.Shared.Map;
  33. using Robust.Shared.Network;
  34. using Robust.Shared.Prototypes;
  35. using Robust.Shared.Timing;
  36. namespace Content.Shared._Shitmed.Medical.Surgery;
  37. public abstract partial class SharedSurgerySystem : EntitySystem
  38. {
  39. [Dependency] private readonly SharedAudioSystem _audio = default!;
  40. [Dependency] private readonly IComponentFactory _compFactory = default!;
  41. [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
  42. [Dependency] private readonly SharedHandsSystem _hands = default!;
  43. [Dependency] private readonly IGameTiming _timing = default!;
  44. [Dependency] private readonly SharedBodySystem _body = default!;
  45. [Dependency] private readonly DamageableSystem _damageable = default!;
  46. [Dependency] private readonly INetManager _net = default!;
  47. [Dependency] private readonly InventorySystem _inventory = default!;
  48. [Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
  49. [Dependency] private readonly MobStateSystem _mobState = default!;
  50. [Dependency] private readonly SharedPopupSystem _popup = default!;
  51. [Dependency] private readonly IPrototypeManager _prototypes = default!;
  52. [Dependency] private readonly RotateToFaceSystem _rotateToFace = default!;
  53. [Dependency] private readonly StandingStateSystem _standing = default!;
  54. [Dependency] private readonly SharedTransformSystem _transform = default!;
  55. [Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
  56. /// <summary>
  57. /// Cache of all surgery prototypes' singleton entities.
  58. /// Cleared after a prototype reload.
  59. /// </summary>
  60. private readonly Dictionary<EntProtoId, EntityUid> _surgeries = new();
  61. private readonly List<EntProtoId> _allSurgeries = new();
  62. /// <summary>
  63. /// Every surgery entity prototype id.
  64. /// Kept in sync with prototype reloads.
  65. /// </summary>
  66. public IReadOnlyList<EntProtoId> AllSurgeries => _allSurgeries;
  67. public override void Initialize()
  68. {
  69. base.Initialize();
  70. SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestartCleanup);
  71. SubscribeLocalEvent<SurgeryTargetComponent, MapInitEvent>(OnMapInit);
  72. SubscribeLocalEvent<SurgeryTargetComponent, SurgeryDoAfterEvent>(OnTargetDoAfter);
  73. SubscribeLocalEvent<SurgeryCloseIncisionConditionComponent, SurgeryValidEvent>(OnCloseIncisionValid);
  74. //SubscribeLocalEvent<SurgeryLarvaConditionComponent, SurgeryValidEvent>(OnLarvaValid);
  75. SubscribeLocalEvent<SurgeryHasBodyConditionComponent, SurgeryValidEvent>(OnHasBodyConditionValid);
  76. SubscribeLocalEvent<SurgeryPartConditionComponent, SurgeryValidEvent>(OnPartConditionValid);
  77. SubscribeLocalEvent<SurgeryOrganConditionComponent, SurgeryValidEvent>(OnOrganConditionValid);
  78. SubscribeLocalEvent<SurgeryWoundedConditionComponent, SurgeryValidEvent>(OnWoundedValid);
  79. SubscribeLocalEvent<SurgeryPartRemovedConditionComponent, SurgeryValidEvent>(OnPartRemovedConditionValid);
  80. SubscribeLocalEvent<SurgeryBodyConditionComponent, SurgeryValidEvent>(OnBodyConditionValid);
  81. SubscribeLocalEvent<SurgeryOrganSlotConditionComponent, SurgeryValidEvent>(OnOrganSlotConditionValid);
  82. SubscribeLocalEvent<SurgeryPartPresentConditionComponent, SurgeryValidEvent>(OnPartPresentConditionValid);
  83. SubscribeLocalEvent<SurgeryMarkingConditionComponent, SurgeryValidEvent>(OnMarkingPresentValid);
  84. SubscribeLocalEvent<SurgeryBodyComponentConditionComponent, SurgeryValidEvent>(OnBodyComponentConditionValid);
  85. SubscribeLocalEvent<SurgeryPartComponentConditionComponent, SurgeryValidEvent>(OnPartComponentConditionValid);
  86. SubscribeLocalEvent<SurgeryOrganOnAddConditionComponent, SurgeryValidEvent>(OnOrganOnAddConditionValid);
  87. //SubscribeLocalEvent<SurgeryRemoveLarvaComponent, SurgeryCompletedEvent>(OnRemoveLarva);
  88. SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnPrototypesReloaded);
  89. InitializeSteps();
  90. LoadPrototypes();
  91. }
  92. private void OnRoundRestartCleanup(RoundRestartCleanupEvent ev)
  93. {
  94. _surgeries.Clear();
  95. }
  96. private void OnMapInit(Entity<SurgeryTargetComponent> ent, ref MapInitEvent args)
  97. {
  98. var data = new InterfaceData("SurgeryBui");
  99. _ui.SetUi(ent.Owner, SurgeryUIKey.Key, data);
  100. }
  101. private void OnTargetDoAfter(Entity<SurgeryTargetComponent> ent, ref SurgeryDoAfterEvent args)
  102. {
  103. if (!_timing.IsFirstTimePredicted)
  104. return;
  105. if (args.Cancelled)
  106. {
  107. var failEv = new SurgeryStepFailedEvent(args.User, ent, args.Surgery, args.Step);
  108. RaiseLocalEvent(args.User, ref failEv);
  109. return;
  110. }
  111. if (args.Handled
  112. || args.Target is not { } target
  113. || !IsSurgeryValid(ent, target, args.Surgery, args.Step, args.User, out var surgery, out var part, out var step)
  114. || !PreviousStepsComplete(ent, part, surgery, args.Step)
  115. || !CanPerformStep(args.User, ent, part, step, false))
  116. {
  117. Log.Warning($"{ToPrettyString(args.User)} tried to start invalid surgery.");
  118. return;
  119. }
  120. var complete = IsStepComplete(ent, part, args.Step, surgery);
  121. args.Repeat = HasComp<SurgeryRepeatableStepComponent>(step) && !complete;
  122. var ev = new SurgeryStepEvent(args.User, ent, part, GetTools(args.User), surgery, step, complete);
  123. RaiseLocalEvent(step, ref ev);
  124. RaiseLocalEvent(args.User, ref ev);
  125. RefreshUI(ent);
  126. }
  127. private void OnCloseIncisionValid(Entity<SurgeryCloseIncisionConditionComponent> ent, ref SurgeryValidEvent args)
  128. {
  129. if (!HasComp<IncisionOpenComponent>(args.Part) ||
  130. !HasComp<BleedersClampedComponent>(args.Part) ||
  131. !HasComp<SkinRetractedComponent>(args.Part) ||
  132. !HasComp<BodyPartReattachedComponent>(args.Part) ||
  133. !HasComp<InternalBleedersClampedComponent>(args.Part))
  134. {
  135. args.Cancelled = true;
  136. }
  137. }
  138. private void OnWoundedValid(Entity<SurgeryWoundedConditionComponent> ent, ref SurgeryValidEvent args)
  139. {
  140. if (!TryComp(args.Body, out DamageableComponent? damageable)
  141. || !TryComp(args.Part, out DamageableComponent? partDamageable)
  142. || damageable.TotalDamage <= 0
  143. && partDamageable.TotalDamage <= 0
  144. && !HasComp<IncisionOpenComponent>(args.Part))
  145. args.Cancelled = true;
  146. }
  147. /*private void OnLarvaValid(Entity<SurgeryLarvaConditionComponent> ent, ref SurgeryValidEvent args)
  148. {
  149. if (!TryComp(args.Body, out VictimInfectedComponent? infected))
  150. args.Cancelled = true;
  151. // The larva has fully developed and surgery is now impossible
  152. if (infected != null && infected.SpawnedLarva != null)
  153. args.Cancelled = true;
  154. }*/
  155. private void OnBodyComponentConditionValid(Entity<SurgeryBodyComponentConditionComponent> ent, ref SurgeryValidEvent args)
  156. {
  157. var present = true;
  158. foreach (var reg in ent.Comp.Components.Values)
  159. {
  160. var compType = reg.Component.GetType();
  161. if (!HasComp(args.Body, compType))
  162. present = false;
  163. }
  164. if (ent.Comp.Inverse ? present : !present)
  165. args.Cancelled = true;
  166. }
  167. private void OnPartComponentConditionValid(Entity<SurgeryPartComponentConditionComponent> ent, ref SurgeryValidEvent args)
  168. {
  169. var present = true;
  170. foreach (var reg in ent.Comp.Components.Values)
  171. {
  172. var compType = reg.Component.GetType();
  173. if (!HasComp(args.Part, compType))
  174. present = false;
  175. }
  176. if (ent.Comp.Inverse ? present : !present)
  177. args.Cancelled = true;
  178. }
  179. // This is literally a duplicate of the checks in OnToolCheck for SurgeryStepComponent.AddOrganOnAdd
  180. private void OnOrganOnAddConditionValid(Entity<SurgeryOrganOnAddConditionComponent> ent, ref SurgeryValidEvent args)
  181. {
  182. if (!TryComp<BodyPartComponent>(args.Part, out var part)
  183. || part.Body != args.Body)
  184. {
  185. args.Cancelled = true;
  186. return;
  187. }
  188. var organSlotIdToOrgan = _body.GetPartOrgans(args.Part, part).ToDictionary(o => o.Item2.SlotId, o => o.Item2);
  189. var allOnAddFound = true;
  190. var zeroOnAddFound = true;
  191. foreach (var (organSlotId, components) in ent.Comp.Components)
  192. {
  193. if (!organSlotIdToOrgan.TryGetValue(organSlotId, out var organ))
  194. continue;
  195. if (organ.OnAdd == null)
  196. {
  197. allOnAddFound = false;
  198. continue;
  199. }
  200. foreach (var key in components.Keys)
  201. {
  202. if (!organ.OnAdd.ContainsKey(key))
  203. allOnAddFound = false;
  204. else
  205. zeroOnAddFound = false;
  206. }
  207. }
  208. if (ent.Comp.Inverse ? allOnAddFound : zeroOnAddFound)
  209. args.Cancelled = true;
  210. }
  211. private void OnHasBodyConditionValid(Entity<SurgeryHasBodyConditionComponent> ent, ref SurgeryValidEvent args)
  212. {
  213. if (CompOrNull<BodyPartComponent>(args.Part)?.Body == null)
  214. args.Cancelled = true;
  215. }
  216. private void OnPartConditionValid(Entity<SurgeryPartConditionComponent> ent, ref SurgeryValidEvent args)
  217. {
  218. if (!TryComp<BodyPartComponent>(args.Part, out var part))
  219. {
  220. args.Cancelled = true;
  221. return;
  222. }
  223. var typeMatch = part.PartType == ent.Comp.Part;
  224. var symmetryMatch = ent.Comp.Symmetry == null || part.Symmetry == ent.Comp.Symmetry;
  225. var valid = typeMatch && symmetryMatch;
  226. if (ent.Comp.Inverse ? valid : !valid)
  227. args.Cancelled = true;
  228. }
  229. private void OnOrganConditionValid(Entity<SurgeryOrganConditionComponent> ent, ref SurgeryValidEvent args)
  230. {
  231. if (!TryComp<BodyPartComponent>(args.Part, out var partComp)
  232. || partComp.Body != args.Body
  233. || ent.Comp.Organ == null)
  234. {
  235. args.Cancelled = true;
  236. return;
  237. }
  238. foreach (var reg in ent.Comp.Organ.Values)
  239. {
  240. if (_body.TryGetBodyPartOrgans(args.Part, reg.Component.GetType(), out var organs)
  241. && organs.Count > 0)
  242. {
  243. if (ent.Comp.Inverse
  244. && (!ent.Comp.Reattaching
  245. || ent.Comp.Reattaching
  246. && !organs.Any(organ => HasComp<OrganReattachedComponent>(organ.Id))))
  247. args.Cancelled = true;
  248. }
  249. else if (!ent.Comp.Inverse)
  250. args.Cancelled = true;
  251. }
  252. }
  253. private void OnBodyConditionValid(Entity<SurgeryBodyConditionComponent> ent, ref SurgeryValidEvent args)
  254. {
  255. if (TryComp<BodyComponent>(args.Body, out var body) && body.Prototype is {} bodyId)
  256. args.Cancelled |= ent.Comp.Accepted.Contains(bodyId) ^ !ent.Comp.Inverse;
  257. }
  258. private void OnOrganSlotConditionValid(Entity<SurgeryOrganSlotConditionComponent> ent, ref SurgeryValidEvent args)
  259. {
  260. args.Cancelled |= _body.CanInsertOrgan(args.Part, ent.Comp.OrganSlot) ^ !ent.Comp.Inverse;
  261. }
  262. private void OnPartRemovedConditionValid(Entity<SurgeryPartRemovedConditionComponent> ent, ref SurgeryValidEvent args)
  263. {
  264. if (!_body.CanAttachToSlot(args.Part, ent.Comp.Connection))
  265. {
  266. args.Cancelled = true;
  267. return;
  268. }
  269. var results = _body.GetBodyChildrenOfType(args.Body, ent.Comp.Part, symmetry: ent.Comp.Symmetry).ToList();
  270. if (results is not { } || !results.Any())
  271. return;
  272. if (!results.Any(part => HasComp<BodyPartReattachedComponent>(part.Id)))
  273. args.Cancelled = true;
  274. }
  275. private void OnPartPresentConditionValid(Entity<SurgeryPartPresentConditionComponent> ent, ref SurgeryValidEvent args)
  276. {
  277. if (args.Part == EntityUid.Invalid
  278. || !HasComp<BodyPartComponent>(args.Part))
  279. args.Cancelled = true;
  280. }
  281. private void OnMarkingPresentValid(Entity<SurgeryMarkingConditionComponent> ent, ref SurgeryValidEvent args)
  282. {
  283. var markingCategory = MarkingCategoriesConversion.FromHumanoidVisualLayers(ent.Comp.MarkingCategory);
  284. var hasMarking = TryComp(args.Body, out HumanoidAppearanceComponent? bodyAppearance)
  285. && bodyAppearance.MarkingSet.Markings.TryGetValue(markingCategory, out var markingList)
  286. && markingList.Any(marking => marking.MarkingId.Contains(ent.Comp.MatchString));
  287. if ((!ent.Comp.Inverse && hasMarking) || (ent.Comp.Inverse && !hasMarking))
  288. args.Cancelled = true;
  289. }
  290. /*private void OnRemoveLarva(Entity<SurgeryRemoveLarvaComponent> ent, ref SurgeryCompletedEvent args)
  291. {
  292. RemCompDeferred<VictimInfectedComponent>(ent);
  293. }*/
  294. protected bool IsSurgeryValid(EntityUid body, EntityUid targetPart, EntProtoId surgery, EntProtoId stepId,
  295. EntityUid user, out Entity<SurgeryComponent> surgeryEnt, out EntityUid part, out EntityUid step)
  296. {
  297. surgeryEnt = default;
  298. part = default;
  299. step = default;
  300. if (!HasComp<SurgeryTargetComponent>(body) ||
  301. !IsLyingDown(body, user) ||
  302. GetSingleton(surgery) is not { } surgeryEntId ||
  303. !TryComp(surgeryEntId, out SurgeryComponent? surgeryComp) ||
  304. !surgeryComp.Steps.Contains(stepId) ||
  305. GetSingleton(stepId) is not { } stepEnt
  306. || !HasComp<BodyPartComponent>(targetPart)
  307. && !HasComp<BodyComponent>(targetPart))
  308. return false;
  309. var ev = new SurgeryValidEvent(body, targetPart);
  310. if (_timing.IsFirstTimePredicted)
  311. {
  312. RaiseLocalEvent(stepEnt, ref ev);
  313. RaiseLocalEvent(surgeryEntId, ref ev);
  314. }
  315. if (ev.Cancelled)
  316. return false;
  317. surgeryEnt = (surgeryEntId, surgeryComp);
  318. part = targetPart;
  319. step = stepEnt;
  320. return true;
  321. }
  322. public EntityUid? GetSingleton(EntProtoId surgeryOrStep)
  323. {
  324. if (!_prototypes.HasIndex(surgeryOrStep))
  325. return null;
  326. // This (for now) assumes that surgery entity data remains unchanged between client
  327. // and server
  328. // if it does not you get the bullet
  329. if (!_surgeries.TryGetValue(surgeryOrStep, out var ent) || TerminatingOrDeleted(ent))
  330. {
  331. ent = Spawn(surgeryOrStep, MapCoordinates.Nullspace);
  332. _surgeries[surgeryOrStep] = ent;
  333. }
  334. return ent;
  335. }
  336. private List<EntityUid> GetTools(EntityUid surgeon)
  337. {
  338. return _hands.EnumerateHeld(surgeon).ToList();
  339. }
  340. public bool IsLyingDown(EntityUid entity, EntityUid user)
  341. {
  342. if (_standing.IsDown(entity))
  343. return true;
  344. if (TryComp(entity, out BuckleComponent? buckle) &&
  345. TryComp(buckle.BuckledTo, out StrapComponent? strap))
  346. {
  347. var rotation = strap.Rotation;
  348. if (rotation.GetCardinalDir() is Direction.West or Direction.East)
  349. return true;
  350. }
  351. _popup.PopupEntity(Loc.GetString("surgery-error-laying"), user, user);
  352. return false;
  353. }
  354. protected virtual void RefreshUI(EntityUid body)
  355. {
  356. }
  357. private void OnPrototypesReloaded(PrototypesReloadedEventArgs args)
  358. {
  359. if (!args.WasModified<EntityPrototype>())
  360. return;
  361. LoadPrototypes();
  362. }
  363. private void LoadPrototypes()
  364. {
  365. // Cache is probably invalid so delete it
  366. foreach (var uid in _surgeries.Values)
  367. {
  368. Del(uid);
  369. }
  370. _surgeries.Clear();
  371. _allSurgeries.Clear();
  372. foreach (var entity in _prototypes.EnumeratePrototypes<EntityPrototype>())
  373. if (entity.HasComponent<SurgeryComponent>())
  374. _allSurgeries.Add(new EntProtoId(entity.ID));
  375. }
  376. }