ApcSystem.cs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. using Content.Server.Emp;
  2. using Content.Server.Popups;
  3. using Content.Server.Power.Components;
  4. using Content.Server.Power.Pow3r;
  5. using Content.Shared.Access.Systems;
  6. using Content.Shared.APC;
  7. using Content.Shared.Emag.Systems;
  8. using Content.Shared.Popups;
  9. using Content.Shared.Rounding;
  10. using Robust.Server.GameObjects;
  11. using Robust.Shared.Audio;
  12. using Robust.Shared.Audio.Systems;
  13. using Robust.Shared.Timing;
  14. namespace Content.Server.Power.EntitySystems;
  15. public sealed class ApcSystem : EntitySystem
  16. {
  17. [Dependency] private readonly AccessReaderSystem _accessReader = default!;
  18. [Dependency] private readonly IGameTiming _gameTiming = default!;
  19. [Dependency] private readonly EmagSystem _emag = default!;
  20. [Dependency] private readonly PopupSystem _popup = default!;
  21. [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
  22. [Dependency] private readonly SharedAudioSystem _audio = default!;
  23. [Dependency] private readonly UserInterfaceSystem _ui = default!;
  24. public override void Initialize()
  25. {
  26. base.Initialize();
  27. UpdatesAfter.Add(typeof(PowerNetSystem));
  28. SubscribeLocalEvent<ApcComponent, BoundUIOpenedEvent>(OnBoundUiOpen);
  29. SubscribeLocalEvent<ApcComponent, ComponentStartup>(OnApcStartup);
  30. SubscribeLocalEvent<ApcComponent, ChargeChangedEvent>(OnBatteryChargeChanged);
  31. SubscribeLocalEvent<ApcComponent, ApcToggleMainBreakerMessage>(OnToggleMainBreaker);
  32. SubscribeLocalEvent<ApcComponent, GotEmaggedEvent>(OnEmagged);
  33. SubscribeLocalEvent<ApcComponent, EmpPulseEvent>(OnEmpPulse);
  34. }
  35. public override void Update(float deltaTime)
  36. {
  37. var query = EntityQueryEnumerator<ApcComponent, PowerNetworkBatteryComponent, UserInterfaceComponent>();
  38. while (query.MoveNext(out var uid, out var apc, out var battery, out var ui))
  39. {
  40. if (apc.LastUiUpdate + ApcComponent.VisualsChangeDelay < _gameTiming.CurTime && _ui.IsUiOpen((uid, ui), ApcUiKey.Key))
  41. {
  42. apc.LastUiUpdate = _gameTiming.CurTime;
  43. UpdateUIState(uid, apc, battery);
  44. }
  45. if (apc.NeedStateUpdate)
  46. {
  47. UpdateApcState(uid, apc, battery);
  48. }
  49. }
  50. }
  51. // Change the APC's state only when the battery state changes, or when it's first created.
  52. private void OnBatteryChargeChanged(EntityUid uid, ApcComponent component, ref ChargeChangedEvent args)
  53. {
  54. UpdateApcState(uid, component);
  55. }
  56. private static void OnApcStartup(EntityUid uid, ApcComponent component, ComponentStartup args)
  57. {
  58. // We cannot update immediately, as various network/battery state is not valid yet.
  59. // Defer until the next tick.
  60. component.NeedStateUpdate = true;
  61. }
  62. private void OnBoundUiOpen(EntityUid uid, ApcComponent component, BoundUIOpenedEvent args)
  63. {
  64. UpdateApcState(uid, component);
  65. }
  66. private void OnToggleMainBreaker(EntityUid uid, ApcComponent component, ApcToggleMainBreakerMessage args)
  67. {
  68. var attemptEv = new ApcToggleMainBreakerAttemptEvent();
  69. RaiseLocalEvent(uid, ref attemptEv);
  70. if (attemptEv.Cancelled)
  71. {
  72. _popup.PopupCursor(Loc.GetString("apc-component-on-toggle-cancel"),
  73. args.Actor, PopupType.Medium);
  74. return;
  75. }
  76. if (_accessReader.IsAllowed(args.Actor, uid))
  77. {
  78. ApcToggleBreaker(uid, component);
  79. }
  80. else
  81. {
  82. _popup.PopupCursor(Loc.GetString("apc-component-insufficient-access"),
  83. args.Actor, PopupType.Medium);
  84. }
  85. }
  86. public void ApcToggleBreaker(EntityUid uid, ApcComponent? apc = null, PowerNetworkBatteryComponent? battery = null)
  87. {
  88. if (!Resolve(uid, ref apc, ref battery))
  89. return;
  90. apc.MainBreakerEnabled = !apc.MainBreakerEnabled;
  91. battery.CanDischarge = apc.MainBreakerEnabled;
  92. UpdateUIState(uid, apc);
  93. _audio.PlayPvs(apc.OnReceiveMessageSound, uid, AudioParams.Default.WithVolume(-2f));
  94. }
  95. private void OnEmagged(EntityUid uid, ApcComponent comp, ref GotEmaggedEvent args)
  96. {
  97. if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
  98. return;
  99. if (_emag.CheckFlag(uid, EmagType.Interaction))
  100. return;
  101. args.Handled = true;
  102. }
  103. public void UpdateApcState(EntityUid uid,
  104. ApcComponent? apc=null,
  105. PowerNetworkBatteryComponent? battery = null)
  106. {
  107. if (!Resolve(uid, ref apc, ref battery, false))
  108. return;
  109. if (apc.LastChargeStateTime == null || apc.LastChargeStateTime + ApcComponent.VisualsChangeDelay < _gameTiming.CurTime)
  110. {
  111. var newState = CalcChargeState(uid, battery.NetworkBattery);
  112. if (newState != apc.LastChargeState)
  113. {
  114. apc.LastChargeState = newState;
  115. apc.LastChargeStateTime = _gameTiming.CurTime;
  116. if (TryComp(uid, out AppearanceComponent? appearance))
  117. {
  118. _appearance.SetData(uid, ApcVisuals.ChargeState, newState, appearance);
  119. }
  120. }
  121. }
  122. var extPowerState = CalcExtPowerState(uid, battery.NetworkBattery);
  123. if (extPowerState != apc.LastExternalState)
  124. {
  125. apc.LastExternalState = extPowerState;
  126. UpdateUIState(uid, apc, battery);
  127. }
  128. apc.NeedStateUpdate = false;
  129. }
  130. public void UpdateUIState(EntityUid uid,
  131. ApcComponent? apc = null,
  132. PowerNetworkBatteryComponent? netBat = null,
  133. UserInterfaceComponent? ui = null)
  134. {
  135. if (!Resolve(uid, ref apc, ref netBat, ref ui))
  136. return;
  137. var battery = netBat.NetworkBattery;
  138. const int ChargeAccuracy = 5;
  139. // TODO: Fix ContentHelpers or make a new one coz this is cooked.
  140. var charge = ContentHelpers.RoundToNearestLevels(battery.CurrentStorage / battery.Capacity, 1.0, 100 / ChargeAccuracy) / 100f * ChargeAccuracy;
  141. var state = new ApcBoundInterfaceState(apc.MainBreakerEnabled,
  142. (int) MathF.Ceiling(battery.CurrentSupply), apc.LastExternalState,
  143. charge);
  144. _ui.SetUiState((uid, ui), ApcUiKey.Key, state);
  145. }
  146. private ApcChargeState CalcChargeState(EntityUid uid, PowerState.Battery battery)
  147. {
  148. if (_emag.CheckFlag(uid, EmagType.Interaction))
  149. return ApcChargeState.Emag;
  150. if (battery.CurrentStorage / battery.Capacity > ApcComponent.HighPowerThreshold)
  151. {
  152. return ApcChargeState.Full;
  153. }
  154. var delta = battery.CurrentSupply - battery.CurrentReceiving;
  155. return delta < 0 ? ApcChargeState.Charging : ApcChargeState.Lack;
  156. }
  157. private ApcExternalPowerState CalcExtPowerState(EntityUid uid, PowerState.Battery battery)
  158. {
  159. if (battery.CurrentReceiving == 0 && !MathHelper.CloseTo(battery.CurrentStorage / battery.Capacity, 1))
  160. {
  161. return ApcExternalPowerState.None;
  162. }
  163. var delta = battery.CurrentSupply - battery.CurrentReceiving;
  164. if (!MathHelper.CloseToPercent(delta, 0, 0.1f) && delta < 0)
  165. {
  166. return ApcExternalPowerState.Low;
  167. }
  168. return ApcExternalPowerState.Good;
  169. }
  170. private void OnEmpPulse(EntityUid uid, ApcComponent component, ref EmpPulseEvent args)
  171. {
  172. if (component.MainBreakerEnabled)
  173. {
  174. args.Affected = true;
  175. args.Disabled = true;
  176. ApcToggleBreaker(uid, component);
  177. }
  178. }
  179. }
  180. [ByRefEvent]
  181. public record struct ApcToggleMainBreakerAttemptEvent(bool Cancelled);