1
0

PoweredLightSystem.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. using Content.Server.Administration.Logs;
  2. using Content.Server.DeviceLinking.Events;
  3. using Content.Server.DeviceLinking.Systems;
  4. using Content.Server.DeviceNetwork;
  5. using Content.Server.DeviceNetwork.Systems;
  6. using Content.Server.Emp;
  7. using Content.Server.Ghost;
  8. using Content.Server.Light.Components;
  9. using Content.Server.Power.Components;
  10. using Content.Shared.Audio;
  11. using Content.Shared.Damage;
  12. using Content.Shared.Database;
  13. using Content.Shared.DoAfter;
  14. using Content.Shared.Hands.EntitySystems;
  15. using Content.Shared.Interaction;
  16. using Content.Shared.Inventory;
  17. using Content.Shared.Light;
  18. using Content.Shared.Light.Components;
  19. using Content.Shared.Popups;
  20. using Robust.Server.GameObjects;
  21. using Robust.Shared.Audio;
  22. using Robust.Shared.Containers;
  23. using Robust.Shared.Player;
  24. using Robust.Shared.Timing;
  25. using Robust.Shared.Audio.Systems;
  26. using Content.Shared.Damage.Systems;
  27. using Content.Shared.Damage.Components;
  28. using Content.Shared.Power;
  29. namespace Content.Server.Light.EntitySystems
  30. {
  31. /// <summary>
  32. /// System for the PoweredLightComponents
  33. /// </summary>
  34. public sealed class PoweredLightSystem : EntitySystem
  35. {
  36. [Dependency] private readonly IGameTiming _gameTiming = default!;
  37. [Dependency] private readonly SharedAmbientSoundSystem _ambientSystem = default!;
  38. [Dependency] private readonly LightBulbSystem _bulbSystem = default!;
  39. [Dependency] private readonly SharedHandsSystem _handsSystem = default!;
  40. [Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
  41. [Dependency] private readonly SharedContainerSystem _containerSystem = default!;
  42. [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
  43. [Dependency] private readonly SharedAudioSystem _audio = default!;
  44. [Dependency] private readonly PointLightSystem _pointLight = default!;
  45. [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
  46. [Dependency] private readonly DamageOnInteractSystem _damageOnInteractSystem = default!;
  47. private static readonly TimeSpan ThunkDelay = TimeSpan.FromSeconds(2);
  48. public const string LightBulbContainer = "light_bulb";
  49. public override void Initialize()
  50. {
  51. base.Initialize();
  52. SubscribeLocalEvent<PoweredLightComponent, ComponentInit>(OnInit);
  53. SubscribeLocalEvent<PoweredLightComponent, MapInitEvent>(OnMapInit);
  54. SubscribeLocalEvent<PoweredLightComponent, InteractUsingEvent>(OnInteractUsing);
  55. SubscribeLocalEvent<PoweredLightComponent, InteractHandEvent>(OnInteractHand);
  56. SubscribeLocalEvent<PoweredLightComponent, GhostBooEvent>(OnGhostBoo);
  57. SubscribeLocalEvent<PoweredLightComponent, DamageChangedEvent>(HandleLightDamaged);
  58. SubscribeLocalEvent<PoweredLightComponent, SignalReceivedEvent>(OnSignalReceived);
  59. SubscribeLocalEvent<PoweredLightComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
  60. SubscribeLocalEvent<PoweredLightComponent, PowerChangedEvent>(OnPowerChanged);
  61. SubscribeLocalEvent<PoweredLightComponent, PoweredLightDoAfterEvent>(OnDoAfter);
  62. SubscribeLocalEvent<PoweredLightComponent, EmpPulseEvent>(OnEmpPulse);
  63. }
  64. private void OnInit(EntityUid uid, PoweredLightComponent light, ComponentInit args)
  65. {
  66. light.LightBulbContainer = _containerSystem.EnsureContainer<ContainerSlot>(uid, LightBulbContainer);
  67. _signalSystem.EnsureSinkPorts(uid, light.OnPort, light.OffPort, light.TogglePort);
  68. }
  69. private void OnMapInit(EntityUid uid, PoweredLightComponent light, MapInitEvent args)
  70. {
  71. // TODO: Use ContainerFill dog
  72. if (light.HasLampOnSpawn != null)
  73. {
  74. var entity = EntityManager.SpawnEntity(light.HasLampOnSpawn, EntityManager.GetComponent<TransformComponent>(uid).Coordinates);
  75. _containerSystem.Insert(entity, light.LightBulbContainer);
  76. }
  77. // need this to update visualizers
  78. UpdateLight(uid, light);
  79. }
  80. private void OnInteractUsing(EntityUid uid, PoweredLightComponent component, InteractUsingEvent args)
  81. {
  82. if (args.Handled)
  83. return;
  84. args.Handled = InsertBulb(uid, args.Used, component);
  85. }
  86. private void OnInteractHand(EntityUid uid, PoweredLightComponent light, InteractHandEvent args)
  87. {
  88. if (args.Handled)
  89. return;
  90. // check if light has bulb to eject
  91. var bulbUid = GetBulb(uid, light);
  92. if (bulbUid == null)
  93. return;
  94. var userUid = args.User;
  95. //removing a broken/burned bulb, so allow instant removal
  96. if(TryComp<LightBulbComponent>(bulbUid.Value, out var bulb) && bulb.State != LightBulbState.Normal)
  97. {
  98. args.Handled = EjectBulb(uid, userUid, light) != null;
  99. return;
  100. }
  101. // removing a working bulb, so require a delay
  102. _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, userUid, light.EjectBulbDelay, new PoweredLightDoAfterEvent(), uid, target: uid)
  103. {
  104. BreakOnMove = true,
  105. BreakOnDamage = true,
  106. });
  107. args.Handled = true;
  108. }
  109. #region Bulb Logic API
  110. /// <summary>
  111. /// Inserts the bulb if possible.
  112. /// </summary>
  113. /// <returns>True if it could insert it, false if it couldn't.</returns>
  114. public bool InsertBulb(EntityUid uid, EntityUid bulbUid, PoweredLightComponent? light = null)
  115. {
  116. if (!Resolve(uid, ref light))
  117. return false;
  118. // check if light already has bulb
  119. if (GetBulb(uid, light) != null)
  120. return false;
  121. // check if bulb fits
  122. if (!EntityManager.TryGetComponent(bulbUid, out LightBulbComponent? lightBulb))
  123. return false;
  124. if (lightBulb.Type != light.BulbType)
  125. return false;
  126. // try to insert bulb in container
  127. if (!_containerSystem.Insert(bulbUid, light.LightBulbContainer))
  128. return false;
  129. UpdateLight(uid, light);
  130. return true;
  131. }
  132. /// <summary>
  133. /// Ejects the bulb to a mob's hand if possible.
  134. /// </summary>
  135. /// <returns>Bulb uid if it was successfully ejected, null otherwise</returns>
  136. public EntityUid? EjectBulb(EntityUid uid, EntityUid? userUid = null, PoweredLightComponent? light = null)
  137. {
  138. if (!Resolve(uid, ref light))
  139. return null;
  140. // check if light has bulb
  141. if (GetBulb(uid, light) is not { Valid: true } bulb)
  142. return null;
  143. // try to remove bulb from container
  144. if (!_containerSystem.Remove(bulb, light.LightBulbContainer))
  145. return null;
  146. // try to place bulb in hands
  147. _handsSystem.PickupOrDrop(userUid, bulb);
  148. UpdateLight(uid, light);
  149. return bulb;
  150. }
  151. /// <summary>
  152. /// Replaces the spawned prototype of a pre-mapinit powered light with a different variant.
  153. /// </summary>
  154. public bool ReplaceSpawnedPrototype(Entity<PoweredLightComponent> light, string bulb)
  155. {
  156. if (light.Comp.LightBulbContainer.ContainedEntity != null)
  157. return false;
  158. if (LifeStage(light.Owner) >= EntityLifeStage.MapInitialized)
  159. return false;
  160. light.Comp.HasLampOnSpawn = bulb;
  161. return true;
  162. }
  163. /// <summary>
  164. /// Try to replace current bulb with a new one
  165. /// If succeed old bulb just drops on floor
  166. /// </summary>
  167. public bool ReplaceBulb(EntityUid uid, EntityUid bulb, PoweredLightComponent? light = null)
  168. {
  169. EjectBulb(uid, null, light);
  170. return InsertBulb(uid, bulb, light);
  171. }
  172. /// <summary>
  173. /// Try to get light bulb inserted in powered light
  174. /// </summary>
  175. /// <returns>Bulb uid if it exist, null otherwise</returns>
  176. public EntityUid? GetBulb(EntityUid uid, PoweredLightComponent? light = null)
  177. {
  178. if (!Resolve(uid, ref light))
  179. return null;
  180. return light.LightBulbContainer.ContainedEntity;
  181. }
  182. /// <summary>
  183. /// Try to break bulb inside light fixture
  184. /// </summary>
  185. public bool TryDestroyBulb(EntityUid uid, PoweredLightComponent? light = null)
  186. {
  187. if (!Resolve(uid, ref light, false))
  188. return false;
  189. // if we aren't mapinited,
  190. // just null the spawned bulb
  191. if (LifeStage(uid) < EntityLifeStage.MapInitialized)
  192. {
  193. light.HasLampOnSpawn = null;
  194. return true;
  195. }
  196. // check bulb state
  197. var bulbUid = GetBulb(uid, light);
  198. if (bulbUid == null || !EntityManager.TryGetComponent(bulbUid.Value, out LightBulbComponent? lightBulb))
  199. return false;
  200. if (lightBulb.State == LightBulbState.Broken)
  201. return false;
  202. // break it
  203. _bulbSystem.SetState(bulbUid.Value, LightBulbState.Broken, lightBulb);
  204. _bulbSystem.PlayBreakSound(bulbUid.Value, lightBulb);
  205. UpdateLight(uid, light);
  206. return true;
  207. }
  208. #endregion
  209. private void UpdateLight(EntityUid uid,
  210. PoweredLightComponent? light = null,
  211. ApcPowerReceiverComponent? powerReceiver = null,
  212. AppearanceComponent? appearance = null)
  213. {
  214. if (!Resolve(uid, ref light, ref powerReceiver, false))
  215. return;
  216. // Optional component.
  217. Resolve(uid, ref appearance, false);
  218. // check if light has bulb
  219. var bulbUid = GetBulb(uid, light);
  220. if (bulbUid == null || !EntityManager.TryGetComponent(bulbUid.Value, out LightBulbComponent? lightBulb))
  221. {
  222. SetLight(uid, false, light: light);
  223. powerReceiver.Load = 0;
  224. _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Empty, appearance);
  225. return;
  226. }
  227. switch (lightBulb.State)
  228. {
  229. case LightBulbState.Normal:
  230. if (powerReceiver.Powered && light.On)
  231. {
  232. SetLight(uid, true, lightBulb.Color, light, lightBulb.LightRadius, lightBulb.LightEnergy, lightBulb.LightSoftness);
  233. _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.On, appearance);
  234. var time = _gameTiming.CurTime;
  235. if (time > light.LastThunk + ThunkDelay)
  236. {
  237. light.LastThunk = time;
  238. _audio.PlayPvs(light.TurnOnSound, uid, light.TurnOnSound.Params.AddVolume(-10f));
  239. }
  240. }
  241. else
  242. {
  243. SetLight(uid, false, light: light);
  244. _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Off, appearance);
  245. }
  246. break;
  247. case LightBulbState.Broken:
  248. SetLight(uid, false, light: light);
  249. _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Broken, appearance);
  250. break;
  251. case LightBulbState.Burned:
  252. SetLight(uid, false, light: light);
  253. _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Burned, appearance);
  254. break;
  255. }
  256. powerReceiver.Load = (light.On && lightBulb.State == LightBulbState.Normal) ? lightBulb.PowerUse : 0;
  257. }
  258. /// <summary>
  259. /// Destroy the light bulb if the light took any damage.
  260. /// </summary>
  261. public void HandleLightDamaged(EntityUid uid, PoweredLightComponent component, DamageChangedEvent args)
  262. {
  263. // Was it being repaired, or did it take damage?
  264. if (args.DamageIncreased)
  265. {
  266. // Eventually, this logic should all be done by this (or some other) system, not a component.
  267. TryDestroyBulb(uid, component);
  268. }
  269. }
  270. private void OnGhostBoo(EntityUid uid, PoweredLightComponent light, GhostBooEvent args)
  271. {
  272. if (light.IgnoreGhostsBoo)
  273. return;
  274. // check cooldown first to prevent abuse
  275. var time = _gameTiming.CurTime;
  276. if (light.LastGhostBlink != null)
  277. {
  278. if (time <= light.LastGhostBlink + light.GhostBlinkingCooldown)
  279. return;
  280. }
  281. light.LastGhostBlink = time;
  282. ToggleBlinkingLight(uid, light, true);
  283. uid.SpawnTimer(light.GhostBlinkingTime, () =>
  284. {
  285. ToggleBlinkingLight(uid, light, false);
  286. });
  287. args.Handled = true;
  288. }
  289. private void OnPowerChanged(EntityUid uid, PoweredLightComponent component, ref PowerChangedEvent args)
  290. {
  291. // TODO: Power moment
  292. var metadata = MetaData(uid);
  293. if (metadata.EntityPaused || TerminatingOrDeleted(uid, metadata))
  294. return;
  295. UpdateLight(uid, component);
  296. }
  297. public void ToggleBlinkingLight(EntityUid uid, PoweredLightComponent light, bool isNowBlinking)
  298. {
  299. if (light.IsBlinking == isNowBlinking)
  300. return;
  301. light.IsBlinking = isNowBlinking;
  302. if (!EntityManager.TryGetComponent(uid, out AppearanceComponent? appearance))
  303. return;
  304. _appearance.SetData(uid, PoweredLightVisuals.Blinking, isNowBlinking, appearance);
  305. }
  306. private void OnSignalReceived(EntityUid uid, PoweredLightComponent component, ref SignalReceivedEvent args)
  307. {
  308. if (args.Port == component.OffPort)
  309. SetState(uid, false, component);
  310. else if (args.Port == component.OnPort)
  311. SetState(uid, true, component);
  312. else if (args.Port == component.TogglePort)
  313. ToggleLight(uid, component);
  314. }
  315. /// <summary>
  316. /// Turns the light on or of when receiving a <see cref="DeviceNetworkConstants.CmdSetState"/> command.
  317. /// The light is turned on or of according to the <see cref="DeviceNetworkConstants.StateEnabled"/> value
  318. /// </summary>
  319. private void OnPacketReceived(EntityUid uid, PoweredLightComponent component, DeviceNetworkPacketEvent args)
  320. {
  321. if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? command) || command != DeviceNetworkConstants.CmdSetState) return;
  322. if (!args.Data.TryGetValue(DeviceNetworkConstants.StateEnabled, out bool enabled)) return;
  323. SetState(uid, enabled, component);
  324. }
  325. private void SetLight(EntityUid uid, bool value, Color? color = null, PoweredLightComponent? light = null, float? radius = null, float? energy = null, float? softness = null)
  326. {
  327. if (!Resolve(uid, ref light))
  328. return;
  329. light.CurrentLit = value;
  330. _ambientSystem.SetAmbience(uid, value);
  331. if (EntityManager.TryGetComponent(uid, out PointLightComponent? pointLight))
  332. {
  333. _pointLight.SetEnabled(uid, value, pointLight);
  334. if (color != null)
  335. _pointLight.SetColor(uid, color.Value, pointLight);
  336. if (radius != null)
  337. _pointLight.SetRadius(uid, (float) radius, pointLight);
  338. if (energy != null)
  339. _pointLight.SetEnergy(uid, (float) energy, pointLight);
  340. if (softness != null)
  341. _pointLight.SetSoftness(uid, (float) softness, pointLight);
  342. }
  343. // light bulbs burn your hands!
  344. if (TryComp<DamageOnInteractComponent>(uid, out var damageOnInteractComp))
  345. _damageOnInteractSystem.SetIsDamageActiveTo((uid, damageOnInteractComp), value);
  346. }
  347. public void ToggleLight(EntityUid uid, PoweredLightComponent? light = null)
  348. {
  349. if (!Resolve(uid, ref light))
  350. return;
  351. light.On = !light.On;
  352. UpdateLight(uid, light);
  353. }
  354. public void SetState(EntityUid uid, bool state, PoweredLightComponent? light = null)
  355. {
  356. if (!Resolve(uid, ref light))
  357. return;
  358. light.On = state;
  359. UpdateLight(uid, light);
  360. }
  361. private void OnDoAfter(EntityUid uid, PoweredLightComponent component, DoAfterEvent args)
  362. {
  363. if (args.Handled || args.Cancelled || args.Args.Target == null)
  364. return;
  365. EjectBulb(args.Args.Target.Value, args.Args.User, component);
  366. args.Handled = true;
  367. }
  368. private void OnEmpPulse(EntityUid uid, PoweredLightComponent component, ref EmpPulseEvent args)
  369. {
  370. if (TryDestroyBulb(uid, component))
  371. args.Affected = true;
  372. }
  373. }
  374. }