ChargerSystem.cs 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. using Content.Server.Power.Components;
  2. using Content.Server.Emp;
  3. using Content.Server.PowerCell;
  4. using Content.Shared.Examine;
  5. using Content.Shared.Power;
  6. using Content.Shared.PowerCell.Components;
  7. using Content.Shared.Emp;
  8. using JetBrains.Annotations;
  9. using Robust.Shared.Containers;
  10. using System.Diagnostics.CodeAnalysis;
  11. using Content.Shared.Storage.Components;
  12. using Robust.Server.Containers;
  13. using Content.Shared.Whitelist;
  14. namespace Content.Server.Power.EntitySystems;
  15. [UsedImplicitly]
  16. internal sealed class ChargerSystem : EntitySystem
  17. {
  18. [Dependency] private readonly ContainerSystem _container = default!;
  19. [Dependency] private readonly PowerCellSystem _powerCell = default!;
  20. [Dependency] private readonly BatterySystem _battery = default!;
  21. [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
  22. [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
  23. public override void Initialize()
  24. {
  25. SubscribeLocalEvent<ChargerComponent, ComponentStartup>(OnStartup);
  26. SubscribeLocalEvent<ChargerComponent, PowerChangedEvent>(OnPowerChanged);
  27. SubscribeLocalEvent<ChargerComponent, EntInsertedIntoContainerMessage>(OnInserted);
  28. SubscribeLocalEvent<ChargerComponent, EntRemovedFromContainerMessage>(OnRemoved);
  29. SubscribeLocalEvent<ChargerComponent, ContainerIsInsertingAttemptEvent>(OnInsertAttempt);
  30. SubscribeLocalEvent<ChargerComponent, InsertIntoEntityStorageAttemptEvent>(OnEntityStorageInsertAttempt);
  31. SubscribeLocalEvent<ChargerComponent, ExaminedEvent>(OnChargerExamine);
  32. SubscribeLocalEvent<ChargerComponent, EmpPulseEvent>(OnEmpPulse);
  33. }
  34. private void OnStartup(EntityUid uid, ChargerComponent component, ComponentStartup args)
  35. {
  36. UpdateStatus(uid, component);
  37. }
  38. private void OnChargerExamine(EntityUid uid, ChargerComponent component, ExaminedEvent args)
  39. {
  40. using (args.PushGroup(nameof(ChargerComponent)))
  41. {
  42. // rate at which the charger charges
  43. args.PushMarkup(Loc.GetString("charger-examine", ("color", "yellow"), ("chargeRate", (int) component.ChargeRate)));
  44. // try to get contents of the charger
  45. if (!_container.TryGetContainer(uid, component.SlotId, out var container))
  46. return;
  47. if (HasComp<PowerCellSlotComponent>(uid))
  48. return;
  49. // if charger is empty and not a power cell type charger, add empty message
  50. // power cells have their own empty message by default, for things like flash lights
  51. if (container.ContainedEntities.Count == 0)
  52. {
  53. args.PushMarkup(Loc.GetString("charger-empty"));
  54. }
  55. else
  56. {
  57. // add how much each item is charged it
  58. foreach (var contained in container.ContainedEntities)
  59. {
  60. if (!TryComp<BatteryComponent>(contained, out var battery))
  61. continue;
  62. var chargePercentage = (battery.CurrentCharge / battery.MaxCharge) * 100;
  63. args.PushMarkup(Loc.GetString("charger-content", ("chargePercentage", (int) chargePercentage)));
  64. }
  65. }
  66. }
  67. }
  68. public override void Update(float frameTime)
  69. {
  70. var query = EntityQueryEnumerator<ActiveChargerComponent, ChargerComponent, ContainerManagerComponent>();
  71. while (query.MoveNext(out var uid, out _, out var charger, out var containerComp))
  72. {
  73. if (!_container.TryGetContainer(uid, charger.SlotId, out var container, containerComp))
  74. continue;
  75. if (charger.Status == CellChargerStatus.Empty || charger.Status == CellChargerStatus.Charged || container.ContainedEntities.Count == 0)
  76. continue;
  77. foreach (var contained in container.ContainedEntities)
  78. {
  79. TransferPower(uid, contained, charger, frameTime);
  80. }
  81. }
  82. }
  83. private void OnPowerChanged(EntityUid uid, ChargerComponent component, ref PowerChangedEvent args)
  84. {
  85. UpdateStatus(uid, component);
  86. }
  87. private void OnInserted(EntityUid uid, ChargerComponent component, EntInsertedIntoContainerMessage args)
  88. {
  89. if (!component.Initialized)
  90. return;
  91. if (args.Container.ID != component.SlotId)
  92. return;
  93. UpdateStatus(uid, component);
  94. }
  95. private void OnRemoved(EntityUid uid, ChargerComponent component, EntRemovedFromContainerMessage args)
  96. {
  97. if (args.Container.ID != component.SlotId)
  98. return;
  99. UpdateStatus(uid, component);
  100. }
  101. /// <summary>
  102. /// Verify that the entity being inserted is actually rechargeable.
  103. /// </summary>
  104. private void OnInsertAttempt(EntityUid uid, ChargerComponent component, ContainerIsInsertingAttemptEvent args)
  105. {
  106. if (!component.Initialized)
  107. return;
  108. if (args.Container.ID != component.SlotId)
  109. return;
  110. if (!TryComp<PowerCellSlotComponent>(args.EntityUid, out var cellSlot))
  111. return;
  112. if (!cellSlot.FitsInCharger)
  113. args.Cancel();
  114. }
  115. private void OnEntityStorageInsertAttempt(EntityUid uid, ChargerComponent component, ref InsertIntoEntityStorageAttemptEvent args)
  116. {
  117. if (!component.Initialized || args.Cancelled)
  118. return;
  119. if (!TryComp<PowerCellSlotComponent>(uid, out var cellSlot))
  120. return;
  121. if (!cellSlot.FitsInCharger)
  122. args.Cancelled = true;
  123. }
  124. private void UpdateStatus(EntityUid uid, ChargerComponent component)
  125. {
  126. var status = GetStatus(uid, component);
  127. TryComp(uid, out AppearanceComponent? appearance);
  128. if (!_container.TryGetContainer(uid, component.SlotId, out var container))
  129. return;
  130. _appearance.SetData(uid, CellVisual.Occupied, container.ContainedEntities.Count != 0, appearance);
  131. if (component.Status == status || !TryComp(uid, out ApcPowerReceiverComponent? receiver))
  132. return;
  133. component.Status = status;
  134. if (component.Status == CellChargerStatus.Charging)
  135. {
  136. AddComp<ActiveChargerComponent>(uid);
  137. }
  138. else
  139. {
  140. RemComp<ActiveChargerComponent>(uid);
  141. }
  142. switch (component.Status)
  143. {
  144. case CellChargerStatus.Off:
  145. receiver.Load = 0;
  146. _appearance.SetData(uid, CellVisual.Light, CellChargerStatus.Off, appearance);
  147. break;
  148. case CellChargerStatus.Empty:
  149. receiver.Load = 0;
  150. _appearance.SetData(uid, CellVisual.Light, CellChargerStatus.Empty, appearance);
  151. break;
  152. case CellChargerStatus.Charging:
  153. receiver.Load = component.ChargeRate;
  154. _appearance.SetData(uid, CellVisual.Light, CellChargerStatus.Charging, appearance);
  155. break;
  156. case CellChargerStatus.Charged:
  157. receiver.Load = 0;
  158. _appearance.SetData(uid, CellVisual.Light, CellChargerStatus.Charged, appearance);
  159. break;
  160. default:
  161. throw new ArgumentOutOfRangeException();
  162. }
  163. }
  164. private void OnEmpPulse(EntityUid uid, ChargerComponent component, ref EmpPulseEvent args)
  165. {
  166. args.Affected = true;
  167. args.Disabled = true;
  168. }
  169. private CellChargerStatus GetStatus(EntityUid uid, ChargerComponent component)
  170. {
  171. if (!component.Portable)
  172. {
  173. if (!TryComp(uid, out TransformComponent? transformComponent) || !transformComponent.Anchored)
  174. return CellChargerStatus.Off;
  175. }
  176. if (!TryComp(uid, out ApcPowerReceiverComponent? apcPowerReceiverComponent))
  177. return CellChargerStatus.Off;
  178. if (!component.Portable && !apcPowerReceiverComponent.Powered)
  179. return CellChargerStatus.Off;
  180. if (HasComp<EmpDisabledComponent>(uid))
  181. return CellChargerStatus.Off;
  182. if (!_container.TryGetContainer(uid, component.SlotId, out var container))
  183. return CellChargerStatus.Off;
  184. if (container.ContainedEntities.Count == 0)
  185. return CellChargerStatus.Empty;
  186. if (!SearchForBattery(container.ContainedEntities[0], out var heldEnt, out var heldBattery))
  187. return CellChargerStatus.Off;
  188. if (_battery.IsFull(heldEnt.Value, heldBattery))
  189. return CellChargerStatus.Charged;
  190. return CellChargerStatus.Charging;
  191. }
  192. private void TransferPower(EntityUid uid, EntityUid targetEntity, ChargerComponent component, float frameTime)
  193. {
  194. if (!TryComp(uid, out ApcPowerReceiverComponent? receiverComponent))
  195. return;
  196. if (!receiverComponent.Powered)
  197. return;
  198. if (_whitelistSystem.IsWhitelistFail(component.Whitelist, targetEntity))
  199. return;
  200. if (!SearchForBattery(targetEntity, out var batteryUid, out var heldBattery))
  201. return;
  202. _battery.SetCharge(batteryUid.Value, heldBattery.CurrentCharge + component.ChargeRate * frameTime, heldBattery);
  203. UpdateStatus(uid, component);
  204. }
  205. private bool SearchForBattery(EntityUid uid, [NotNullWhen(true)] out EntityUid? batteryUid, [NotNullWhen(true)] out BatteryComponent? component)
  206. {
  207. // try get a battery directly on the inserted entity
  208. if (!TryComp(uid, out component))
  209. {
  210. // or by checking for a power cell slot on the inserted entity
  211. return _powerCell.TryGetBatteryFromSlot(uid, out batteryUid, out component);
  212. }
  213. batteryUid = uid;
  214. return true;
  215. }
  216. }