1
0

NukeSystem.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663
  1. using Content.Server.AlertLevel;
  2. using Content.Server.Audio;
  3. using Content.Server.Chat.Systems;
  4. using Content.Server.Explosion.EntitySystems;
  5. using Content.Server.Pinpointer;
  6. using Content.Server.Popups;
  7. using Content.Server.Station.Systems;
  8. using Content.Shared.Audio;
  9. using Content.Shared.Containers.ItemSlots;
  10. using Content.Shared.Coordinates.Helpers;
  11. using Content.Shared.DoAfter;
  12. using Content.Shared.Examine;
  13. using Content.Shared.Maps;
  14. using Content.Shared.Nuke;
  15. using Content.Shared.Popups;
  16. using Robust.Server.GameObjects;
  17. using Robust.Shared.Audio;
  18. using Robust.Shared.Audio.Systems;
  19. using Robust.Shared.Containers;
  20. using Robust.Shared.Map;
  21. using Robust.Shared.Map.Components;
  22. using Robust.Shared.Player;
  23. using Robust.Shared.Random;
  24. using Robust.Shared.Utility;
  25. namespace Content.Server.Nuke;
  26. public sealed class NukeSystem : EntitySystem
  27. {
  28. [Dependency] private readonly AlertLevelSystem _alertLevel = default!;
  29. [Dependency] private readonly ChatSystem _chatSystem = default!;
  30. [Dependency] private readonly ExplosionSystem _explosions = default!;
  31. [Dependency] private readonly IRobustRandom _random = default!;
  32. [Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
  33. [Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
  34. [Dependency] private readonly NavMapSystem _navMap = default!;
  35. [Dependency] private readonly PointLightSystem _pointLight = default!;
  36. [Dependency] private readonly PopupSystem _popups = default!;
  37. [Dependency] private readonly ServerGlobalSoundSystem _sound = default!;
  38. [Dependency] private readonly SharedAudioSystem _audio = default!;
  39. [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
  40. [Dependency] private readonly SharedTransformSystem _transform = default!;
  41. [Dependency] private readonly SharedMapSystem _map = default!;
  42. [Dependency] private readonly StationSystem _station = default!;
  43. [Dependency] private readonly UserInterfaceSystem _ui = default!;
  44. [Dependency] private readonly AppearanceSystem _appearance = default!;
  45. /// <summary>
  46. /// Used to calculate when the nuke song should start playing for maximum kino with the nuke sfx
  47. /// </summary>
  48. private float _nukeSongLength;
  49. private ResolvedSoundSpecifier _selectedNukeSong = String.Empty;
  50. /// <summary>
  51. /// Time to leave between the nuke song and the nuke alarm playing.
  52. /// </summary>
  53. private const float NukeSongBuffer = 1.5f;
  54. public override void Initialize()
  55. {
  56. base.Initialize();
  57. SubscribeLocalEvent<NukeComponent, ComponentInit>(OnInit);
  58. SubscribeLocalEvent<NukeComponent, ComponentRemove>(OnRemove);
  59. SubscribeLocalEvent<NukeComponent, MapInitEvent>(OnMapInit);
  60. SubscribeLocalEvent<NukeComponent, EntInsertedIntoContainerMessage>(OnItemSlotChanged);
  61. SubscribeLocalEvent<NukeComponent, EntRemovedFromContainerMessage>(OnItemSlotChanged);
  62. SubscribeLocalEvent<NukeComponent, ExaminedEvent>(OnExaminedEvent);
  63. // Shouldn't need re-anchoring.
  64. SubscribeLocalEvent<NukeComponent, AnchorStateChangedEvent>(OnAnchorChanged);
  65. // ui events
  66. SubscribeLocalEvent<NukeComponent, NukeAnchorMessage>(OnAnchorButtonPressed);
  67. SubscribeLocalEvent<NukeComponent, NukeArmedMessage>(OnArmButtonPressed);
  68. SubscribeLocalEvent<NukeComponent, NukeKeypadMessage>(OnKeypadButtonPressed);
  69. SubscribeLocalEvent<NukeComponent, NukeKeypadClearMessage>(OnClearButtonPressed);
  70. SubscribeLocalEvent<NukeComponent, NukeKeypadEnterMessage>(OnEnterButtonPressed);
  71. // Doafter events
  72. SubscribeLocalEvent<NukeComponent, NukeDisarmDoAfterEvent>(OnDoAfter);
  73. }
  74. private void OnInit(EntityUid uid, NukeComponent component, ComponentInit args)
  75. {
  76. component.RemainingTime = component.Timer;
  77. _itemSlots.AddItemSlot(uid, SharedNukeComponent.NukeDiskSlotId, component.DiskSlot);
  78. UpdateStatus(uid, component);
  79. UpdateUserInterface(uid, component);
  80. }
  81. public override void Update(float frameTime)
  82. {
  83. base.Update(frameTime);
  84. var query = EntityQueryEnumerator<NukeComponent>();
  85. while (query.MoveNext(out var uid, out var nuke))
  86. {
  87. switch (nuke.Status)
  88. {
  89. case NukeStatus.ARMED:
  90. TickTimer(uid, frameTime, nuke);
  91. break;
  92. case NukeStatus.COOLDOWN:
  93. TickCooldown(uid, frameTime, nuke);
  94. break;
  95. }
  96. }
  97. }
  98. private void OnMapInit(EntityUid uid, NukeComponent nuke, MapInitEvent args)
  99. {
  100. var originStation = _station.GetOwningStation(uid);
  101. if (originStation != null)
  102. nuke.OriginStation = originStation;
  103. else
  104. {
  105. var transform = Transform(uid);
  106. nuke.OriginMapGrid = (transform.MapID, transform.GridUid);
  107. }
  108. nuke.Code = GenerateRandomNumberString(nuke.CodeLength);
  109. }
  110. private void OnRemove(EntityUid uid, NukeComponent component, ComponentRemove args)
  111. {
  112. _itemSlots.RemoveItemSlot(uid, component.DiskSlot);
  113. }
  114. private void OnItemSlotChanged(EntityUid uid, NukeComponent component, ContainerModifiedMessage args)
  115. {
  116. if (!component.Initialized)
  117. return;
  118. if (args.Container.ID != component.DiskSlot.ID)
  119. return;
  120. UpdateStatus(uid, component);
  121. UpdateUserInterface(uid, component);
  122. }
  123. #region Anchor
  124. private void OnAnchorChanged(EntityUid uid, NukeComponent component, ref AnchorStateChangedEvent args)
  125. {
  126. UpdateUserInterface(uid, component);
  127. if (args.Anchored == false && component.Status == NukeStatus.ARMED && component.RemainingTime > component.DisarmDoafterLength)
  128. {
  129. // yes, this means technically if you can find a way to unanchor the nuke, you can disarm it
  130. // without the doafter. but that takes some effort, and it won't allow you to disarm a nuke that can't be disarmed by the doafter.
  131. DisarmBomb(uid, component);
  132. }
  133. UpdateAppearance(uid, component);
  134. }
  135. #endregion
  136. #region UI Events
  137. private async void OnAnchorButtonPressed(EntityUid uid, NukeComponent component, NukeAnchorMessage args)
  138. {
  139. // malicious client sanity check
  140. if (component.Status == NukeStatus.ARMED)
  141. return;
  142. // Nuke has to have the disk in it to be moved
  143. if (!component.DiskSlot.HasItem)
  144. {
  145. var msg = Loc.GetString("nuke-component-cant-anchor-toggle");
  146. _popups.PopupEntity(msg, uid, args.Actor, PopupType.MediumCaution);
  147. return;
  148. }
  149. // manually set transform anchor (bypassing anchorable)
  150. // todo: it will break pullable system
  151. var xform = Transform(uid);
  152. if (xform.Anchored)
  153. {
  154. _transform.Unanchor(uid, xform);
  155. _itemSlots.SetLock(uid, component.DiskSlot, true);
  156. }
  157. else
  158. {
  159. if (!TryComp<MapGridComponent>(xform.GridUid, out var grid))
  160. return;
  161. var worldPos = _transform.GetWorldPosition(xform);
  162. foreach (var tile in _map.GetTilesIntersecting(xform.GridUid.Value, grid, new Circle(worldPos, component.RequiredFloorRadius), false))
  163. {
  164. if (!tile.IsSpace(_tileDefManager))
  165. continue;
  166. var msg = Loc.GetString("nuke-component-cant-anchor-floor");
  167. _popups.PopupEntity(msg, uid, args.Actor, PopupType.MediumCaution);
  168. return;
  169. }
  170. _transform.SetCoordinates(uid, xform, xform.Coordinates.SnapToGrid());
  171. _transform.AnchorEntity(uid, xform);
  172. _itemSlots.SetLock(uid, component.DiskSlot, false);
  173. }
  174. UpdateUserInterface(uid, component);
  175. }
  176. private void OnEnterButtonPressed(EntityUid uid, NukeComponent component, NukeKeypadEnterMessage args)
  177. {
  178. if (component.Status != NukeStatus.AWAIT_CODE)
  179. return;
  180. UpdateStatus(uid, component);
  181. UpdateUserInterface(uid, component);
  182. }
  183. private void OnKeypadButtonPressed(EntityUid uid, NukeComponent component, NukeKeypadMessage args)
  184. {
  185. PlayNukeKeypadSound(uid, args.Value, component);
  186. if (component.Status != NukeStatus.AWAIT_CODE)
  187. return;
  188. if (component.EnteredCode.Length >= component.CodeLength)
  189. return;
  190. component.EnteredCode += args.Value.ToString();
  191. UpdateUserInterface(uid, component);
  192. }
  193. private void OnClearButtonPressed(EntityUid uid, NukeComponent component, NukeKeypadClearMessage args)
  194. {
  195. _audio.PlayPvs(component.KeypadPressSound, uid);
  196. if (component.Status != NukeStatus.AWAIT_CODE)
  197. return;
  198. component.EnteredCode = "";
  199. UpdateUserInterface(uid, component);
  200. }
  201. private void OnArmButtonPressed(EntityUid uid, NukeComponent component, NukeArmedMessage args)
  202. {
  203. if (!component.DiskSlot.HasItem)
  204. return;
  205. if (component.Status == NukeStatus.AWAIT_ARM && Transform(uid).Anchored)
  206. ArmBomb(uid, component);
  207. else
  208. {
  209. DisarmBombDoafter(uid, args.Actor, component);
  210. }
  211. }
  212. #endregion
  213. #region Doafter Events
  214. private void OnDoAfter(EntityUid uid, NukeComponent component, DoAfterEvent args)
  215. {
  216. if (args.Handled || args.Cancelled)
  217. return;
  218. DisarmBomb(uid, component);
  219. var ev = new NukeDisarmSuccessEvent();
  220. RaiseLocalEvent(ev);
  221. args.Handled = true;
  222. }
  223. #endregion
  224. private void TickCooldown(EntityUid uid, float frameTime, NukeComponent? nuke = null)
  225. {
  226. if (!Resolve(uid, ref nuke))
  227. return;
  228. nuke.CooldownTime -= frameTime;
  229. if (nuke.CooldownTime <= 0)
  230. {
  231. // reset nuke to default state
  232. nuke.CooldownTime = 0;
  233. nuke.Status = NukeStatus.AWAIT_ARM;
  234. UpdateStatus(uid, nuke);
  235. }
  236. UpdateUserInterface(uid, nuke);
  237. }
  238. private void TickTimer(EntityUid uid, float frameTime, NukeComponent? nuke = null)
  239. {
  240. if (!Resolve(uid, ref nuke))
  241. return;
  242. nuke.RemainingTime -= frameTime;
  243. // Start playing the nuke event song so that it ends a couple seconds before the alert sound
  244. // should play
  245. if (nuke.RemainingTime <= _nukeSongLength + nuke.AlertSoundTime + NukeSongBuffer && !nuke.PlayedNukeSong && !ResolvedSoundSpecifier.IsNullOrEmpty(_selectedNukeSong))
  246. {
  247. _sound.DispatchStationEventMusic(uid, _selectedNukeSong, StationEventMusicType.Nuke);
  248. nuke.PlayedNukeSong = true;
  249. }
  250. // play alert sound if time is running out
  251. if (nuke.RemainingTime <= nuke.AlertSoundTime && !nuke.PlayedAlertSound)
  252. {
  253. _sound.PlayGlobalOnStation(uid, _audio.ResolveSound(nuke.AlertSound), new AudioParams{Volume = -5f});
  254. _sound.StopStationEventMusic(uid, StationEventMusicType.Nuke);
  255. nuke.PlayedAlertSound = true;
  256. UpdateAppearance(uid, nuke);
  257. }
  258. if (nuke.RemainingTime <= 0)
  259. {
  260. nuke.RemainingTime = 0;
  261. ActivateBomb(uid, nuke);
  262. }
  263. else
  264. UpdateUserInterface(uid, nuke);
  265. }
  266. private void UpdateStatus(EntityUid uid, NukeComponent? component = null)
  267. {
  268. if (!Resolve(uid, ref component))
  269. return;
  270. switch (component.Status)
  271. {
  272. case NukeStatus.AWAIT_DISK:
  273. if (component.DiskSlot.HasItem)
  274. component.Status = NukeStatus.AWAIT_CODE;
  275. break;
  276. case NukeStatus.AWAIT_CODE:
  277. if (!component.DiskSlot.HasItem)
  278. {
  279. component.Status = NukeStatus.AWAIT_DISK;
  280. component.EnteredCode = "";
  281. break;
  282. }
  283. // var isValid = _codes.IsCodeValid(uid, component.EnteredCode);
  284. if (component.EnteredCode == component.Code)
  285. {
  286. component.Status = NukeStatus.AWAIT_ARM;
  287. component.RemainingTime = component.Timer;
  288. _audio.PlayPvs(component.AccessGrantedSound, uid);
  289. }
  290. else
  291. {
  292. component.EnteredCode = "";
  293. _audio.PlayPvs(component.AccessDeniedSound, uid);
  294. }
  295. break;
  296. case NukeStatus.AWAIT_ARM:
  297. // do nothing, wait for arm button to be pressed
  298. break;
  299. case NukeStatus.ARMED:
  300. // do nothing, wait for arm button to be unpressed
  301. break;
  302. }
  303. }
  304. private void UpdateUserInterface(EntityUid uid, NukeComponent? component = null)
  305. {
  306. if (!Resolve(uid, ref component))
  307. return;
  308. if (!_ui.HasUi(uid, NukeUiKey.Key))
  309. return;
  310. var anchored = Transform(uid).Anchored;
  311. var allowArm = component.DiskSlot.HasItem &&
  312. (component.Status == NukeStatus.AWAIT_ARM ||
  313. component.Status == NukeStatus.ARMED);
  314. var state = new NukeUiState
  315. {
  316. Status = component.Status,
  317. RemainingTime = (int) component.RemainingTime,
  318. DiskInserted = component.DiskSlot.HasItem,
  319. IsAnchored = anchored,
  320. AllowArm = allowArm,
  321. EnteredCodeLength = component.EnteredCode.Length,
  322. MaxCodeLength = component.CodeLength,
  323. CooldownTime = (int) component.CooldownTime
  324. };
  325. _ui.SetUiState(uid, NukeUiKey.Key, state);
  326. }
  327. private void PlayNukeKeypadSound(EntityUid uid, int number, NukeComponent? component = null)
  328. {
  329. if (!Resolve(uid, ref component))
  330. return;
  331. // This is a C mixolydian blues scale.
  332. // 1 2 3 C D Eb
  333. // 4 5 6 E F F#
  334. // 7 8 9 G A Bb
  335. var semitoneShift = number switch
  336. {
  337. 1 => 0,
  338. 2 => 2,
  339. 3 => 3,
  340. 4 => 4,
  341. 5 => 5,
  342. 6 => 6,
  343. 7 => 7,
  344. 8 => 9,
  345. 9 => 10,
  346. 0 => component.LastPlayedKeypadSemitones + 12,
  347. _ => 0
  348. };
  349. // Don't double-dip on the octave shifting
  350. component.LastPlayedKeypadSemitones = number == 0 ? component.LastPlayedKeypadSemitones : semitoneShift;
  351. var opts = component.KeypadPressSound.Params;
  352. opts = AudioHelpers.ShiftSemitone(opts, semitoneShift).AddVolume(-5f);
  353. _audio.PlayPvs(component.KeypadPressSound, uid, opts);
  354. }
  355. public string GenerateRandomNumberString(int length)
  356. {
  357. var ret = "";
  358. for (var i = 0; i < length; i++)
  359. {
  360. var c = (char) _random.Next('0', '9' + 1);
  361. ret += c;
  362. }
  363. return ret;
  364. }
  365. #region Public API
  366. /// <summary>
  367. /// Force a nuclear bomb to start a countdown timer
  368. /// </summary>
  369. public void ArmBomb(EntityUid uid, NukeComponent? component = null)
  370. {
  371. if (!Resolve(uid, ref component))
  372. return;
  373. if (component.Status == NukeStatus.ARMED)
  374. return;
  375. var nukeXform = Transform(uid);
  376. var stationUid = _station.GetStationInMap(nukeXform.MapID);
  377. // The nuke may not be on a station, so it's more important to just
  378. // let people know that a nuclear bomb was armed in their vicinity instead.
  379. // Otherwise, you could set every station to whatever AlertLevelOnActivate is.
  380. if (stationUid != null)
  381. _alertLevel.SetLevel(stationUid.Value, component.AlertLevelOnActivate, true, true, true, true);
  382. var pos = _transform.GetMapCoordinates(uid, xform: nukeXform);
  383. var x = (int) pos.X;
  384. var y = (int) pos.Y;
  385. var posText = $"({x}, {y})";
  386. // We are collapsing the randomness here, otherwise we would get separate random song picks for checking duration and when actually playing the song afterwards
  387. _selectedNukeSong = _audio.ResolveSound(component.ArmMusic);
  388. // warn a crew
  389. var announcement = Loc.GetString("nuke-component-announcement-armed",
  390. ("time", (int) component.RemainingTime),
  391. ("location", FormattedMessage.RemoveMarkupOrThrow(_navMap.GetNearestBeaconString((uid, nukeXform)))));
  392. var sender = Loc.GetString("nuke-component-announcement-sender");
  393. _chatSystem.DispatchStationAnnouncement(stationUid ?? uid, announcement, sender, false, null, Color.Red);
  394. _sound.PlayGlobalOnStation(uid, _audio.ResolveSound(component.ArmSound));
  395. _nukeSongLength = (float) _audio.GetAudioLength(_selectedNukeSong).TotalSeconds;
  396. // turn on the spinny light
  397. _pointLight.SetEnabled(uid, true);
  398. // enable the navmap beacon for people to find it
  399. _navMap.SetBeaconEnabled(uid, true);
  400. _itemSlots.SetLock(uid, component.DiskSlot, true);
  401. if (!nukeXform.Anchored)
  402. {
  403. // Admin command shenanigans, just make sure.
  404. _transform.AnchorEntity(uid, nukeXform);
  405. }
  406. component.Status = NukeStatus.ARMED;
  407. UpdateUserInterface(uid, component);
  408. UpdateAppearance(uid, component);
  409. }
  410. /// <summary>
  411. /// Stop nuclear bomb timer
  412. /// </summary>
  413. public void DisarmBomb(EntityUid uid, NukeComponent? component = null)
  414. {
  415. if (!Resolve(uid, ref component))
  416. return;
  417. if (component.Status != NukeStatus.ARMED)
  418. return;
  419. var stationUid = _station.GetOwningStation(uid);
  420. if (stationUid != null)
  421. _alertLevel.SetLevel(stationUid.Value, component.AlertLevelOnDeactivate, true, true, true);
  422. // warn a crew
  423. var announcement = Loc.GetString("nuke-component-announcement-unarmed");
  424. var sender = Loc.GetString("nuke-component-announcement-sender");
  425. _chatSystem.DispatchStationAnnouncement(uid, announcement, sender, false);
  426. component.PlayedNukeSong = false;
  427. _sound.PlayGlobalOnStation(uid, _audio.ResolveSound(component.DisarmSound));
  428. _sound.StopStationEventMusic(uid, StationEventMusicType.Nuke);
  429. // reset nuke remaining time to either itself or the minimum time, whichever is higher
  430. component.RemainingTime = Math.Max(component.RemainingTime, component.MinimumTime);
  431. // disable sound and reset it
  432. component.PlayedAlertSound = false;
  433. component.AlertAudioStream = _audio.Stop(component.AlertAudioStream);
  434. // turn off the spinny light
  435. _pointLight.SetEnabled(uid, false);
  436. // disable the navmap beacon now that its disarmed
  437. _navMap.SetBeaconEnabled(uid, false);
  438. // start bomb cooldown
  439. _itemSlots.SetLock(uid, component.DiskSlot, false);
  440. component.Status = NukeStatus.COOLDOWN;
  441. component.CooldownTime = component.Cooldown;
  442. UpdateUserInterface(uid, component);
  443. UpdateAppearance(uid, component);
  444. }
  445. /// <summary>
  446. /// Toggle bomb arm button
  447. /// </summary>
  448. public void ToggleBomb(EntityUid uid, NukeComponent? component = null)
  449. {
  450. if (!Resolve(uid, ref component))
  451. return;
  452. if (component.Status == NukeStatus.ARMED)
  453. DisarmBomb(uid, component);
  454. else
  455. ArmBomb(uid, component);
  456. }
  457. /// <summary>
  458. /// Force bomb to explode immediately
  459. /// </summary>
  460. public void ActivateBomb(EntityUid uid, NukeComponent? component = null,
  461. TransformComponent? transform = null)
  462. {
  463. if (!Resolve(uid, ref component, ref transform))
  464. return;
  465. if (component.Exploded)
  466. return;
  467. component.Exploded = true;
  468. _explosions.QueueExplosion(uid,
  469. component.ExplosionType,
  470. component.TotalIntensity,
  471. component.IntensitySlope,
  472. component.MaxIntensity);
  473. RaiseLocalEvent(new NukeExplodedEvent()
  474. {
  475. OwningStation = transform.GridUid,
  476. });
  477. _sound.StopStationEventMusic(uid, StationEventMusicType.Nuke);
  478. Del(uid);
  479. }
  480. /// <summary>
  481. /// Set remaining time value
  482. /// </summary>
  483. public void SetRemainingTime(EntityUid uid, float timer, NukeComponent? component = null)
  484. {
  485. if (!Resolve(uid, ref component))
  486. return;
  487. component.RemainingTime = timer;
  488. UpdateUserInterface(uid, component);
  489. }
  490. #endregion
  491. private void DisarmBombDoafter(EntityUid uid, EntityUid user, NukeComponent nuke)
  492. {
  493. var doAfter = new DoAfterArgs(EntityManager, user, nuke.DisarmDoafterLength, new NukeDisarmDoAfterEvent(), uid, target: uid)
  494. {
  495. BreakOnDamage = true,
  496. BreakOnMove = true,
  497. NeedHand = true,
  498. };
  499. if (!_doAfter.TryStartDoAfter(doAfter))
  500. return;
  501. _popups.PopupEntity(Loc.GetString("nuke-component-doafter-warning"), user,
  502. user, PopupType.LargeCaution);
  503. }
  504. private void UpdateAppearance(EntityUid uid, NukeComponent nuke)
  505. {
  506. var xform = Transform(uid);
  507. _appearance.SetData(uid, NukeVisuals.Deployed, xform.Anchored);
  508. NukeVisualState state;
  509. if (nuke.PlayedAlertSound)
  510. state = NukeVisualState.YoureFucked;
  511. else if (nuke.Status == NukeStatus.ARMED)
  512. state = NukeVisualState.Armed;
  513. else
  514. state = NukeVisualState.Idle;
  515. _appearance.SetData(uid, NukeVisuals.State, state);
  516. }
  517. private void OnExaminedEvent(EntityUid uid, NukeComponent component, ExaminedEvent args)
  518. {
  519. if (component.PlayedAlertSound)
  520. args.PushMarkup(Loc.GetString("nuke-examine-exploding"));
  521. else if (component.Status == NukeStatus.ARMED)
  522. args.PushMarkup(Loc.GetString("nuke-examine-armed"));
  523. if (Transform(uid).Anchored)
  524. args.PushMarkup(Loc.GetString("examinable-anchored"));
  525. else
  526. args.PushMarkup(Loc.GetString("examinable-unanchored"));
  527. }
  528. }
  529. public sealed class NukeExplodedEvent : EntityEventArgs
  530. {
  531. public EntityUid? OwningStation;
  532. }
  533. /// <summary>
  534. /// Raised directed on the nuke when its disarm doafter is successful.
  535. /// So the game knows not to end.
  536. /// </summary>
  537. public sealed class NukeDisarmSuccessEvent : EntityEventArgs
  538. {
  539. }