DefusableSystem.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. using Content.Server.Defusable.Components;
  2. using Content.Server.Explosion.Components;
  3. using Content.Server.Explosion.EntitySystems;
  4. using Content.Server.Popups;
  5. using Content.Server.Wires;
  6. using Content.Shared.Administration.Logs;
  7. using Content.Shared.Construction.Components;
  8. using Content.Shared.Database;
  9. using Content.Shared.Defusable;
  10. using Content.Shared.Examine;
  11. using Content.Shared.Explosion.Components;
  12. using Content.Shared.Explosion.Components.OnTrigger;
  13. using Content.Shared.Popups;
  14. using Content.Shared.Verbs;
  15. using Content.Shared.Wires;
  16. using Robust.Server.GameObjects;
  17. using Robust.Shared.Audio;
  18. using Robust.Shared.Audio.Systems;
  19. namespace Content.Server.Defusable.Systems;
  20. /// <inheritdoc/>
  21. public sealed class DefusableSystem : SharedDefusableSystem
  22. {
  23. [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
  24. [Dependency] private readonly ExplosionSystem _explosion = default!;
  25. [Dependency] private readonly PopupSystem _popup = default!;
  26. [Dependency] private readonly TriggerSystem _trigger = default!;
  27. [Dependency] private readonly SharedAudioSystem _audio = default!;
  28. [Dependency] private readonly TransformSystem _transform = default!;
  29. [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
  30. [Dependency] private readonly WiresSystem _wiresSystem = default!;
  31. /// <inheritdoc/>
  32. public override void Initialize()
  33. {
  34. base.Initialize();
  35. SubscribeLocalEvent<DefusableComponent, ExaminedEvent>(OnExamine);
  36. SubscribeLocalEvent<DefusableComponent, GetVerbsEvent<AlternativeVerb>>(OnGetAltVerbs);
  37. SubscribeLocalEvent<DefusableComponent, AnchorAttemptEvent>(OnAnchorAttempt);
  38. SubscribeLocalEvent<DefusableComponent, UnanchorAttemptEvent>(OnUnanchorAttempt);
  39. }
  40. #region Subscribed Events
  41. /// <summary>
  42. /// Adds a verb allowing for the bomb to be started easily.
  43. /// </summary>
  44. private void OnGetAltVerbs(EntityUid uid, DefusableComponent comp, GetVerbsEvent<AlternativeVerb> args)
  45. {
  46. if (!args.CanInteract || !args.CanAccess || args.Hands == null)
  47. return;
  48. args.Verbs.Add(new AlternativeVerb
  49. {
  50. Text = Loc.GetString("defusable-verb-begin"),
  51. Disabled = comp is { Activated: true, Usable: true },
  52. Priority = 10,
  53. Act = () =>
  54. {
  55. TryStartCountdown(uid, args.User, comp);
  56. }
  57. });
  58. }
  59. private void OnExamine(EntityUid uid, DefusableComponent comp, ExaminedEvent args)
  60. {
  61. if (!args.IsInDetailsRange)
  62. return;
  63. using (args.PushGroup(nameof(DefusableComponent)))
  64. {
  65. if (!comp.Usable)
  66. {
  67. args.PushMarkup(Loc.GetString("defusable-examine-defused", ("name", uid)));
  68. }
  69. else if (comp.Activated && TryComp<ActiveTimerTriggerComponent>(uid, out var activeComp))
  70. {
  71. if (comp.DisplayTime)
  72. {
  73. args.PushMarkup(Loc.GetString("defusable-examine-live", ("name", uid),
  74. ("time", MathF.Floor(activeComp.TimeRemaining))));
  75. }
  76. else
  77. {
  78. args.PushMarkup(Loc.GetString("defusable-examine-live-display-off", ("name", uid)));
  79. }
  80. }
  81. else
  82. {
  83. args.PushMarkup(Loc.GetString("defusable-examine-inactive", ("name", uid)));
  84. }
  85. }
  86. args.PushMarkup(Loc.GetString("defusable-examine-bolts", ("down", comp.Bolted)));
  87. }
  88. private void OnAnchorAttempt(EntityUid uid, DefusableComponent component, AnchorAttemptEvent args)
  89. {
  90. if (CheckAnchorAttempt(uid, component, args))
  91. args.Cancel();
  92. }
  93. private void OnUnanchorAttempt(EntityUid uid, DefusableComponent component, UnanchorAttemptEvent args)
  94. {
  95. if (CheckAnchorAttempt(uid, component, args))
  96. args.Cancel();
  97. }
  98. private bool CheckAnchorAttempt(EntityUid uid, DefusableComponent component, BaseAnchoredAttemptEvent args)
  99. {
  100. // Don't allow the thing to be anchored if bolted to the ground
  101. if (!component.Bolted)
  102. return false;
  103. var msg = Loc.GetString("defusable-popup-cant-anchor", ("name", uid));
  104. _popup.PopupEntity(msg, uid, args.User);
  105. return true;
  106. }
  107. #endregion
  108. #region Public
  109. public void TryStartCountdown(EntityUid uid, EntityUid user, DefusableComponent comp)
  110. {
  111. if (!comp.Usable)
  112. {
  113. _popup.PopupEntity(Loc.GetString("defusable-popup-fried", ("name", uid)), uid);
  114. return;
  115. }
  116. var xform = Transform(uid);
  117. if (!xform.Anchored)
  118. _transform.AnchorEntity(uid, xform);
  119. SetBolt(comp, true);
  120. SetActivated(comp, true);
  121. _popup.PopupEntity(Loc.GetString("defusable-popup-begun", ("name", uid)), uid);
  122. if (TryComp<OnUseTimerTriggerComponent>(uid, out var timerTrigger))
  123. {
  124. _trigger.HandleTimerTrigger(
  125. uid,
  126. user,
  127. timerTrigger.Delay,
  128. timerTrigger.BeepInterval,
  129. timerTrigger.InitialBeepDelay,
  130. timerTrigger.BeepSound
  131. );
  132. }
  133. RaiseLocalEvent(uid, new BombArmedEvent(uid));
  134. _appearance.SetData(uid, DefusableVisuals.Active, comp.Activated);
  135. if (TryComp<WiresPanelComponent>(uid, out var wiresPanelComponent))
  136. _wiresSystem.TogglePanel(uid, wiresPanelComponent, false);
  137. }
  138. public void TryDetonateBomb(EntityUid uid, EntityUid detonator, DefusableComponent comp)
  139. {
  140. if (!comp.Activated)
  141. return;
  142. _popup.PopupEntity(Loc.GetString("defusable-popup-boom", ("name", uid)), uid, PopupType.LargeCaution);
  143. RaiseLocalEvent(uid, new BombDetonatedEvent(uid));
  144. _explosion.TriggerExplosive(uid, user:detonator);
  145. QueueDel(uid);
  146. _appearance.SetData(uid, DefusableVisuals.Active, comp.Activated);
  147. }
  148. public void TryDefuseBomb(EntityUid uid, DefusableComponent comp)
  149. {
  150. if (!comp.Activated)
  151. return;
  152. _popup.PopupEntity(Loc.GetString("defusable-popup-defuse", ("name", uid)), uid);
  153. SetActivated(comp, false);
  154. var xform = Transform(uid);
  155. if (comp.Disposable)
  156. {
  157. SetUsable(comp, false);
  158. RemComp<ExplodeOnTriggerComponent>(uid);
  159. RemComp<OnUseTimerTriggerComponent>(uid);
  160. }
  161. RemComp<ActiveTimerTriggerComponent>(uid);
  162. _audio.PlayPvs(comp.DefusalSound, uid);
  163. RaiseLocalEvent(uid, new BombDefusedEvent(uid));
  164. comp.ActivatedWireUsed = false;
  165. comp.DelayWireUsed = false;
  166. comp.ProceedWireCut = false;
  167. comp.ProceedWireUsed = false;
  168. comp.Bolted = false;
  169. if (xform.Anchored)
  170. _transform.Unanchor(uid, xform);
  171. _appearance.SetData(uid, DefusableVisuals.Active, comp.Activated);
  172. }
  173. // jesus christ
  174. public void SetUsable(DefusableComponent component, bool value)
  175. {
  176. component.Usable = value;
  177. }
  178. public void SetDisplayTime(DefusableComponent component, bool value)
  179. {
  180. component.DisplayTime = value;
  181. }
  182. /// <summary>
  183. /// Sets the Activated value of a component to a value.
  184. /// </summary>
  185. /// <param name="component"></param>
  186. /// <param name="value"></param>
  187. /// <remarks>
  188. /// Use <see cref="TryDefuseBomb"/> to defuse bomb. This is a setter.
  189. /// </remarks>
  190. public void SetActivated(DefusableComponent component, bool value)
  191. {
  192. component.Activated = value;
  193. }
  194. public void SetBolt(DefusableComponent component, bool value)
  195. {
  196. component.Bolted = value;
  197. }
  198. #endregion
  199. #region Wires
  200. public void DelayWirePulse(EntityUid user, Wire wire, DefusableComponent comp)
  201. {
  202. if (comp is not { Activated: true, DelayWireUsed: false })
  203. return;
  204. _trigger.TryDelay(wire.Owner, 30f);
  205. _popup.PopupEntity(Loc.GetString("defusable-popup-wire-chirp", ("name", wire.Owner)), wire.Owner);
  206. comp.DelayWireUsed = true;
  207. }
  208. public bool ProceedWireCut(EntityUid user, Wire wire, DefusableComponent comp)
  209. {
  210. if (comp is not { Activated: true, ProceedWireCut: false })
  211. return true;
  212. _popup.PopupEntity(Loc.GetString("defusable-popup-wire-proceed-pulse", ("name", wire.Owner)), wire.Owner);
  213. SetDisplayTime(comp, false);
  214. comp.ProceedWireCut = true;
  215. return true;
  216. }
  217. public void ProceedWirePulse(EntityUid user, Wire wire, DefusableComponent comp)
  218. {
  219. if (comp is { Activated: true, ProceedWireUsed: false })
  220. {
  221. comp.ProceedWireUsed = true;
  222. _trigger.TryDelay(wire.Owner, -15f);
  223. }
  224. _popup.PopupEntity(Loc.GetString("defusable-popup-wire-proceed-pulse", ("name", wire.Owner)), wire.Owner);
  225. }
  226. public bool ActivateWireCut(EntityUid user, Wire wire, DefusableComponent comp)
  227. {
  228. // if you cut the wire it just defuses the bomb
  229. if (comp.Activated)
  230. {
  231. TryDefuseBomb(wire.Owner, comp);
  232. _adminLogger.Add(LogType.Explosion, LogImpact.High,
  233. $"{ToPrettyString(user):user} has defused {ToPrettyString(wire.Owner):entity}!");
  234. }
  235. return true;
  236. }
  237. public void ActivateWirePulse(EntityUid user, Wire wire, DefusableComponent comp)
  238. {
  239. // if the component isnt active, just start the countdown
  240. // if it is and it isn't already used then delay it
  241. if (comp.Activated)
  242. {
  243. if (!comp.ActivatedWireUsed)
  244. {
  245. _trigger.TryDelay(wire.Owner, 30f);
  246. _popup.PopupEntity(Loc.GetString("defusable-popup-wire-chirp", ("name", wire.Owner)), wire.Owner);
  247. comp.ActivatedWireUsed = true;
  248. }
  249. }
  250. else
  251. {
  252. TryStartCountdown(wire.Owner, user, comp);
  253. }
  254. }
  255. public bool BoomWireCut(EntityUid user, Wire wire, DefusableComponent comp)
  256. {
  257. if (comp.Activated)
  258. {
  259. TryDetonateBomb(wire.Owner, user, comp);
  260. }
  261. else
  262. {
  263. SetUsable(comp, false);
  264. }
  265. return true;
  266. }
  267. public bool BoomWireMend(EntityUid user, Wire wire, DefusableComponent comp)
  268. {
  269. if (comp is { Activated: false, Usable: false })
  270. {
  271. SetUsable(comp, true);
  272. }
  273. // you're already dead lol
  274. return true;
  275. }
  276. public void BoomWirePulse(EntityUid user, Wire wire, DefusableComponent comp)
  277. {
  278. if (comp.Activated)
  279. {
  280. TryDetonateBomb(wire.Owner, user, comp);
  281. }
  282. }
  283. public bool BoltWireMend(EntityUid user, Wire wire, DefusableComponent comp)
  284. {
  285. if (!comp.Activated)
  286. return true;
  287. SetBolt(comp, true);
  288. _audio.PlayPvs(comp.BoltSound, wire.Owner);
  289. _popup.PopupEntity(Loc.GetString("defusable-popup-wire-bolt-pulse", ("name", wire.Owner)), wire.Owner);
  290. return true;
  291. }
  292. public bool BoltWireCut(EntityUid user, Wire wire, DefusableComponent comp)
  293. {
  294. if (!comp.Activated)
  295. return true;
  296. SetBolt(comp, false);
  297. _audio.PlayPvs(comp.BoltSound, wire.Owner);
  298. _popup.PopupEntity(Loc.GetString("defusable-popup-wire-bolt-pulse", ("name", wire.Owner)), wire.Owner);
  299. return true;
  300. }
  301. public void BoltWirePulse(EntityUid user, Wire wire, DefusableComponent comp)
  302. {
  303. _popup.PopupEntity(Loc.GetString("defusable-popup-wire-bolt-pulse", ("name", wire.Owner)), wire.Owner);
  304. }
  305. #endregion
  306. }
  307. public sealed class BombDefusedEvent : EntityEventArgs
  308. {
  309. public EntityUid Entity;
  310. public BombDefusedEvent(EntityUid entity)
  311. {
  312. Entity = entity;
  313. }
  314. }
  315. public sealed class BombArmedEvent : EntityEventArgs
  316. {
  317. public EntityUid Entity;
  318. public BombArmedEvent(EntityUid entity)
  319. {
  320. Entity = entity;
  321. }
  322. }
  323. public sealed class BombDetonatedEvent : EntityEventArgs
  324. {
  325. public EntityUid Entity;
  326. public BombDetonatedEvent(EntityUid entity)
  327. {
  328. Entity = entity;
  329. }
  330. }