MetabolizerSystem.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. using Content.Server.Body.Components;
  2. using Content.Shared.Chemistry.EntitySystems;
  3. using Content.Shared.Administration.Logs;
  4. using Content.Shared.Body.Organ;
  5. using Content.Shared.Chemistry.Components;
  6. using Content.Shared.Chemistry.Components.SolutionManager;
  7. using Content.Shared.Chemistry.Reagent;
  8. using Content.Shared.Database;
  9. using Content.Shared.EntityEffects;
  10. using Content.Shared.FixedPoint;
  11. using Content.Shared.Mobs.Components;
  12. using Content.Shared.Mobs.Systems;
  13. using Robust.Shared.Collections;
  14. using Robust.Shared.Prototypes;
  15. using Robust.Shared.Random;
  16. using Robust.Shared.Timing;
  17. using Content.Shared.Bed.Sleep; // Shitmed Change
  18. namespace Content.Server.Body.Systems
  19. {
  20. public sealed class MetabolizerSystem : EntitySystem
  21. {
  22. [Dependency] private readonly IGameTiming _gameTiming = default!;
  23. [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
  24. [Dependency] private readonly IRobustRandom _random = default!;
  25. [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
  26. [Dependency] private readonly MobStateSystem _mobStateSystem = default!;
  27. [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
  28. private EntityQuery<OrganComponent> _organQuery;
  29. private EntityQuery<SolutionContainerManagerComponent> _solutionQuery;
  30. public override void Initialize()
  31. {
  32. base.Initialize();
  33. _organQuery = GetEntityQuery<OrganComponent>();
  34. _solutionQuery = GetEntityQuery<SolutionContainerManagerComponent>();
  35. SubscribeLocalEvent<MetabolizerComponent, ComponentInit>(OnMetabolizerInit);
  36. SubscribeLocalEvent<MetabolizerComponent, MapInitEvent>(OnMapInit);
  37. SubscribeLocalEvent<MetabolizerComponent, EntityUnpausedEvent>(OnUnpaused);
  38. SubscribeLocalEvent<MetabolizerComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
  39. }
  40. private void OnMapInit(Entity<MetabolizerComponent> ent, ref MapInitEvent args)
  41. {
  42. ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.UpdateInterval;
  43. }
  44. private void OnUnpaused(Entity<MetabolizerComponent> ent, ref EntityUnpausedEvent args)
  45. {
  46. ent.Comp.NextUpdate += args.PausedTime;
  47. }
  48. private void OnMetabolizerInit(Entity<MetabolizerComponent> entity, ref ComponentInit args)
  49. {
  50. if (!entity.Comp.SolutionOnBody)
  51. {
  52. _solutionContainerSystem.EnsureSolution(entity.Owner, entity.Comp.SolutionName, out _);
  53. }
  54. else if (_organQuery.CompOrNull(entity)?.Body is { } body)
  55. {
  56. _solutionContainerSystem.EnsureSolution(body, entity.Comp.SolutionName, out _);
  57. }
  58. }
  59. private void OnApplyMetabolicMultiplier(
  60. Entity<MetabolizerComponent> ent,
  61. ref ApplyMetabolicMultiplierEvent args)
  62. {
  63. // TODO REFACTOR THIS
  64. // This will slowly drift over time due to floating point errors.
  65. // Instead, raise an event with the base rates and allow modifiers to get applied to it.
  66. if (args.Apply)
  67. {
  68. ent.Comp.UpdateInterval *= args.Multiplier;
  69. return;
  70. }
  71. ent.Comp.UpdateInterval /= args.Multiplier;
  72. }
  73. public override void Update(float frameTime)
  74. {
  75. base.Update(frameTime);
  76. var metabolizers = new ValueList<(EntityUid Uid, MetabolizerComponent Component)>(Count<MetabolizerComponent>());
  77. var query = EntityQueryEnumerator<MetabolizerComponent>();
  78. while (query.MoveNext(out var uid, out var comp))
  79. {
  80. metabolizers.Add((uid, comp));
  81. }
  82. foreach (var (uid, metab) in metabolizers)
  83. {
  84. // Only update as frequently as it should
  85. if (_gameTiming.CurTime < metab.NextUpdate)
  86. continue;
  87. metab.NextUpdate += metab.UpdateInterval;
  88. TryMetabolize((uid, metab));
  89. }
  90. }
  91. private void TryMetabolize(Entity<MetabolizerComponent, OrganComponent?, SolutionContainerManagerComponent?> ent)
  92. {
  93. _organQuery.Resolve(ent, ref ent.Comp2, logMissing: false);
  94. // First step is get the solution we actually care about
  95. var solutionName = ent.Comp1.SolutionName;
  96. Solution? solution = null;
  97. Entity<SolutionComponent>? soln = default!;
  98. EntityUid? solutionEntityUid = null;
  99. if (ent.Comp1.SolutionOnBody)
  100. {
  101. if (ent.Comp2?.Body is { } body)
  102. {
  103. if (!_solutionQuery.Resolve(body, ref ent.Comp3, logMissing: false))
  104. return;
  105. _solutionContainerSystem.TryGetSolution((body, ent.Comp3), solutionName, out soln, out solution);
  106. solutionEntityUid = body;
  107. }
  108. }
  109. else
  110. {
  111. if (!_solutionQuery.Resolve(ent, ref ent.Comp3, logMissing: false))
  112. return;
  113. _solutionContainerSystem.TryGetSolution((ent, ent), solutionName, out soln, out solution);
  114. solutionEntityUid = ent;
  115. }
  116. if (solutionEntityUid is null
  117. || soln is null
  118. || solution is null
  119. || solution.Contents.Count == 0)
  120. {
  121. return;
  122. }
  123. // randomize the reagent list so we don't have any weird quirks
  124. // like alphabetical order or insertion order mattering for processing
  125. var list = solution.Contents.ToArray();
  126. _random.Shuffle(list);
  127. int reagents = 0;
  128. foreach (var (reagent, quantity) in list)
  129. {
  130. if (!_prototypeManager.TryIndex<ReagentPrototype>(reagent.Prototype, out var proto))
  131. continue;
  132. var mostToRemove = FixedPoint2.Zero;
  133. if (proto.Metabolisms is null)
  134. {
  135. if (ent.Comp1.RemoveEmpty)
  136. {
  137. solution.RemoveReagent(reagent, FixedPoint2.New(1));
  138. }
  139. continue;
  140. }
  141. // we're done here entirely if this is true
  142. if (reagents >= ent.Comp1.MaxReagentsProcessable)
  143. return;
  144. // loop over all our groups and see which ones apply
  145. if (ent.Comp1.MetabolismGroups is null)
  146. continue;
  147. foreach (var group in ent.Comp1.MetabolismGroups)
  148. {
  149. if (!proto.Metabolisms.TryGetValue(group.Id, out var entry))
  150. continue;
  151. var rate = entry.MetabolismRate * group.MetabolismRateModifier;
  152. // Remove $rate, as long as there's enough reagent there to actually remove that much
  153. mostToRemove = FixedPoint2.Clamp(rate, 0, quantity);
  154. float scale = (float)mostToRemove / (float)rate;
  155. // if it's possible for them to be dead, and they are,
  156. // then we shouldn't process any effects, but should probably
  157. // still remove reagents
  158. if (TryComp<MobStateComponent>(solutionEntityUid.Value, out var state))
  159. {
  160. // Shitmed Change Start
  161. if (!proto.WorksOnTheDead && _mobStateSystem.IsDead(solutionEntityUid.Value, state))
  162. continue;
  163. if (proto.WorksOnUnconscious == true &&
  164. (_mobStateSystem.IsCritical(solutionEntityUid.Value, state) ||
  165. HasComp<SleepingComponent>(solutionEntityUid.Value)))
  166. continue;
  167. // Shitmed Change End
  168. }
  169. var actualEntity = ent.Comp2?.Body ?? solutionEntityUid.Value;
  170. var args = new EntityEffectReagentArgs(actualEntity, EntityManager, ent, solution, mostToRemove, proto, null, scale);
  171. // do all effects, if conditions apply
  172. foreach (var effect in entry.Effects)
  173. {
  174. if (!effect.ShouldApply(args, _random))
  175. continue;
  176. if (effect.ShouldLog)
  177. {
  178. _adminLogger.Add(
  179. LogType.ReagentEffect,
  180. effect.LogImpact,
  181. $"Metabolism effect {effect.GetType().Name:effect}"
  182. + $" of reagent {proto.LocalizedName:reagent}"
  183. + $" applied on entity {actualEntity:entity}"
  184. + $" at {Transform(actualEntity).Coordinates:coordinates}"
  185. );
  186. }
  187. effect.Effect(args);
  188. }
  189. }
  190. // remove a certain amount of reagent
  191. if (mostToRemove > FixedPoint2.Zero)
  192. {
  193. solution.RemoveReagent(reagent, mostToRemove);
  194. // We have processed a reagant, so count it towards the cap
  195. reagents += 1;
  196. }
  197. }
  198. _solutionContainerSystem.UpdateChemicals(soln.Value);
  199. }
  200. }
  201. // TODO REFACTOR THIS
  202. // This will cause rates to slowly drift over time due to floating point errors.
  203. // Instead, the system that raised this should trigger an update and subscribe to get-modifier events.
  204. [ByRefEvent]
  205. public readonly record struct ApplyMetabolicMultiplierEvent(
  206. EntityUid Uid,
  207. float Multiplier,
  208. bool Apply)
  209. {
  210. /// <summary>
  211. /// The entity whose metabolism is being modified.
  212. /// </summary>
  213. public readonly EntityUid Uid = Uid;
  214. /// <summary>
  215. /// What the metabolism's update rate will be multiplied by.
  216. /// </summary>
  217. public readonly float Multiplier = Multiplier;
  218. /// <summary>
  219. /// If true, apply the multiplier. If false, revert it.
  220. /// </summary>
  221. public readonly bool Apply = Apply;
  222. }
  223. }