EmagSystem.cs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. using Content.Shared.Administration.Logs;
  2. using Content.Shared.Charges.Components;
  3. using Content.Shared.Charges.Systems;
  4. using Content.Shared.Database;
  5. using Content.Shared.Emag.Components;
  6. using Content.Shared.IdentityManagement;
  7. using Content.Shared.Interaction;
  8. using Content.Shared.Popups;
  9. using Content.Shared.Tag;
  10. using Robust.Shared.Audio.Systems;
  11. using Robust.Shared.Serialization;
  12. namespace Content.Shared.Emag.Systems;
  13. /// How to add an emag interaction:
  14. /// 1. Go to the system for the component you want the interaction with
  15. /// 2. Subscribe to the GotEmaggedEvent
  16. /// 3. Have some check for if this actually needs to be emagged or is already emagged (to stop charge waste)
  17. /// 4. Past the check, add all the effects you desire and HANDLE THE EVENT ARGUMENT so a charge is spent
  18. /// 5. Optionally, set Repeatable on the event to true if you don't want the emagged component to be added
  19. public sealed class EmagSystem : EntitySystem
  20. {
  21. [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
  22. [Dependency] private readonly SharedChargesSystem _charges = default!;
  23. [Dependency] private readonly SharedPopupSystem _popup = default!;
  24. [Dependency] private readonly TagSystem _tag = default!;
  25. [Dependency] private readonly SharedAudioSystem _audio = default!;
  26. public override void Initialize()
  27. {
  28. base.Initialize();
  29. SubscribeLocalEvent<EmagComponent, AfterInteractEvent>(OnAfterInteract);
  30. SubscribeLocalEvent<EmaggedComponent, OnAccessOverriderAccessUpdatedEvent>(OnAccessOverriderAccessUpdated);
  31. }
  32. private void OnAccessOverriderAccessUpdated(Entity<EmaggedComponent> entity, ref OnAccessOverriderAccessUpdatedEvent args)
  33. {
  34. if (!CompareFlag(entity.Comp.EmagType, EmagType.Access))
  35. return;
  36. entity.Comp.EmagType &= ~EmagType.Access;
  37. Dirty(entity);
  38. }
  39. private void OnAfterInteract(EntityUid uid, EmagComponent comp, AfterInteractEvent args)
  40. {
  41. if (!args.CanReach || args.Target is not { } target)
  42. return;
  43. args.Handled = TryEmagEffect((uid, comp), args.User, target);
  44. }
  45. /// <summary>
  46. /// Does the emag effect on a specified entity
  47. /// </summary>
  48. public bool TryEmagEffect(Entity<EmagComponent?> ent, EntityUid user, EntityUid target)
  49. {
  50. if (!Resolve(ent, ref ent.Comp, false))
  51. return false;
  52. if (_tag.HasTag(target, ent.Comp.EmagImmuneTag))
  53. return false;
  54. TryComp<LimitedChargesComponent>(ent, out var charges);
  55. if (_charges.IsEmpty(ent, charges))
  56. {
  57. _popup.PopupClient(Loc.GetString("emag-no-charges"), user, user);
  58. return false;
  59. }
  60. var emaggedEvent = new GotEmaggedEvent(user, ent.Comp.EmagType);
  61. RaiseLocalEvent(target, ref emaggedEvent);
  62. if (!emaggedEvent.Handled)
  63. return false;
  64. _popup.PopupPredicted(Loc.GetString("emag-success", ("target", Identity.Entity(target, EntityManager))), user, user, PopupType.Medium);
  65. _audio.PlayPredicted(ent.Comp.EmagSound, ent, ent);
  66. _adminLogger.Add(LogType.Emag, LogImpact.High, $"{ToPrettyString(user):player} emagged {ToPrettyString(target):target} with flag(s): {ent.Comp.EmagType}");
  67. if (charges != null && emaggedEvent.Handled)
  68. _charges.UseCharge(ent, charges);
  69. if (!emaggedEvent.Repeatable)
  70. {
  71. EnsureComp<EmaggedComponent>(target, out var emaggedComp);
  72. emaggedComp.EmagType |= ent.Comp.EmagType;
  73. Dirty(target, emaggedComp);
  74. }
  75. return emaggedEvent.Handled;
  76. }
  77. /// <summary>
  78. /// Checks whether an entity has the EmaggedComponent with a set flag.
  79. /// </summary>
  80. /// <param name="target">The target entity to check for the flag.</param>
  81. /// <param name="flag">The EmagType flag to check for.</param>
  82. /// <returns>True if entity has EmaggedComponent and the provided flag. False if the entity lacks EmaggedComponent or provided flag.</returns>
  83. public bool CheckFlag(EntityUid target, EmagType flag)
  84. {
  85. if (!TryComp<EmaggedComponent>(target, out var comp))
  86. return false;
  87. if ((comp.EmagType & flag) == flag)
  88. return true;
  89. return false;
  90. }
  91. /// <summary>
  92. /// Compares a flag to the target.
  93. /// </summary>
  94. /// <param name="target">The target flag to check.</param>
  95. /// <param name="flag">The flag to check for within the target.</param>
  96. /// <returns>True if target contains flag. Otherwise false.</returns>
  97. public bool CompareFlag(EmagType target, EmagType flag)
  98. {
  99. if ((target & flag) == flag)
  100. return true;
  101. return false;
  102. }
  103. }
  104. [Flags]
  105. [Serializable, NetSerializable]
  106. public enum EmagType : byte
  107. {
  108. None = 0,
  109. Interaction = 1 << 1,
  110. Access = 1 << 2
  111. }
  112. /// <summary>
  113. /// Shows a popup to emag user (client side only!) and adds <see cref="EmaggedComponent"/> to the entity when handled
  114. /// </summary>
  115. /// <param name="UserUid">Emag user</param>
  116. /// <param name="Type">The emag type to use</param>
  117. /// <param name="Handled">Did the emagging succeed? Causes a user-only popup to show on client side</param>
  118. /// <param name="Repeatable">Can the entity be emagged more than once? Prevents adding of <see cref="EmaggedComponent"/></param>
  119. /// <remarks>Needs to be handled in shared/client, not just the server, to actually show the emagging popup</remarks>
  120. [ByRefEvent]
  121. public record struct GotEmaggedEvent(EntityUid UserUid, EmagType Type, bool Handled = false, bool Repeatable = false);