1
0

FlashSystem.cs 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. using System.Linq;
  2. using Content.Server.Flash.Components;
  3. using Content.Shared.Flash.Components;
  4. using Content.Server.Light.EntitySystems;
  5. using Content.Server.Popups;
  6. using Content.Server.Stunnable;
  7. using Content.Shared.Charges.Components;
  8. using Content.Shared.Charges.Systems;
  9. using Content.Shared.Eye.Blinding.Components;
  10. using Content.Shared.Flash;
  11. using Content.Shared.IdentityManagement;
  12. using Content.Shared.Interaction.Events;
  13. using Content.Shared.Inventory;
  14. using Content.Shared.Tag;
  15. using Content.Shared.Traits.Assorted;
  16. using Content.Shared.Weapons.Melee.Events;
  17. using Content.Shared.StatusEffect;
  18. using Content.Shared.Examine;
  19. using Robust.Server.Audio;
  20. using Robust.Server.GameObjects;
  21. using Robust.Shared.Audio;
  22. using Robust.Shared.Random;
  23. using InventoryComponent = Content.Shared.Inventory.InventoryComponent;
  24. namespace Content.Server.Flash
  25. {
  26. internal sealed class FlashSystem : SharedFlashSystem
  27. {
  28. [Dependency] private readonly AppearanceSystem _appearance = default!;
  29. [Dependency] private readonly AudioSystem _audio = default!;
  30. [Dependency] private readonly SharedChargesSystem _charges = default!;
  31. [Dependency] private readonly EntityLookupSystem _entityLookup = default!;
  32. [Dependency] private readonly SharedTransformSystem _transform = default!;
  33. [Dependency] private readonly ExamineSystemShared _examine = default!;
  34. [Dependency] private readonly InventorySystem _inventory = default!;
  35. [Dependency] private readonly PopupSystem _popup = default!;
  36. [Dependency] private readonly StunSystem _stun = default!;
  37. [Dependency] private readonly TagSystem _tag = default!;
  38. [Dependency] private readonly IRobustRandom _random = default!;
  39. [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
  40. public override void Initialize()
  41. {
  42. base.Initialize();
  43. SubscribeLocalEvent<FlashComponent, MeleeHitEvent>(OnFlashMeleeHit);
  44. // ran before toggling light for extra-bright lantern
  45. SubscribeLocalEvent<FlashComponent, UseInHandEvent>(OnFlashUseInHand, before: new[] { typeof(HandheldLightSystem) });
  46. SubscribeLocalEvent<InventoryComponent, FlashAttemptEvent>(OnInventoryFlashAttempt);
  47. SubscribeLocalEvent<FlashImmunityComponent, FlashAttemptEvent>(OnFlashImmunityFlashAttempt);
  48. SubscribeLocalEvent<PermanentBlindnessComponent, FlashAttemptEvent>(OnPermanentBlindnessFlashAttempt);
  49. SubscribeLocalEvent<TemporaryBlindnessComponent, FlashAttemptEvent>(OnTemporaryBlindnessFlashAttempt);
  50. }
  51. private void OnFlashMeleeHit(EntityUid uid, FlashComponent comp, MeleeHitEvent args)
  52. {
  53. if (!args.IsHit ||
  54. !args.HitEntities.Any() ||
  55. !UseFlash(uid, comp, args.User))
  56. {
  57. return;
  58. }
  59. args.Handled = true;
  60. foreach (var e in args.HitEntities)
  61. {
  62. Flash(e, args.User, uid, comp.FlashDuration, comp.SlowTo, melee: true, stunDuration: comp.MeleeStunDuration);
  63. }
  64. }
  65. private void OnFlashUseInHand(EntityUid uid, FlashComponent comp, UseInHandEvent args)
  66. {
  67. if (args.Handled || !UseFlash(uid, comp, args.User))
  68. return;
  69. args.Handled = true;
  70. FlashArea(uid, args.User, comp.Range, comp.AoeFlashDuration, comp.SlowTo, true, comp.Probability);
  71. }
  72. private bool UseFlash(EntityUid uid, FlashComponent comp, EntityUid user)
  73. {
  74. if (comp.Flashing)
  75. return false;
  76. TryComp<LimitedChargesComponent>(uid, out var charges);
  77. if (_charges.IsEmpty(uid, charges))
  78. return false;
  79. _charges.UseCharge(uid, charges);
  80. _audio.PlayPvs(comp.Sound, uid);
  81. comp.Flashing = true;
  82. _appearance.SetData(uid, FlashVisuals.Flashing, true);
  83. if (_charges.IsEmpty(uid, charges))
  84. {
  85. _appearance.SetData(uid, FlashVisuals.Burnt, true);
  86. _tag.AddTag(uid, "Trash");
  87. _popup.PopupEntity(Loc.GetString("flash-component-becomes-empty"), user);
  88. }
  89. uid.SpawnTimer(400, () =>
  90. {
  91. _appearance.SetData(uid, FlashVisuals.Flashing, false);
  92. comp.Flashing = false;
  93. });
  94. return true;
  95. }
  96. public void Flash(EntityUid target,
  97. EntityUid? user,
  98. EntityUid? used,
  99. float flashDuration,
  100. float slowTo,
  101. bool displayPopup = true,
  102. bool melee = false,
  103. TimeSpan? stunDuration = null)
  104. {
  105. var attempt = new FlashAttemptEvent(target, user, used);
  106. RaiseLocalEvent(target, attempt, true);
  107. if (attempt.Cancelled)
  108. return;
  109. // don't paralyze, slowdown or convert to rev if the target is immune to flashes
  110. if (!_statusEffectsSystem.TryAddStatusEffect<FlashedComponent>(target, FlashedKey, TimeSpan.FromSeconds(flashDuration / 1000f), true))
  111. return;
  112. if (stunDuration != null)
  113. {
  114. _stun.TryParalyze(target, stunDuration.Value, true);
  115. }
  116. else
  117. {
  118. _stun.TrySlowdown(target, TimeSpan.FromSeconds(flashDuration / 1000f), true,
  119. slowTo, slowTo);
  120. }
  121. if (displayPopup && user != null && target != user && Exists(user.Value))
  122. {
  123. _popup.PopupEntity(Loc.GetString("flash-component-user-blinds-you",
  124. ("user", Identity.Entity(user.Value, EntityManager))), target, target);
  125. }
  126. if (melee)
  127. {
  128. var ev = new AfterFlashedEvent(target, user, used);
  129. if (user != null)
  130. RaiseLocalEvent(user.Value, ref ev);
  131. if (used != null)
  132. RaiseLocalEvent(used.Value, ref ev);
  133. }
  134. }
  135. public override void FlashArea(Entity<FlashComponent?> source, EntityUid? user, float range, float duration, float slowTo = 0.8f, bool displayPopup = false, float probability = 1f, SoundSpecifier? sound = null)
  136. {
  137. var transform = Transform(source);
  138. var mapPosition = _transform.GetMapCoordinates(transform);
  139. var statusEffectsQuery = GetEntityQuery<StatusEffectsComponent>();
  140. var damagedByFlashingQuery = GetEntityQuery<DamagedByFlashingComponent>();
  141. foreach (var entity in _entityLookup.GetEntitiesInRange(transform.Coordinates, range))
  142. {
  143. if (!_random.Prob(probability))
  144. continue;
  145. // Is the entity affected by the flash either through status effects or by taking damage?
  146. if (!statusEffectsQuery.HasComponent(entity) && !damagedByFlashingQuery.HasComponent(entity))
  147. continue;
  148. // Check for entites in view
  149. // put damagedByFlashingComponent in the predicate because shadow anomalies block vision.
  150. if (!_examine.InRangeUnOccluded(entity, mapPosition, range, predicate: (e) => damagedByFlashingQuery.HasComponent(e)))
  151. continue;
  152. // They shouldn't have flash removed in between right?
  153. Flash(entity, user, source, duration, slowTo, displayPopup);
  154. }
  155. _audio.PlayPvs(sound, source, AudioParams.Default.WithVolume(1f).WithMaxDistance(3f));
  156. }
  157. private void OnInventoryFlashAttempt(EntityUid uid, InventoryComponent component, FlashAttemptEvent args)
  158. {
  159. foreach (var slot in new[] { "head", "eyes", "mask" })
  160. {
  161. if (args.Cancelled)
  162. break;
  163. if (_inventory.TryGetSlotEntity(uid, slot, out var item, component))
  164. RaiseLocalEvent(item.Value, args, true);
  165. }
  166. }
  167. private void OnFlashImmunityFlashAttempt(EntityUid uid, FlashImmunityComponent component, FlashAttemptEvent args)
  168. {
  169. if (component.Enabled)
  170. args.Cancel();
  171. }
  172. private void OnPermanentBlindnessFlashAttempt(EntityUid uid, PermanentBlindnessComponent component, FlashAttemptEvent args)
  173. {
  174. // check for total blindness
  175. if (component.Blindness == 0)
  176. args.Cancel();
  177. }
  178. private void OnTemporaryBlindnessFlashAttempt(EntityUid uid, TemporaryBlindnessComponent component, FlashAttemptEvent args)
  179. {
  180. args.Cancel();
  181. }
  182. }
  183. /// <summary>
  184. /// Called before a flash is used to check if the attempt is cancelled by blindness, items or FlashImmunityComponent.
  185. /// Raised on the target hit by the flash, the user of the flash and the flash used.
  186. /// </summary>
  187. public sealed class FlashAttemptEvent : CancellableEntityEventArgs
  188. {
  189. public readonly EntityUid Target;
  190. public readonly EntityUid? User;
  191. public readonly EntityUid? Used;
  192. public FlashAttemptEvent(EntityUid target, EntityUid? user, EntityUid? used)
  193. {
  194. Target = target;
  195. User = user;
  196. Used = used;
  197. }
  198. }
  199. /// <summary>
  200. /// Called after a flash is used via melee on another person to check for rev conversion.
  201. /// Raised on the target hit by the flash, the user of the flash and the flash used.
  202. /// </summary>
  203. [ByRefEvent]
  204. public readonly struct AfterFlashedEvent
  205. {
  206. public readonly EntityUid Target;
  207. public readonly EntityUid? User;
  208. public readonly EntityUid? Used;
  209. public AfterFlashedEvent(EntityUid target, EntityUid? user, EntityUid? used)
  210. {
  211. Target = target;
  212. User = user;
  213. Used = used;
  214. }
  215. }
  216. }