1
0

GeneratorSystem.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. using System.Linq;
  2. using Content.Server.Audio;
  3. using Content.Server.Fluids.EntitySystems;
  4. using Content.Server.Materials;
  5. using Content.Server.Popups;
  6. using Content.Server.Power.Components;
  7. using Content.Server.Power.EntitySystems;
  8. using Content.Shared.Chemistry.EntitySystems;
  9. using Content.Shared.FixedPoint;
  10. using Content.Shared.Popups;
  11. using Content.Shared.Power.Generator;
  12. using Robust.Server.GameObjects;
  13. namespace Content.Server.Power.Generator;
  14. /// <inheritdoc/>
  15. /// <seealso cref="FuelGeneratorComponent"/>
  16. /// <seealso cref="ChemicalFuelGeneratorAdapterComponent"/>
  17. /// <seealso cref="SolidFuelGeneratorAdapterComponent"/>
  18. public sealed class GeneratorSystem : SharedGeneratorSystem
  19. {
  20. [Dependency] private readonly AppearanceSystem _appearance = default!;
  21. [Dependency] private readonly AmbientSoundSystem _ambientSound = default!;
  22. [Dependency] private readonly MaterialStorageSystem _materialStorage = default!;
  23. [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
  24. [Dependency] private readonly PopupSystem _popup = default!;
  25. [Dependency] private readonly PuddleSystem _puddle = default!;
  26. public override void Initialize()
  27. {
  28. UpdatesBefore.Add(typeof(PowerNetSystem));
  29. SubscribeLocalEvent<FuelGeneratorComponent, PortableGeneratorSetTargetPowerMessage>(OnTargetPowerSet);
  30. SubscribeLocalEvent<FuelGeneratorComponent, PortableGeneratorEjectFuelMessage>(OnEjectFuel);
  31. SubscribeLocalEvent<FuelGeneratorComponent, AnchorStateChangedEvent>(OnAnchorStateChanged);
  32. SubscribeLocalEvent<SolidFuelGeneratorAdapterComponent, GeneratorGetFuelEvent>(SolidGetFuel);
  33. SubscribeLocalEvent<SolidFuelGeneratorAdapterComponent, GeneratorUseFuel>(SolidUseFuel);
  34. SubscribeLocalEvent<SolidFuelGeneratorAdapterComponent, GeneratorEmpty>(SolidEmpty);
  35. SubscribeLocalEvent<ChemicalFuelGeneratorAdapterComponent, GeneratorGetFuelEvent>(ChemicalGetFuel);
  36. SubscribeLocalEvent<ChemicalFuelGeneratorAdapterComponent, GeneratorUseFuel>(ChemicalUseFuel);
  37. SubscribeLocalEvent<ChemicalFuelGeneratorAdapterComponent, GeneratorGetCloggedEvent>(ChemicalGetClogged);
  38. SubscribeLocalEvent<ChemicalFuelGeneratorAdapterComponent, GeneratorEmpty>(ChemicalEmpty);
  39. }
  40. private void OnAnchorStateChanged(EntityUid uid, FuelGeneratorComponent component, ref AnchorStateChangedEvent args)
  41. {
  42. // Turn off generator if unanchored while running.
  43. if (!component.On)
  44. return;
  45. SetFuelGeneratorOn(uid, false, component);
  46. }
  47. private void OnEjectFuel(EntityUid uid, FuelGeneratorComponent component, PortableGeneratorEjectFuelMessage args)
  48. {
  49. EmptyGenerator(uid);
  50. }
  51. private void SolidEmpty(EntityUid uid, SolidFuelGeneratorAdapterComponent component, GeneratorEmpty args)
  52. {
  53. _materialStorage.EjectAllMaterial(uid);
  54. }
  55. private void ChemicalEmpty(Entity<ChemicalFuelGeneratorAdapterComponent> entity, ref GeneratorEmpty args)
  56. {
  57. if (!_solutionContainer.ResolveSolution(entity.Owner, entity.Comp.SolutionName, ref entity.Comp.Solution, out var solution))
  58. return;
  59. var spillSolution = _solutionContainer.SplitSolution(entity.Comp.Solution.Value, solution.Volume);
  60. _puddle.TrySpillAt(entity.Owner, spillSolution, out _);
  61. }
  62. private void ChemicalGetClogged(Entity<ChemicalFuelGeneratorAdapterComponent> entity, ref GeneratorGetCloggedEvent args)
  63. {
  64. if (!_solutionContainer.ResolveSolution(entity.Owner, entity.Comp.SolutionName, ref entity.Comp.Solution, out var solution))
  65. return;
  66. foreach (var reagentQuantity in solution)
  67. {
  68. if (!entity.Comp.Reagents.ContainsKey(reagentQuantity.Reagent.Prototype))
  69. {
  70. args.Clogged = true;
  71. return;
  72. }
  73. }
  74. }
  75. private void ChemicalUseFuel(Entity<ChemicalFuelGeneratorAdapterComponent> entity, ref GeneratorUseFuel args)
  76. {
  77. if (!_solutionContainer.ResolveSolution(entity.Owner, entity.Comp.SolutionName, ref entity.Comp.Solution, out var solution))
  78. return;
  79. var totalReagent = 0f;
  80. foreach (var (reagentId, _) in entity.Comp.Reagents)
  81. {
  82. totalReagent += solution.GetTotalPrototypeQuantity(reagentId).Float();
  83. totalReagent += entity.Comp.FractionalReagents.GetValueOrDefault(reagentId);
  84. }
  85. if (totalReagent == 0)
  86. return;
  87. foreach (var (reagentId, multiplier) in entity.Comp.Reagents)
  88. {
  89. var fractionalReagent = entity.Comp.FractionalReagents.GetValueOrDefault(reagentId);
  90. var availableReagent = solution.GetTotalPrototypeQuantity(reagentId);
  91. var availForRatio = fractionalReagent + availableReagent.Float();
  92. var removalPercentage = availForRatio / totalReagent;
  93. var toRemove = RemoveFractionalFuel(
  94. ref fractionalReagent,
  95. args.FuelUsed * removalPercentage,
  96. multiplier * FixedPoint2.Epsilon.Float(),
  97. availableReagent.Value);
  98. entity.Comp.FractionalReagents[reagentId] = fractionalReagent;
  99. _solutionContainer.RemoveReagent(entity.Comp.Solution.Value, reagentId, FixedPoint2.FromCents(toRemove));
  100. }
  101. }
  102. private void ChemicalGetFuel(Entity<ChemicalFuelGeneratorAdapterComponent> entity, ref GeneratorGetFuelEvent args)
  103. {
  104. if (!_solutionContainer.ResolveSolution(entity.Owner, entity.Comp.SolutionName, ref entity.Comp.Solution, out var solution))
  105. return;
  106. var fuel = 0f;
  107. foreach (var (reagentId, multiplier) in entity.Comp.Reagents)
  108. {
  109. var reagent = solution.GetTotalPrototypeQuantity(reagentId).Float();
  110. reagent += entity.Comp.FractionalReagents.GetValueOrDefault(reagentId) * FixedPoint2.Epsilon.Float();
  111. fuel += reagent * multiplier;
  112. }
  113. args.Fuel = fuel;
  114. }
  115. private void SolidUseFuel(EntityUid uid, SolidFuelGeneratorAdapterComponent component, GeneratorUseFuel args)
  116. {
  117. var availableMaterial = _materialStorage.GetMaterialAmount(uid, component.FuelMaterial);
  118. var toRemove = RemoveFractionalFuel(
  119. ref component.FractionalMaterial,
  120. args.FuelUsed,
  121. component.Multiplier,
  122. availableMaterial);
  123. _materialStorage.TryChangeMaterialAmount(uid, component.FuelMaterial, -toRemove);
  124. }
  125. private int RemoveFractionalFuel(ref float fractional, float fuelUsed, float multiplier, int availableQuantity)
  126. {
  127. // Just a sanity thing since I got worried this might be possible.
  128. if (!float.IsFinite(fractional))
  129. fractional = 0;
  130. fractional -= fuelUsed / multiplier;
  131. if (fractional >= 0)
  132. return 0;
  133. // worst (unrealistic) case: -5.5 -> -6.0 -> 6
  134. var toRemove = -(int) MathF.Floor(fractional);
  135. toRemove = Math.Min(availableQuantity, toRemove);
  136. fractional = Math.Max(0, fractional + toRemove);
  137. return toRemove;
  138. }
  139. private void SolidGetFuel(
  140. EntityUid uid,
  141. SolidFuelGeneratorAdapterComponent component,
  142. ref GeneratorGetFuelEvent args)
  143. {
  144. var material = component.FractionalMaterial + _materialStorage.GetMaterialAmount(uid, component.FuelMaterial);
  145. args.Fuel = material * component.Multiplier;
  146. }
  147. private void OnTargetPowerSet(EntityUid uid, FuelGeneratorComponent component,
  148. PortableGeneratorSetTargetPowerMessage args)
  149. {
  150. component.TargetPower = Math.Clamp(
  151. args.TargetPower,
  152. component.MinTargetPower / 1000,
  153. component.MaxTargetPower / 1000) * 1000;
  154. }
  155. public void SetFuelGeneratorOn(EntityUid uid, bool on, FuelGeneratorComponent? generator = null)
  156. {
  157. if (!Resolve(uid, ref generator))
  158. return;
  159. if (on && !Transform(uid).Anchored)
  160. {
  161. // Generator must be anchored to start.
  162. return;
  163. }
  164. generator.On = on;
  165. UpdateState(uid, generator);
  166. }
  167. public override void Update(float frameTime)
  168. {
  169. var query = EntityQueryEnumerator<FuelGeneratorComponent, PowerSupplierComponent>();
  170. while (query.MoveNext(out var uid, out var gen, out var supplier))
  171. {
  172. if (!gen.On)
  173. continue;
  174. var fuel = GetFuel(uid);
  175. if (fuel <= 0)
  176. {
  177. SetFuelGeneratorOn(uid, false, gen);
  178. continue;
  179. }
  180. if (GetIsClogged(uid))
  181. {
  182. _popup.PopupEntity(Loc.GetString("generator-clogged", ("generator", uid)), uid, PopupType.SmallCaution);
  183. SetFuelGeneratorOn(uid, false, gen);
  184. continue;
  185. }
  186. supplier.Enabled = true;
  187. supplier.MaxSupply = gen.TargetPower;
  188. var eff = 1 / CalcFuelEfficiency(gen.TargetPower, gen.OptimalPower, gen);
  189. var consumption = gen.OptimalBurnRate * frameTime * eff;
  190. RaiseLocalEvent(uid, new GeneratorUseFuel(consumption));
  191. }
  192. }
  193. public float GetFuel(EntityUid generator)
  194. {
  195. GeneratorGetFuelEvent getFuelEvent = default;
  196. RaiseLocalEvent(generator, ref getFuelEvent);
  197. return getFuelEvent.Fuel;
  198. }
  199. public bool GetIsClogged(EntityUid generator)
  200. {
  201. GeneratorGetCloggedEvent getCloggedEvent = default;
  202. RaiseLocalEvent(generator, ref getCloggedEvent);
  203. return getCloggedEvent.Clogged;
  204. }
  205. public void EmptyGenerator(EntityUid generator)
  206. {
  207. RaiseLocalEvent(generator, GeneratorEmpty.Instance);
  208. }
  209. private void UpdateState(EntityUid generator, FuelGeneratorComponent component)
  210. {
  211. _appearance.SetData(generator, GeneratorVisuals.Running, component.On);
  212. _ambientSound.SetAmbience(generator, component.On);
  213. if (!component.On)
  214. Comp<PowerSupplierComponent>(generator).Enabled = false;
  215. }
  216. }
  217. /// <summary>
  218. /// Raised by <see cref="GeneratorSystem"/> to calculate the amount of remaining fuel in the generator.
  219. /// </summary>
  220. [ByRefEvent]
  221. public record struct GeneratorGetFuelEvent(float Fuel);
  222. /// <summary>
  223. /// Raised by <see cref="GeneratorSystem"/> to check if a generator is "clogged".
  224. /// For example there's bad chemicals in the fuel tank that prevent starting it.
  225. /// </summary>
  226. [ByRefEvent]
  227. public record struct GeneratorGetCloggedEvent(bool Clogged);
  228. /// <summary>
  229. /// Raised by <see cref="GeneratorSystem"/> to draw fuel from its adapters.
  230. /// </summary>
  231. /// <remarks>
  232. /// Implementations are expected to round fuel consumption up if the used fuel value is too small (e.g. reagent units).
  233. /// </remarks>
  234. public record struct GeneratorUseFuel(float FuelUsed);
  235. /// <summary>
  236. /// Raised by <see cref="GeneratorSystem"/> to empty a generator of its fuel contents.
  237. /// </summary>
  238. public sealed class GeneratorEmpty
  239. {
  240. public static readonly GeneratorEmpty Instance = new();
  241. }