HealingSystem.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. using Content.Server.Administration.Logs;
  2. using Content.Server.Body.Components;
  3. using Content.Server.Body.Systems;
  4. using Content.Server.Medical.Components;
  5. using Content.Server.Popups;
  6. using Content.Server.Stack;
  7. using Content.Shared.Chemistry.EntitySystems;
  8. using Content.Shared.Audio;
  9. using Content.Shared.Damage;
  10. using Content.Shared.Database;
  11. using Content.Shared.DoAfter;
  12. using Content.Shared.FixedPoint;
  13. using Content.Shared.IdentityManagement;
  14. using Content.Shared.Interaction;
  15. using Content.Shared.Interaction.Events;
  16. using Content.Shared.Medical;
  17. using Content.Shared.Mobs;
  18. using Content.Shared.Mobs.Components;
  19. using Content.Shared.Mobs.Systems;
  20. using Content.Shared.Popups;
  21. using Content.Shared.Stacks;
  22. using Robust.Shared.Audio.Systems;
  23. using Robust.Shared.Random;
  24. using Content.Shared.Body.Systems; // Shitmed Change
  25. using Content.Shared._Shitmed.Targeting; // Shitmed Change
  26. namespace Content.Server.Medical;
  27. public sealed class HealingSystem : EntitySystem
  28. {
  29. [Dependency] private readonly SharedAudioSystem _audio = default!;
  30. [Dependency] private readonly IAdminLogManager _adminLogger = default!;
  31. [Dependency] private readonly DamageableSystem _damageable = default!;
  32. [Dependency] private readonly SharedTargetingSystem _targetingSystem = default!; // Shitmed Change
  33. [Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!;
  34. [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
  35. [Dependency] private readonly IRobustRandom _random = default!;
  36. [Dependency] private readonly StackSystem _stacks = default!;
  37. [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
  38. [Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!;
  39. [Dependency] private readonly PopupSystem _popupSystem = default!;
  40. [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
  41. [Dependency] private readonly SharedBodySystem _bodySystem = default!; // Shitmed Change
  42. public override void Initialize()
  43. {
  44. base.Initialize();
  45. SubscribeLocalEvent<HealingComponent, UseInHandEvent>(OnHealingUse);
  46. SubscribeLocalEvent<HealingComponent, AfterInteractEvent>(OnHealingAfterInteract);
  47. SubscribeLocalEvent<DamageableComponent, HealingDoAfterEvent>(OnDoAfter);
  48. }
  49. private void OnDoAfter(Entity<DamageableComponent> entity, ref HealingDoAfterEvent args)
  50. {
  51. var dontRepeat = false;
  52. if (!TryComp(args.Used, out HealingComponent? healing))
  53. return;
  54. if (args.Handled || args.Cancelled)
  55. return;
  56. if (healing.DamageContainers is not null &&
  57. entity.Comp.DamageContainerID is not null &&
  58. !healing.DamageContainers.Contains(entity.Comp.DamageContainerID))
  59. {
  60. return;
  61. }
  62. // Heal some bloodloss damage.
  63. if (healing.BloodlossModifier != 0)
  64. {
  65. if (!TryComp<BloodstreamComponent>(entity, out var bloodstream))
  66. return;
  67. var isBleeding = bloodstream.BleedAmount > 0;
  68. _bloodstreamSystem.TryModifyBleedAmount(entity.Owner, healing.BloodlossModifier);
  69. if (isBleeding != bloodstream.BleedAmount > 0)
  70. {
  71. var popup = (args.User == entity.Owner)
  72. ? Loc.GetString("medical-item-stop-bleeding-self")
  73. : Loc.GetString("medical-item-stop-bleeding", ("target", Identity.Entity(entity.Owner, EntityManager)));
  74. _popupSystem.PopupEntity(popup, entity, args.User);
  75. }
  76. }
  77. // Restores missing blood
  78. if (healing.ModifyBloodLevel != 0)
  79. _bloodstreamSystem.TryModifyBloodLevel(entity.Owner, healing.ModifyBloodLevel);
  80. var healed = _damageable.TryChangeDamage(entity.Owner, healing.Damage * _damageable.UniversalTopicalsHealModifier, true, origin: args.User, canSever: false); // Shitmed Change
  81. if (healed == null && healing.BloodlossModifier != 0)
  82. return;
  83. var total = healed?.GetTotal() ?? FixedPoint2.Zero;
  84. // Re-verify that we can heal the damage.
  85. if (TryComp<StackComponent>(args.Used.Value, out var stackComp))
  86. {
  87. _stacks.Use(args.Used.Value, 1, stackComp);
  88. if (_stacks.GetCount(args.Used.Value, stackComp) <= 0)
  89. dontRepeat = true;
  90. }
  91. else
  92. {
  93. QueueDel(args.Used.Value);
  94. }
  95. if (entity.Owner != args.User)
  96. {
  97. _adminLogger.Add(LogType.Healed,
  98. $"{EntityManager.ToPrettyString(args.User):user} healed {EntityManager.ToPrettyString(entity.Owner):target} for {total:damage} damage");
  99. }
  100. else
  101. {
  102. _adminLogger.Add(LogType.Healed,
  103. $"{EntityManager.ToPrettyString(args.User):user} healed themselves for {total:damage} damage");
  104. }
  105. _audio.PlayPvs(healing.HealingEndSound, entity.Owner, AudioHelpers.WithVariation(0.125f, _random).WithVolume(1f));
  106. // Logic to determine the whether or not to repeat the healing action
  107. args.Repeat = (HasDamage(entity, healing) || IsPartDamaged(args.User, entity)) && !dontRepeat; // Shitmed change
  108. if (!args.Repeat && !dontRepeat)
  109. _popupSystem.PopupEntity(Loc.GetString("medical-item-finished-using", ("item", args.Used)), entity.Owner, args.User);
  110. args.Handled = true;
  111. }
  112. private bool HasDamage(Entity<DamageableComponent> ent, HealingComponent healing)
  113. {
  114. var damageableDict = ent.Comp.Damage.DamageDict;
  115. var healingDict = healing.Damage.DamageDict;
  116. foreach (var type in healingDict)
  117. {
  118. if (damageableDict[type.Key].Value > 0)
  119. {
  120. return true;
  121. }
  122. }
  123. if (TryComp<BloodstreamComponent>(ent, out var bloodstream))
  124. {
  125. // Is ent missing blood that we can restore?
  126. if (healing.ModifyBloodLevel > 0
  127. && _solutionContainerSystem.ResolveSolution(ent.Owner, bloodstream.BloodSolutionName, ref bloodstream.BloodSolution, out var bloodSolution)
  128. && bloodSolution.Volume < bloodSolution.MaxVolume)
  129. {
  130. return true;
  131. }
  132. // Is ent bleeding and can we stop it?
  133. if (healing.BloodlossModifier < 0 && bloodstream.BleedAmount > 0)
  134. {
  135. return true;
  136. }
  137. }
  138. return false;
  139. }
  140. // Shitmed Change Start
  141. private bool IsPartDamaged(EntityUid user, EntityUid target)
  142. {
  143. if (!TryComp(user, out TargetingComponent? targeting))
  144. return false;
  145. var (targetType, targetSymmetry) = _bodySystem.ConvertTargetBodyPart(targeting.Target);
  146. foreach (var part in _bodySystem.GetBodyChildrenOfType(target, targetType, symmetry: targetSymmetry))
  147. if (TryComp<DamageableComponent>(part.Id, out var damageable)
  148. && damageable.TotalDamage > part.Component.MinIntegrity)
  149. return true;
  150. return false;
  151. }
  152. // Shitmed Change End
  153. private void OnHealingUse(Entity<HealingComponent> entity, ref UseInHandEvent args)
  154. {
  155. if (args.Handled)
  156. return;
  157. if (TryHeal(entity, args.User, args.User, entity.Comp))
  158. args.Handled = true;
  159. }
  160. private void OnHealingAfterInteract(Entity<HealingComponent> entity, ref AfterInteractEvent args)
  161. {
  162. if (args.Handled || !args.CanReach || args.Target == null)
  163. return;
  164. if (TryHeal(entity, args.User, args.Target.Value, entity.Comp))
  165. args.Handled = true;
  166. }
  167. private bool TryHeal(EntityUid uid, EntityUid user, EntityUid target, HealingComponent component)
  168. {
  169. if (!TryComp<DamageableComponent>(target, out var targetDamage))
  170. return false;
  171. if (component.DamageContainers is not null &&
  172. targetDamage.DamageContainerID is not null &&
  173. !component.DamageContainers.Contains(targetDamage.DamageContainerID))
  174. {
  175. return false;
  176. }
  177. if (user != target && !_interactionSystem.InRangeUnobstructed(user, target, popup: true))
  178. return false;
  179. if (TryComp<StackComponent>(uid, out var stack) && stack.Count < 1)
  180. return false;
  181. if (!HasDamage((target, targetDamage), component) && !IsPartDamaged(user, target)) // Shitmed Change
  182. {
  183. _popupSystem.PopupEntity(Loc.GetString("medical-item-cant-use", ("item", uid)), uid, user);
  184. return false;
  185. }
  186. _audio.PlayPvs(component.HealingBeginSound, uid,
  187. AudioHelpers.WithVariation(0.125f, _random).WithVolume(1f));
  188. var isNotSelf = user != target;
  189. if (isNotSelf)
  190. {
  191. var msg = Loc.GetString("medical-item-popup-target", ("user", Identity.Entity(user, EntityManager)), ("item", uid));
  192. _popupSystem.PopupEntity(msg, target, target, PopupType.Medium);
  193. }
  194. var delay = isNotSelf
  195. ? component.Delay
  196. : component.Delay * GetScaledHealingPenalty(user, component);
  197. var doAfterEventArgs =
  198. new DoAfterArgs(EntityManager, user, delay, new HealingDoAfterEvent(), target, target: target, used: uid)
  199. {
  200. // Didn't break on damage as they may be trying to prevent it and
  201. // not being able to heal your own ticking damage would be frustrating.
  202. NeedHand = true,
  203. BreakOnMove = true,
  204. BreakOnWeightlessMove = false,
  205. };
  206. _doAfter.TryStartDoAfter(doAfterEventArgs);
  207. return true;
  208. }
  209. /// <summary>
  210. /// Scales the self-heal penalty based on the amount of damage taken
  211. /// </summary>
  212. /// <param name="uid"></param>
  213. /// <param name="component"></param>
  214. /// <returns></returns>
  215. public float GetScaledHealingPenalty(EntityUid uid, HealingComponent component)
  216. {
  217. var output = component.Delay;
  218. if (!TryComp<MobThresholdsComponent>(uid, out var mobThreshold) ||
  219. !TryComp<DamageableComponent>(uid, out var damageable))
  220. return output;
  221. if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out var amount, mobThreshold))
  222. return 1;
  223. var percentDamage = (float)(damageable.TotalDamage / amount);
  224. //basically make it scale from 1 to the multiplier.
  225. var modifier = percentDamage * (component.SelfHealPenaltyMultiplier - 1) + 1;
  226. return Math.Max(modifier, 1);
  227. }
  228. }