WiresSystem.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970
  1. using System.Diagnostics.CodeAnalysis;
  2. using System.Linq;
  3. using System.Threading;
  4. using Content.Server.Construction;
  5. using Content.Server.Construction.Components;
  6. using Content.Server.Power.Components;
  7. using Content.Shared.DoAfter;
  8. using Content.Shared.GameTicking;
  9. using Content.Shared.Hands.Components;
  10. using Content.Shared.Interaction;
  11. using Content.Shared.Popups;
  12. using Content.Shared.Power;
  13. using Content.Shared.Tools.Components;
  14. using Content.Shared.Wires;
  15. using Robust.Server.GameObjects;
  16. using Robust.Shared.Player;
  17. using Robust.Shared.Prototypes;
  18. using Robust.Shared.Random;
  19. namespace Content.Server.Wires;
  20. public sealed class WiresSystem : SharedWiresSystem
  21. {
  22. [Dependency] private readonly IPrototypeManager _protoMan = default!;
  23. [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
  24. [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
  25. [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
  26. [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
  27. [Dependency] private readonly IRobustRandom _random = default!;
  28. [Dependency] private readonly ConstructionSystem _construction = default!;
  29. // This is where all the wire layouts are stored.
  30. [ViewVariables] private readonly Dictionary<string, WireLayout> _layouts = new();
  31. private float _toolTime = 0f;
  32. #region Initialization
  33. public override void Initialize()
  34. {
  35. base.Initialize();
  36. SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
  37. // this is a broadcast event
  38. SubscribeLocalEvent<WiresComponent, PanelChangedEvent>(OnPanelChanged);
  39. SubscribeLocalEvent<WiresComponent, WiresActionMessage>(OnWiresActionMessage);
  40. SubscribeLocalEvent<WiresComponent, InteractUsingEvent>(OnInteractUsing);
  41. SubscribeLocalEvent<WiresComponent, MapInitEvent>(OnMapInit);
  42. SubscribeLocalEvent<WiresComponent, TimedWireEvent>(OnTimedWire);
  43. SubscribeLocalEvent<WiresComponent, PowerChangedEvent>(OnWiresPowered);
  44. SubscribeLocalEvent<WiresComponent, WireDoAfterEvent>(OnDoAfter);
  45. SubscribeLocalEvent<WiresPanelSecurityComponent, WiresPanelSecurityEvent>(SetWiresPanelSecurity);
  46. }
  47. private void SetOrCreateWireLayout(EntityUid uid, WiresComponent? wires = null)
  48. {
  49. if (!Resolve(uid, ref wires))
  50. return;
  51. WireLayout? layout = null;
  52. List<Wire>? wireSet = null;
  53. if (!wires.AlwaysRandomize)
  54. {
  55. TryGetLayout(wires.LayoutId, out layout);
  56. }
  57. List<IWireAction> wireActions = new();
  58. var dummyWires = 0;
  59. if (!_protoMan.TryIndex(wires.LayoutId, out WireLayoutPrototype? layoutPrototype))
  60. {
  61. return;
  62. }
  63. dummyWires += layoutPrototype.DummyWires;
  64. if (layoutPrototype.Wires != null)
  65. {
  66. wireActions.AddRange(layoutPrototype.Wires);
  67. }
  68. // does the prototype have a parent (and are the wires empty?) if so, we just create
  69. // a new layout based on that
  70. foreach (var parentLayout in _protoMan.EnumerateParents<WireLayoutPrototype>(wires.LayoutId))
  71. {
  72. if (parentLayout.Wires != null)
  73. {
  74. wireActions.AddRange(parentLayout.Wires);
  75. }
  76. dummyWires += parentLayout.DummyWires;
  77. }
  78. if (wireActions.Count > 0)
  79. {
  80. foreach (var wire in wireActions)
  81. {
  82. wire.Initialize();
  83. }
  84. wireSet = CreateWireSet(uid, layout, wireActions, dummyWires);
  85. }
  86. if (wireSet == null || wireSet.Count == 0)
  87. {
  88. return;
  89. }
  90. wires.WiresList.AddRange(wireSet);
  91. var types = new Dictionary<object, int>();
  92. if (layout != null)
  93. {
  94. for (var i = 0; i < wireSet.Count; i++)
  95. {
  96. wires.WiresList[layout.Specifications[i].Position] = wireSet[i];
  97. }
  98. var id = 0;
  99. foreach (var wire in wires.WiresList)
  100. {
  101. wire.Id = id++;
  102. if (wire.Action == null)
  103. continue;
  104. var wireType = wire.Action.GetType();
  105. if (types.ContainsKey(wireType))
  106. {
  107. types[wireType] += 1;
  108. }
  109. else
  110. {
  111. types.Add(wireType, 1);
  112. }
  113. // don't care about the result, this should've
  114. // been handled in layout creation
  115. wire.Action.AddWire(wire, types[wireType]);
  116. }
  117. }
  118. else
  119. {
  120. var enumeratedList = new List<(int, Wire)>();
  121. var data = new Dictionary<int, WireLayout.WireData>();
  122. for (int i = 0; i < wireSet.Count; i++)
  123. {
  124. enumeratedList.Add((i, wireSet[i]));
  125. }
  126. _random.Shuffle(enumeratedList);
  127. for (var i = 0; i < enumeratedList.Count; i++)
  128. {
  129. (int id, Wire d) = enumeratedList[i];
  130. d.Id = i;
  131. if (d.Action != null)
  132. {
  133. var actionType = d.Action.GetType();
  134. if (!types.TryAdd(actionType, 1))
  135. types[actionType] += 1;
  136. if (!d.Action.AddWire(d, types[actionType]))
  137. d.Action = null;
  138. }
  139. data.Add(id, new WireLayout.WireData(d.Letter, d.Color, i));
  140. wires.WiresList[i] = wireSet[id];
  141. }
  142. if (!wires.AlwaysRandomize && !string.IsNullOrEmpty(wires.LayoutId))
  143. {
  144. AddLayout(wires.LayoutId, new WireLayout(data));
  145. }
  146. }
  147. }
  148. private List<Wire>? CreateWireSet(EntityUid uid, WireLayout? layout, List<IWireAction> wires, int dummyWires)
  149. {
  150. if (wires.Count == 0)
  151. return null;
  152. List<WireColor> colors =
  153. new((WireColor[])Enum.GetValues(typeof(WireColor)));
  154. List<WireLetter> letters =
  155. new((WireLetter[])Enum.GetValues(typeof(WireLetter)));
  156. var wireSet = new List<Wire>();
  157. for (var i = 0; i < wires.Count; i++)
  158. {
  159. wireSet.Add(CreateWire(uid, wires[i], i, layout, colors, letters));
  160. }
  161. for (var i = 1; i <= dummyWires; i++)
  162. {
  163. wireSet.Add(CreateWire(uid, null, wires.Count + i, layout, colors, letters));
  164. }
  165. return wireSet;
  166. }
  167. private Wire CreateWire(EntityUid uid, IWireAction? action, int position, WireLayout? layout, List<WireColor> colors, List<WireLetter> letters)
  168. {
  169. WireLetter letter;
  170. WireColor color;
  171. if (layout != null
  172. && layout.Specifications.TryGetValue(position, out var spec))
  173. {
  174. color = spec.Color;
  175. letter = spec.Letter;
  176. colors.Remove(color);
  177. letters.Remove(letter);
  178. }
  179. else
  180. {
  181. color = colors.Count == 0 ? WireColor.Red : _random.PickAndTake(colors);
  182. letter = letters.Count == 0 ? WireLetter.α : _random.PickAndTake(letters);
  183. }
  184. return new Wire(
  185. uid,
  186. false,
  187. color,
  188. letter,
  189. position,
  190. action);
  191. }
  192. #endregion
  193. #region DoAfters
  194. private void OnTimedWire(EntityUid uid, WiresComponent component, TimedWireEvent args)
  195. {
  196. args.Delegate(args.Wire);
  197. UpdateUserInterface(uid);
  198. }
  199. /// <summary>
  200. /// Tries to cancel an active wire action via the given key that it's stored in.
  201. /// </summary>
  202. /// <param name="key">The key used to cancel the action.</param>
  203. public bool TryCancelWireAction(EntityUid owner, object key)
  204. {
  205. if (TryGetData<CancellationTokenSource?>(owner, key, out var token))
  206. {
  207. token.Cancel();
  208. return true;
  209. }
  210. return false;
  211. }
  212. /// <summary>
  213. /// Starts a timed action for this entity.
  214. /// </summary>
  215. /// <param name="delay">How long this takes to finish</param>
  216. /// <param name="key">The key used to cancel the action</param>
  217. /// <param name="onFinish">The event that is sent out when the wire is finished <see cref="TimedWireEvent" /></param>
  218. public void StartWireAction(EntityUid owner, float delay, object key, TimedWireEvent onFinish)
  219. {
  220. if (!HasComp<WiresComponent>(owner))
  221. {
  222. return;
  223. }
  224. if (!_activeWires.ContainsKey(owner))
  225. {
  226. _activeWires.Add(owner, new());
  227. }
  228. CancellationTokenSource tokenSource = new();
  229. // Starting an already started action will do nothing.
  230. if (HasData(owner, key))
  231. {
  232. return;
  233. }
  234. SetData(owner, key, tokenSource);
  235. _activeWires[owner].Add(new ActiveWireAction
  236. (
  237. key,
  238. delay,
  239. tokenSource.Token,
  240. onFinish
  241. ));
  242. }
  243. private Dictionary<EntityUid, List<ActiveWireAction>> _activeWires = new();
  244. private List<(EntityUid, ActiveWireAction)> _finishedWires = new();
  245. public override void Update(float frameTime)
  246. {
  247. foreach (var (owner, activeWires) in _activeWires)
  248. {
  249. if (!HasComp<WiresComponent>(owner))
  250. _activeWires.Remove(owner);
  251. foreach (var wire in activeWires)
  252. {
  253. if (wire.CancelToken.IsCancellationRequested)
  254. {
  255. RaiseLocalEvent(owner, wire.OnFinish, true);
  256. _finishedWires.Add((owner, wire));
  257. }
  258. else
  259. {
  260. wire.TimeLeft -= frameTime;
  261. if (wire.TimeLeft <= 0)
  262. {
  263. RaiseLocalEvent(owner, wire.OnFinish, true);
  264. _finishedWires.Add((owner, wire));
  265. }
  266. }
  267. }
  268. }
  269. if (_finishedWires.Count != 0)
  270. {
  271. foreach (var (owner, wireAction) in _finishedWires)
  272. {
  273. if (!_activeWires.TryGetValue(owner, out var activeWire))
  274. {
  275. continue;
  276. }
  277. activeWire.RemoveAll(action => action.CancelToken == wireAction.CancelToken);
  278. if (activeWire.Count == 0)
  279. {
  280. _activeWires.Remove(owner);
  281. }
  282. RemoveData(owner, wireAction.Id);
  283. }
  284. _finishedWires.Clear();
  285. }
  286. }
  287. private sealed class ActiveWireAction
  288. {
  289. /// <summary>
  290. /// The wire action's ID. This is so that once the action is finished,
  291. /// any related data can be removed from the state dictionary.
  292. /// </summary>
  293. public object Id;
  294. /// <summary>
  295. /// How much time is left in this action before it finishes.
  296. /// </summary>
  297. public float TimeLeft;
  298. /// <summary>
  299. /// The token used to cancel the action.
  300. /// </summary>
  301. public CancellationToken CancelToken;
  302. /// <summary>
  303. /// The event called once the action finishes.
  304. /// </summary>
  305. public TimedWireEvent OnFinish;
  306. public ActiveWireAction(object identifier, float time, CancellationToken cancelToken, TimedWireEvent onFinish)
  307. {
  308. Id = identifier;
  309. TimeLeft = time;
  310. CancelToken = cancelToken;
  311. OnFinish = onFinish;
  312. }
  313. }
  314. #endregion
  315. #region Event Handling
  316. private void OnWiresPowered(EntityUid uid, WiresComponent component, ref PowerChangedEvent args)
  317. {
  318. UpdateUserInterface(uid);
  319. foreach (var wire in component.WiresList)
  320. {
  321. wire.Action?.Update(wire);
  322. }
  323. }
  324. private void OnWiresActionMessage(EntityUid uid, WiresComponent component, WiresActionMessage args)
  325. {
  326. var player = args.Actor;
  327. if (!EntityManager.TryGetComponent(player, out HandsComponent? handsComponent))
  328. {
  329. _popupSystem.PopupEntity(Loc.GetString("wires-component-ui-on-receive-message-no-hands"), uid, player);
  330. return;
  331. }
  332. if (!_interactionSystem.InRangeUnobstructed(player, uid))
  333. {
  334. _popupSystem.PopupEntity(Loc.GetString("wires-component-ui-on-receive-message-cannot-reach"), uid, player);
  335. return;
  336. }
  337. var activeHand = handsComponent.ActiveHand;
  338. if (activeHand == null)
  339. return;
  340. if (activeHand.HeldEntity == null)
  341. return;
  342. var activeHandEntity = activeHand.HeldEntity.Value;
  343. if (!EntityManager.TryGetComponent(activeHandEntity, out ToolComponent? tool))
  344. return;
  345. TryDoWireAction(uid, player, activeHandEntity, args.Id, args.Action, component, tool);
  346. }
  347. private void OnDoAfter(EntityUid uid, WiresComponent component, WireDoAfterEvent args)
  348. {
  349. if (args.Cancelled)
  350. {
  351. component.WiresQueue.Remove(args.Id);
  352. return;
  353. }
  354. if (args.Handled || args.Args.Target == null || args.Args.Used == null)
  355. return;
  356. UpdateWires(args.Args.Target.Value, args.Args.User, args.Args.Used.Value, args.Id, args.Action, component);
  357. args.Handled = true;
  358. }
  359. private void OnInteractUsing(EntityUid uid, WiresComponent component, InteractUsingEvent args)
  360. {
  361. if (args.Handled)
  362. return;
  363. if (!TryComp<ToolComponent>(args.Used, out var tool))
  364. return;
  365. if (!IsPanelOpen(uid))
  366. return;
  367. if (Tool.HasQuality(args.Used, "Cutting", tool) ||
  368. Tool.HasQuality(args.Used, "Pulsing", tool))
  369. {
  370. if (TryComp(args.User, out ActorComponent? actor))
  371. {
  372. _uiSystem.OpenUi(uid, WiresUiKey.Key, actor.PlayerSession);
  373. args.Handled = true;
  374. }
  375. }
  376. }
  377. private void OnPanelChanged(Entity<WiresComponent> ent, ref PanelChangedEvent args)
  378. {
  379. if (args.Open)
  380. return;
  381. _uiSystem.CloseUi(ent.Owner, WiresUiKey.Key);
  382. }
  383. private void OnMapInit(EntityUid uid, WiresComponent component, MapInitEvent args)
  384. {
  385. if (!string.IsNullOrEmpty(component.LayoutId))
  386. SetOrCreateWireLayout(uid, component);
  387. if (component.SerialNumber == null)
  388. GenerateSerialNumber(uid, component);
  389. if (component.WireSeed == 0)
  390. component.WireSeed = _random.Next(1, int.MaxValue);
  391. // Update the construction graph to make sure that it starts on the node specified by WiresPanelSecurityComponent
  392. if (TryComp<WiresPanelSecurityComponent>(uid, out var wiresPanelSecurity) &&
  393. !string.IsNullOrEmpty(wiresPanelSecurity.SecurityLevel) &&
  394. TryComp<ConstructionComponent>(uid, out var construction))
  395. {
  396. _construction.ChangeNode(uid, null, wiresPanelSecurity.SecurityLevel, true, construction);
  397. }
  398. UpdateUserInterface(uid);
  399. }
  400. #endregion
  401. #region Entity API
  402. private void GenerateSerialNumber(EntityUid uid, WiresComponent? wires = null)
  403. {
  404. if (!Resolve(uid, ref wires))
  405. return;
  406. Span<char> data = stackalloc char[9];
  407. data[4] = '-';
  408. if (_random.Prob(0.01f))
  409. {
  410. for (var i = 0; i < 4; i++)
  411. {
  412. // Cyrillic Letters
  413. data[i] = (char)_random.Next(0x0410, 0x0430);
  414. }
  415. }
  416. else
  417. {
  418. for (var i = 0; i < 4; i++)
  419. {
  420. // Letters
  421. data[i] = (char)_random.Next(0x41, 0x5B);
  422. }
  423. }
  424. for (var i = 5; i < 9; i++)
  425. {
  426. // Digits
  427. data[i] = (char)_random.Next(0x30, 0x3A);
  428. }
  429. wires.SerialNumber = new string(data);
  430. UpdateUserInterface(uid);
  431. }
  432. private void UpdateUserInterface(EntityUid uid, WiresComponent? wires = null, UserInterfaceComponent? ui = null)
  433. {
  434. if (!Resolve(uid, ref wires, ref ui, false)) // logging this means that we get a bunch of errors
  435. return;
  436. var clientList = new List<ClientWire>();
  437. foreach (var entry in wires.WiresList)
  438. {
  439. clientList.Add(new ClientWire(entry.Id, entry.IsCut, entry.Color,
  440. entry.Letter));
  441. var statusData = entry.Action?.GetStatusLightData(entry);
  442. if (statusData != null && entry.Action?.StatusKey != null)
  443. {
  444. wires.Statuses[entry.Action.StatusKey] = (entry.OriginalPosition, statusData);
  445. }
  446. }
  447. var statuses = new List<(int position, object key, object value)>();
  448. foreach (var (key, value) in wires.Statuses)
  449. {
  450. var valueCast = ((int position, StatusLightData? value))value;
  451. statuses.Add((valueCast.position, key, valueCast.value!));
  452. }
  453. statuses.Sort((a, b) => a.position.CompareTo(b.position));
  454. _uiSystem.SetUiState((uid, ui), WiresUiKey.Key, new WiresBoundUserInterfaceState(
  455. clientList.ToArray(),
  456. statuses.Select(p => new StatusEntry(p.key, p.value)).ToArray(),
  457. Loc.GetString(wires.BoardName),
  458. wires.SerialNumber,
  459. wires.WireSeed));
  460. }
  461. public void OpenUserInterface(EntityUid uid, ICommonSession player)
  462. {
  463. _uiSystem.OpenUi(uid, WiresUiKey.Key, player);
  464. }
  465. /// <summary>
  466. /// Tries to get a wire on this entity by its integer id.
  467. /// </summary>
  468. /// <returns>The wire if found, otherwise null</returns>
  469. public Wire? TryGetWire(EntityUid uid, int id, WiresComponent? wires = null)
  470. {
  471. if (!Resolve(uid, ref wires))
  472. return null;
  473. return id >= 0 && id < wires.WiresList.Count
  474. ? wires.WiresList[id]
  475. : null;
  476. }
  477. /// <summary>
  478. /// Tries to get all the wires on this entity by the wire action type.
  479. /// </summary>
  480. /// <returns>Enumerator of all wires in this entity according to the given type.</returns>
  481. public IEnumerable<Wire> TryGetWires<T>(EntityUid uid, WiresComponent? wires = null)
  482. {
  483. if (!Resolve(uid, ref wires))
  484. yield break;
  485. foreach (var wire in wires.WiresList)
  486. {
  487. if (wire.GetType() == typeof(T))
  488. {
  489. yield return wire;
  490. }
  491. }
  492. }
  493. public void SetWiresPanelSecurity(EntityUid uid, WiresPanelSecurityComponent component, WiresPanelSecurityEvent args)
  494. {
  495. component.Examine = args.Examine;
  496. component.WiresAccessible = args.WiresAccessible;
  497. Dirty(uid, component);
  498. if (!args.WiresAccessible)
  499. {
  500. _uiSystem.CloseUi(uid, WiresUiKey.Key);
  501. }
  502. }
  503. private void TryDoWireAction(EntityUid target, EntityUid user, EntityUid toolEntity, int id, WiresAction action, WiresComponent? wires = null, ToolComponent? tool = null)
  504. {
  505. if (!Resolve(target, ref wires)
  506. || !Resolve(toolEntity, ref tool))
  507. return;
  508. if (wires.WiresQueue.Contains(id))
  509. return;
  510. var wire = TryGetWire(target, id, wires);
  511. if (wire == null)
  512. return;
  513. switch (action)
  514. {
  515. case WiresAction.Cut:
  516. if (!Tool.HasQuality(toolEntity, "Cutting", tool))
  517. {
  518. _popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-wirecutters"), user);
  519. return;
  520. }
  521. if (wire.IsCut)
  522. {
  523. _popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-cannot-cut-cut-wire"), user);
  524. return;
  525. }
  526. break;
  527. case WiresAction.Mend:
  528. if (!Tool.HasQuality(toolEntity, "Cutting", tool))
  529. {
  530. _popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-wirecutters"), user);
  531. return;
  532. }
  533. if (!wire.IsCut)
  534. {
  535. _popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-cannot-mend-uncut-wire"), user);
  536. return;
  537. }
  538. break;
  539. case WiresAction.Pulse:
  540. if (!Tool.HasQuality(toolEntity, "Pulsing", tool))
  541. {
  542. _popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-multitool"), user);
  543. return;
  544. }
  545. if (wire.IsCut)
  546. {
  547. _popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-cannot-pulse-cut-wire"), user);
  548. return;
  549. }
  550. break;
  551. }
  552. wires.WiresQueue.Add(id);
  553. if (_toolTime > 0f)
  554. {
  555. var args = new DoAfterArgs(EntityManager, user, _toolTime, new WireDoAfterEvent(action, id), target, target: target, used: toolEntity)
  556. {
  557. NeedHand = true,
  558. BreakOnDamage = true,
  559. BreakOnMove = true
  560. };
  561. _doAfter.TryStartDoAfter(args);
  562. }
  563. else
  564. {
  565. UpdateWires(target, user, toolEntity, id, action, wires);
  566. }
  567. }
  568. private void UpdateWires(EntityUid used, EntityUid user, EntityUid toolEntity, int id, WiresAction action, WiresComponent? wires = null, ToolComponent? tool = null)
  569. {
  570. if (!Resolve(used, ref wires))
  571. return;
  572. if (!wires.WiresQueue.Contains(id))
  573. return;
  574. if (!Resolve(toolEntity, ref tool))
  575. {
  576. wires.WiresQueue.Remove(id);
  577. return;
  578. }
  579. var wire = TryGetWire(used, id, wires);
  580. if (wire == null)
  581. {
  582. wires.WiresQueue.Remove(id);
  583. return;
  584. }
  585. switch (action)
  586. {
  587. case WiresAction.Cut:
  588. if (!Tool.HasQuality(toolEntity, "Cutting", tool))
  589. {
  590. _popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-wirecutters"), user);
  591. break;
  592. }
  593. if (wire.IsCut)
  594. {
  595. _popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-cannot-cut-cut-wire"), user);
  596. break;
  597. }
  598. Tool.PlayToolSound(toolEntity, tool, null);
  599. if (wire.Action == null || wire.Action.Cut(user, wire))
  600. {
  601. wire.IsCut = true;
  602. }
  603. UpdateUserInterface(used);
  604. break;
  605. case WiresAction.Mend:
  606. if (!Tool.HasQuality(toolEntity, "Cutting", tool))
  607. {
  608. _popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-wirecutters"), user);
  609. break;
  610. }
  611. if (!wire.IsCut)
  612. {
  613. _popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-cannot-mend-uncut-wire"), user);
  614. break;
  615. }
  616. Tool.PlayToolSound(toolEntity, tool, null);
  617. if (wire.Action == null || wire.Action.Mend(user, wire))
  618. {
  619. wire.IsCut = false;
  620. }
  621. UpdateUserInterface(used);
  622. break;
  623. case WiresAction.Pulse:
  624. if (!Tool.HasQuality(toolEntity, "Pulsing", tool))
  625. {
  626. _popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-multitool"), user);
  627. break;
  628. }
  629. if (wire.IsCut)
  630. {
  631. _popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-cannot-pulse-cut-wire"), user);
  632. break;
  633. }
  634. wire.Action?.Pulse(user, wire);
  635. UpdateUserInterface(used);
  636. Audio.PlayPvs(wires.PulseSound, used);
  637. break;
  638. }
  639. wire.Action?.Update(wire);
  640. wires.WiresQueue.Remove(id);
  641. }
  642. /// <summary>
  643. /// Tries to get the stateful data stored in this entity's WiresComponent.
  644. /// </summary>
  645. /// <param name="identifier">The key that stores the data in the WiresComponent.</param>
  646. public bool TryGetData<T>(EntityUid uid, object identifier, [NotNullWhen(true)] out T? data, WiresComponent? wires = null)
  647. {
  648. data = default(T);
  649. if (!Resolve(uid, ref wires))
  650. return false;
  651. wires.StateData.TryGetValue(identifier, out var result);
  652. if (result is not T)
  653. {
  654. return false;
  655. }
  656. data = (T)result;
  657. return true;
  658. }
  659. /// <summary>
  660. /// Sets data in the entity's WiresComponent state dictionary by key.
  661. /// </summary>
  662. /// <param name="identifier">The key that stores the data in the WiresComponent.</param>
  663. /// <param name="data">The data to store using the given identifier.</param>
  664. public void SetData(EntityUid uid, object identifier, object data, WiresComponent? wires = null)
  665. {
  666. if (!Resolve(uid, ref wires))
  667. return;
  668. if (wires.StateData.TryGetValue(identifier, out var storedMessage))
  669. {
  670. if (storedMessage == data)
  671. {
  672. return;
  673. }
  674. }
  675. wires.StateData[identifier] = data;
  676. UpdateUserInterface(uid, wires);
  677. }
  678. /// <summary>
  679. /// If this entity has data stored via this key in the WiresComponent it has
  680. /// </summary>
  681. public bool HasData(EntityUid uid, object identifier, WiresComponent? wires = null)
  682. {
  683. if (!Resolve(uid, ref wires))
  684. return false;
  685. return wires.StateData.ContainsKey(identifier);
  686. }
  687. /// <summary>
  688. /// Removes data from this entity stored in the given key from the entity's WiresComponent.
  689. /// </summary>
  690. /// <param name="identifier">The key that stores the data in the WiresComponent.</param>
  691. public void RemoveData(EntityUid uid, object identifier, WiresComponent? wires = null)
  692. {
  693. if (!Resolve(uid, ref wires))
  694. return;
  695. wires.StateData.Remove(identifier);
  696. }
  697. #endregion
  698. #region Layout Handling
  699. private bool TryGetLayout(string id, [NotNullWhen(true)] out WireLayout? layout)
  700. {
  701. return _layouts.TryGetValue(id, out layout);
  702. }
  703. private void AddLayout(string id, WireLayout layout)
  704. {
  705. _layouts.Add(id, layout);
  706. }
  707. private void Reset(RoundRestartCleanupEvent args)
  708. {
  709. _layouts.Clear();
  710. }
  711. #endregion
  712. }
  713. public sealed class Wire
  714. {
  715. /// <summary>
  716. /// The entity that registered the wire.
  717. /// </summary>
  718. public EntityUid Owner { get; }
  719. /// <summary>
  720. /// Whether the wire is cut.
  721. /// </summary>
  722. public bool IsCut { get; set; }
  723. /// <summary>
  724. /// Used in client-server communication to identify a wire without telling the client what the wire does.
  725. /// </summary>
  726. [ViewVariables]
  727. public int Id { get; set; }
  728. /// <summary>
  729. /// The original position of this wire in the prototype.
  730. /// </summary>
  731. [ViewVariables]
  732. public int OriginalPosition { get; set; }
  733. /// <summary>
  734. /// The color of the wire.
  735. /// </summary>
  736. [ViewVariables]
  737. public WireColor Color { get; }
  738. /// <summary>
  739. /// The greek letter shown below the wire.
  740. /// </summary>
  741. [ViewVariables]
  742. public WireLetter Letter { get; }
  743. /// <summary>
  744. /// The action that this wire performs when mended, cut or puled. This also determines the status lights that this wire adds.
  745. /// </summary>
  746. public IWireAction? Action { get; set; }
  747. public Wire(EntityUid owner, bool isCut, WireColor color, WireLetter letter, int position, IWireAction? action)
  748. {
  749. Owner = owner;
  750. IsCut = isCut;
  751. Color = color;
  752. OriginalPosition = position;
  753. Letter = letter;
  754. Action = action;
  755. }
  756. }
  757. // this is here so that when a DoAfter event is called,
  758. // WiresSystem can call the action in question after the
  759. // doafter is finished (either through cancellation
  760. // or completion - this is implementation dependent)
  761. public delegate void WireActionDelegate(Wire wire);
  762. // callbacks over the event bus,
  763. // because async is banned
  764. public sealed class TimedWireEvent : EntityEventArgs
  765. {
  766. /// <summary>
  767. /// The function to be called once
  768. /// the timed event is complete.
  769. /// </summary>
  770. public WireActionDelegate Delegate { get; }
  771. /// <summary>
  772. /// The wire tied to this timed wire event.
  773. /// </summary>
  774. public Wire Wire { get; }
  775. public TimedWireEvent(WireActionDelegate @delegate, Wire wire)
  776. {
  777. Delegate = @delegate;
  778. Wire = wire;
  779. }
  780. }
  781. public sealed class WireLayout
  782. {
  783. // why is this an <int, WireData>?
  784. // List<T>.Insert panics,
  785. // and I needed a uniquer key for wires
  786. // which allows me to have a unified identifier
  787. [ViewVariables] public IReadOnlyDictionary<int, WireData> Specifications { get; }
  788. public WireLayout(IReadOnlyDictionary<int, WireData> specifications)
  789. {
  790. Specifications = specifications;
  791. }
  792. public sealed class WireData
  793. {
  794. public WireLetter Letter { get; }
  795. public WireColor Color { get; }
  796. public int Position { get; }
  797. public WireData(WireLetter letter, WireColor color, int position)
  798. {
  799. Letter = letter;
  800. Color = color;
  801. Position = position;
  802. }
  803. }
  804. }