1
0

NetworkConfiguratorSystem.cs 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851
  1. using System.Linq;
  2. using Content.Server.Administration.Logs;
  3. using Content.Server.DeviceLinking.Systems;
  4. using Content.Server.DeviceNetwork.Components;
  5. using Content.Shared.Access.Components;
  6. using Content.Shared.Access.Systems;
  7. using Content.Shared.Database;
  8. using Content.Shared.DeviceLinking;
  9. using Content.Shared.DeviceNetwork;
  10. using Content.Shared.DeviceNetwork.Components;
  11. using Content.Shared.DeviceNetwork.Systems;
  12. using Content.Shared.Examine;
  13. using Content.Shared.Interaction;
  14. using Content.Shared.Popups;
  15. using Content.Shared.UserInterface;
  16. using Content.Shared.Verbs;
  17. using JetBrains.Annotations;
  18. using Robust.Server.Audio;
  19. using Robust.Server.GameObjects;
  20. using Robust.Shared.Audio;
  21. using Robust.Shared.Map.Events;
  22. using Robust.Shared.Timing;
  23. using Robust.Shared.Utility;
  24. namespace Content.Server.DeviceNetwork.Systems;
  25. [UsedImplicitly]
  26. public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
  27. {
  28. [Dependency] private readonly DeviceListSystem _deviceListSystem = default!;
  29. [Dependency] private readonly DeviceLinkSystem _deviceLinkSystem = default!;
  30. [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
  31. [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
  32. [Dependency] private readonly AccessReaderSystem _accessSystem = default!;
  33. [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
  34. [Dependency] private readonly AudioSystem _audioSystem = default!;
  35. [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
  36. [Dependency] private readonly IGameTiming _gameTiming = default!;
  37. [Dependency] private readonly IAdminLogManager _adminLogger = default!;
  38. public override void Initialize()
  39. {
  40. base.Initialize();
  41. SubscribeLocalEvent<NetworkConfiguratorComponent, MapInitEvent>(OnMapInit);
  42. SubscribeLocalEvent<NetworkConfiguratorComponent, ComponentShutdown>(OnShutdown);
  43. //Interaction
  44. SubscribeLocalEvent<NetworkConfiguratorComponent, AfterInteractEvent>(AfterInteract); //TODO: Replace with utility verb?
  45. SubscribeLocalEvent<NetworkConfiguratorComponent, ExaminedEvent>(DoExamine);
  46. //Verbs
  47. SubscribeLocalEvent<NetworkConfiguratorComponent, GetVerbsEvent<UtilityVerb>>(OnAddInteractVerb);
  48. SubscribeLocalEvent<DeviceNetworkComponent, GetVerbsEvent<AlternativeVerb>>(OnAddAlternativeSaveDeviceVerb);
  49. SubscribeLocalEvent<NetworkConfiguratorComponent, GetVerbsEvent<AlternativeVerb>>(OnAddSwitchModeVerb);
  50. //UI
  51. SubscribeLocalEvent<NetworkConfiguratorComponent, BoundUIClosedEvent>(OnUiClosed);
  52. SubscribeLocalEvent<NetworkConfiguratorComponent, NetworkConfiguratorRemoveDeviceMessage>(OnRemoveDevice);
  53. SubscribeLocalEvent<NetworkConfiguratorComponent, NetworkConfiguratorClearDevicesMessage>(OnClearDevice);
  54. SubscribeLocalEvent<NetworkConfiguratorComponent, NetworkConfiguratorLinksSaveMessage>(OnSaveLinks);
  55. SubscribeLocalEvent<NetworkConfiguratorComponent, NetworkConfiguratorClearLinksMessage>(OnClearLinks);
  56. SubscribeLocalEvent<NetworkConfiguratorComponent, NetworkConfiguratorToggleLinkMessage>(OnToggleLinks);
  57. SubscribeLocalEvent<NetworkConfiguratorComponent, NetworkConfiguratorButtonPressedMessage>(OnConfigButtonPressed);
  58. SubscribeLocalEvent<NetworkConfiguratorComponent, BoundUserInterfaceCheckRangeEvent>(OnUiRangeCheck);
  59. SubscribeLocalEvent<DeviceListComponent, ComponentRemove>(OnComponentRemoved);
  60. SubscribeLocalEvent<BeforeSerializationEvent>(OnMapSave);
  61. }
  62. private void OnMapSave(BeforeSerializationEvent ev)
  63. {
  64. var enumerator = AllEntityQuery<NetworkConfiguratorComponent>();
  65. while (enumerator.MoveNext(out var uid, out var conf))
  66. {
  67. if (!TryComp(conf.ActiveDeviceList, out TransformComponent? listXform))
  68. continue;
  69. if (!ev.MapIds.Contains(listXform.MapID))
  70. continue;
  71. // The linked device list is (probably) being saved. Make sure that the configurator is also being saved
  72. // (i.e., not in the hands of a mapper/ghost). In the future, map saving should raise a separate event
  73. // containing a set of all entities that are about to be saved, which would make checking this much easier.
  74. // This is a shitty bandaid, and will force close the UI during auto-saves.
  75. // TODO Map serialization refactor
  76. // I'm refactoring it now and I still dont know what to do
  77. var xform = Transform(uid);
  78. if (ev.MapIds.Contains(xform.MapID) && IsSaveable(uid))
  79. continue;
  80. _uiSystem.CloseUi(uid, NetworkConfiguratorUiKey.Configure);
  81. DebugTools.AssertNull(conf.ActiveDeviceList);
  82. }
  83. bool IsSaveable(EntityUid uid)
  84. {
  85. while (uid.IsValid())
  86. {
  87. if (Prototype(uid)?.MapSavable == false)
  88. return false;
  89. uid = Transform(uid).ParentUid;
  90. }
  91. return true;
  92. }
  93. }
  94. private void OnUiRangeCheck(Entity<NetworkConfiguratorComponent> ent, ref BoundUserInterfaceCheckRangeEvent args)
  95. {
  96. if (ent.Comp.ActiveDeviceList == null || args.Result == BoundUserInterfaceRangeResult.Fail)
  97. return;
  98. DebugTools.Assert(Exists(ent.Comp.ActiveDeviceList));
  99. if (!_interactionSystem.InRangeUnobstructed(args.Actor!, ent.Comp.ActiveDeviceList.Value))
  100. args.Result = BoundUserInterfaceRangeResult.Fail;
  101. }
  102. private void OnShutdown(EntityUid uid, NetworkConfiguratorComponent component, ComponentShutdown args)
  103. {
  104. ClearDevices(uid, component);
  105. if (TryComp(component.ActiveDeviceList, out DeviceListComponent? list))
  106. list.Configurators.Remove(uid);
  107. component.ActiveDeviceList = null;
  108. }
  109. private void OnMapInit(EntityUid uid, NetworkConfiguratorComponent component, MapInitEvent args)
  110. {
  111. UpdateListUiState(uid, component);
  112. }
  113. private void TryAddNetworkDevice(EntityUid? targetUid, EntityUid configuratorUid, EntityUid userUid,
  114. NetworkConfiguratorComponent? configurator = null)
  115. {
  116. if (!Resolve(configuratorUid, ref configurator))
  117. return;
  118. TryAddNetworkDevice(configuratorUid, targetUid, userUid, configurator);
  119. }
  120. private void TryAddNetworkDevice(EntityUid configuratorUid, EntityUid? targetUid, EntityUid userUid, NetworkConfiguratorComponent configurator, DeviceNetworkComponent? device = null)
  121. {
  122. if (!targetUid.HasValue || !Resolve(targetUid.Value, ref device, false))
  123. return;
  124. //This checks if the device is marked as having a savable address,
  125. //to avoid adding pdas and whatnot to air alarms. This flag is true
  126. //by default, so this will only prevent devices from being added to
  127. //network configurator lists if manually set to false in the prototype
  128. if (!device.SavableAddress)
  129. return;
  130. var address = device.Address;
  131. if (string.IsNullOrEmpty(address))
  132. {
  133. // This primarily checks if the entity in question is pre-map init or not.
  134. // This is because otherwise, anything that uses DeviceNetwork will not
  135. // have an address populated, as all devices that use DeviceNetwork
  136. // obtain their address on map init. If the entity is post-map init,
  137. // and it still doesn't have an address, it will fail. Otherwise,
  138. // it stores the entity's UID as a string for visual effect, that way
  139. // a mapper can reference the devices they've gathered by UID, instead of
  140. // by device network address. These entries, if the multitool is still in
  141. // the map after it being saved, are cleared upon mapinit.
  142. if (MetaData(targetUid.Value).EntityLifeStage == EntityLifeStage.MapInitialized)
  143. {
  144. _popupSystem.PopupCursor(Loc.GetString("network-configurator-device-failed", ("device", targetUid)),
  145. userUid);
  146. return;
  147. }
  148. address = $"UID: {targetUid.Value}";
  149. }
  150. if (configurator.Devices.ContainsValue(targetUid.Value))
  151. {
  152. _popupSystem.PopupCursor(Loc.GetString("network-configurator-device-already-saved", ("device", targetUid)), userUid);
  153. return;
  154. }
  155. device.Configurators.Add(configuratorUid);
  156. configurator.Devices.Add(address, targetUid.Value);
  157. _popupSystem.PopupCursor(Loc.GetString("network-configurator-device-saved", ("address", device.Address), ("device", targetUid)),
  158. userUid, PopupType.Medium);
  159. _adminLogger.Add(LogType.DeviceLinking, LogImpact.Low, $"{ToPrettyString(userUid):actor} saved {ToPrettyString(targetUid.Value):subject} to {ToPrettyString(configuratorUid):tool}");
  160. UpdateListUiState(configuratorUid, configurator);
  161. }
  162. private void TryLinkDevice(EntityUid uid, NetworkConfiguratorComponent configurator, EntityUid? target, EntityUid user)
  163. {
  164. if (!HasComp<DeviceLinkSourceComponent>(target) && !HasComp<DeviceLinkSinkComponent>(target))
  165. return;
  166. if (configurator.ActiveDeviceLink == target)
  167. {
  168. _popupSystem.PopupEntity(Loc.GetString("network-configurator-link-mode-stopped"), target.Value, user);
  169. configurator.ActiveDeviceLink = null;
  170. return;
  171. }
  172. if (configurator.ActiveDeviceLink.HasValue
  173. && (HasComp<DeviceLinkSourceComponent>(target)
  174. && HasComp<DeviceLinkSinkComponent>(configurator.ActiveDeviceLink)
  175. || HasComp<DeviceLinkSinkComponent>(target)
  176. && HasComp<DeviceLinkSourceComponent>(configurator.ActiveDeviceLink)))
  177. {
  178. OpenDeviceLinkUi(uid, target, user, configurator);
  179. return;
  180. }
  181. if (HasComp<DeviceLinkSourceComponent>(target) && HasComp<DeviceLinkSourceComponent>(configurator.ActiveDeviceLink)
  182. || HasComp<DeviceLinkSinkComponent>(target) && HasComp<DeviceLinkSinkComponent>(configurator.ActiveDeviceLink))
  183. return;
  184. _popupSystem.PopupEntity(Loc.GetString("network-configurator-link-mode-started", ("device", Name(target.Value))), target.Value, user);
  185. configurator.ActiveDeviceLink = target;
  186. }
  187. private void TryLinkDefaults(EntityUid _, NetworkConfiguratorComponent configurator, EntityUid? targetUid, EntityUid user)
  188. {
  189. if (!configurator.LinkModeActive || !configurator.ActiveDeviceLink.HasValue
  190. || !targetUid.HasValue || configurator.ActiveDeviceLink == targetUid)
  191. return;
  192. if (!HasComp<DeviceLinkSourceComponent>(targetUid) && !HasComp<DeviceLinkSinkComponent>(targetUid))
  193. return;
  194. if (TryComp(configurator.ActiveDeviceLink, out DeviceLinkSourceComponent? activeSource) && TryComp(targetUid, out DeviceLinkSinkComponent? targetSink))
  195. {
  196. _deviceLinkSystem.LinkDefaults(user, configurator.ActiveDeviceLink.Value, targetUid.Value, activeSource, targetSink);
  197. }
  198. else if (TryComp(configurator.ActiveDeviceLink, out DeviceLinkSinkComponent? activeSink) && TryComp(targetUid, out DeviceLinkSourceComponent? targetSource))
  199. {
  200. _deviceLinkSystem.LinkDefaults(user, targetUid.Value, configurator.ActiveDeviceLink.Value, targetSource, activeSink);
  201. }
  202. }
  203. private bool AccessCheck(EntityUid target, EntityUid? user, NetworkConfiguratorComponent component)
  204. {
  205. if (!TryComp(target, out AccessReaderComponent? reader) || user == null)
  206. return true;
  207. if (_accessSystem.IsAllowed(user.Value, target, reader))
  208. return true;
  209. _audioSystem.PlayPvs(component.SoundNoAccess, user.Value, AudioParams.Default.WithVolume(-2f).WithPitchScale(1.2f));
  210. _popupSystem.PopupEntity(Loc.GetString("network-configurator-device-access-denied"), target, user.Value);
  211. return false;
  212. }
  213. private void OnComponentRemoved(EntityUid uid, DeviceListComponent component, ComponentRemove args)
  214. {
  215. _uiSystem.CloseUi(uid, NetworkConfiguratorUiKey.Configure);
  216. }
  217. /// <summary>
  218. /// Toggles between linking and listing mode
  219. /// </summary>
  220. private void SwitchMode(EntityUid? userUid, EntityUid configuratorUid, NetworkConfiguratorComponent configurator)
  221. {
  222. if (Delay(configurator))
  223. return;
  224. configurator.LinkModeActive = !configurator.LinkModeActive;
  225. if (!userUid.HasValue)
  226. return;
  227. if (!configurator.LinkModeActive)
  228. configurator.ActiveDeviceLink = null;
  229. UpdateModeAppearance(userUid.Value, configuratorUid, configurator);
  230. }
  231. /// <summary>
  232. /// Sets the mode to linking or list depending on the link mode parameter
  233. /// </summary>>
  234. private void SetMode(EntityUid configuratorUid, NetworkConfiguratorComponent configurator, EntityUid userUid, bool linkMode)
  235. {
  236. configurator.LinkModeActive = linkMode;
  237. if (!linkMode)
  238. configurator.ActiveDeviceLink = null;
  239. UpdateModeAppearance(userUid, configuratorUid, configurator);
  240. }
  241. /// <summary>
  242. /// Updates the configurators appearance and plays a sound indicating that the mode switched
  243. /// </summary>
  244. private void UpdateModeAppearance(EntityUid userUid, EntityUid configuratorUid, NetworkConfiguratorComponent configurator)
  245. {
  246. Dirty(configuratorUid, configurator);
  247. _appearanceSystem.SetData(configuratorUid, NetworkConfiguratorVisuals.Mode, configurator.LinkModeActive);
  248. var pitch = configurator.LinkModeActive ? 1 : 0.8f;
  249. _audioSystem.PlayPvs(configurator.SoundSwitchMode, userUid, AudioParams.Default.WithVolume(1.5f).WithPitchScale(pitch));
  250. }
  251. /// <summary>
  252. /// Returns true if the last time this method was called is earlier than the configurators use delay.
  253. /// </summary>
  254. private bool Delay(NetworkConfiguratorComponent configurator)
  255. {
  256. var currentTime = _gameTiming.CurTime;
  257. if (currentTime < configurator.LastUseAttempt + configurator.UseDelay)
  258. return true;
  259. configurator.LastUseAttempt = currentTime;
  260. return false;
  261. }
  262. #region Interactions
  263. private void DoExamine(EntityUid uid, NetworkConfiguratorComponent component, ExaminedEvent args)
  264. {
  265. var mode = component.LinkModeActive ? "network-configurator-examine-mode-link" : "network-configurator-examine-mode-list";
  266. args.PushMarkup(Loc.GetString("network-configurator-examine-current-mode", ("mode", Loc.GetString(mode))));
  267. }
  268. private void AfterInteract(EntityUid uid, NetworkConfiguratorComponent component, AfterInteractEvent args)
  269. {
  270. OnUsed(uid, component, args.Target, args.User, args.CanReach);
  271. }
  272. /// <summary>
  273. /// Either adds a device to the device list or shows the config ui if the target is ant entity with a device list
  274. /// </summary>
  275. private void OnUsed(EntityUid uid, NetworkConfiguratorComponent configurator, EntityUid? target, EntityUid user, bool canReach = true)
  276. {
  277. if (!canReach || !target.HasValue)
  278. return;
  279. DetermineMode(uid, configurator, target, user);
  280. if (configurator.LinkModeActive)
  281. {
  282. TryLinkDevice(uid, configurator, target, user);
  283. return;
  284. }
  285. if (!HasComp<DeviceListComponent>(target))
  286. {
  287. TryAddNetworkDevice(uid, target, user, configurator);
  288. return;
  289. }
  290. OpenDeviceListUi(uid, target, user, configurator);
  291. }
  292. private void DetermineMode(EntityUid configuratorUid, NetworkConfiguratorComponent configurator, EntityUid? target, EntityUid userUid)
  293. {
  294. var hasLinking = HasComp<DeviceLinkSinkComponent>(target) || HasComp<DeviceLinkSourceComponent>(target);
  295. if (hasLinking && HasComp<DeviceListComponent>(target) || hasLinking == configurator.LinkModeActive)
  296. return;
  297. if (hasLinking)
  298. {
  299. SetMode(configuratorUid, configurator, userUid, true);
  300. return;
  301. }
  302. if (HasComp<DeviceNetworkComponent>(target))
  303. SetMode(configuratorUid, configurator, userUid, false);
  304. }
  305. #endregion
  306. #region Verbs
  307. /// <summary>
  308. /// Adds the interaction verb which is either configuring device lists or saving a device onto the configurator
  309. /// </summary>
  310. private void OnAddInteractVerb(EntityUid uid, NetworkConfiguratorComponent configurator, GetVerbsEvent<UtilityVerb> args)
  311. {
  312. if (!args.CanAccess || !args.CanInteract || !args.Using.HasValue)
  313. return;
  314. var verb = new UtilityVerb
  315. {
  316. Act = () => OnUsed(uid, configurator, args.Target, args.User),
  317. Impact = LogImpact.Low
  318. };
  319. if (configurator.LinkModeActive && (HasComp<DeviceLinkSinkComponent>(args.Target) || HasComp<DeviceLinkSourceComponent>(args.Target)))
  320. {
  321. var linkStarted = configurator.ActiveDeviceLink.HasValue;
  322. verb.Text = Loc.GetString(linkStarted ? "network-configurator-link" : "network-configurator-start-link");
  323. verb.Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/in.svg.192dpi.png"));
  324. args.Verbs.Add(verb);
  325. }
  326. else if (HasComp<DeviceNetworkComponent>(args.Target))
  327. {
  328. var isDeviceList = HasComp<DeviceListComponent>(args.Target);
  329. verb.Text = Loc.GetString(isDeviceList ? "network-configurator-configure" : "network-configurator-save-device");
  330. verb.Icon = isDeviceList
  331. ? new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/settings.svg.192dpi.png"))
  332. : new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/in.svg.192dpi.png"));
  333. args.Verbs.Add(verb);
  334. }
  335. }
  336. /// <summary>
  337. /// Powerful. Funny alt interact using.
  338. /// Adds an alternative verb for saving a device on the configurator for entities with the <see cref="DeviceListComponent"/>.
  339. /// Allows alt clicking entities with a network configurator that would otherwise trigger a different action like entities
  340. /// with a <see cref="DeviceListComponent"/>
  341. /// </summary>
  342. private void OnAddAlternativeSaveDeviceVerb(EntityUid uid, DeviceNetworkComponent component, GetVerbsEvent<AlternativeVerb> args)
  343. {
  344. if (!args.CanAccess || !args.CanInteract || !args.Using.HasValue
  345. || !TryComp<NetworkConfiguratorComponent>(args.Using.Value, out var configurator))
  346. return;
  347. if (!configurator.LinkModeActive && HasComp<DeviceListComponent>(args.Target))
  348. {
  349. AlternativeVerb verb = new()
  350. {
  351. Text = Loc.GetString("network-configurator-save-device"),
  352. Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/in.svg.192dpi.png")),
  353. Act = () => TryAddNetworkDevice(args.Target, args.Using.Value, args.User),
  354. Impact = LogImpact.Low
  355. };
  356. args.Verbs.Add(verb);
  357. return;
  358. }
  359. if (configurator is { LinkModeActive: true, ActiveDeviceLink: { } }
  360. && (HasComp<DeviceLinkSinkComponent>(args.Target) || HasComp<DeviceLinkSourceComponent>(args.Target)))
  361. {
  362. AlternativeVerb verb = new()
  363. {
  364. Text = Loc.GetString("network-configurator-link-defaults"),
  365. Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/in.svg.192dpi.png")),
  366. Act = () => TryLinkDefaults(args.Using.Value, configurator, args.Target, args.User),
  367. Impact = LogImpact.Low
  368. };
  369. args.Verbs.Add(verb);
  370. }
  371. }
  372. private void OnAddSwitchModeVerb(EntityUid uid, NetworkConfiguratorComponent configurator, GetVerbsEvent<AlternativeVerb> args)
  373. {
  374. if (!args.CanAccess || !args.CanInteract || !args.Using.HasValue || !HasComp<NetworkConfiguratorComponent>(args.Target))
  375. return;
  376. AlternativeVerb verb = new()
  377. {
  378. Text = Loc.GetString("network-configurator-switch-mode"),
  379. Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")),
  380. Act = () => SwitchMode(args.User, args.Target, configurator),
  381. Impact = LogImpact.Low
  382. };
  383. args.Verbs.Add(verb);
  384. }
  385. #endregion
  386. #region UI
  387. private void OpenDeviceLinkUi(EntityUid configuratorUid, EntityUid? targetUid, EntityUid userUid, NetworkConfiguratorComponent configurator)
  388. {
  389. if (Delay(configurator))
  390. return;
  391. if (!targetUid.HasValue || !configurator.ActiveDeviceLink.HasValue || !AccessCheck(targetUid.Value, userUid, configurator))
  392. return;
  393. _uiSystem.OpenUi(configuratorUid, NetworkConfiguratorUiKey.Link, userUid);
  394. configurator.DeviceLinkTarget = targetUid;
  395. if (TryComp(configurator.ActiveDeviceLink, out DeviceLinkSourceComponent? activeSource) && TryComp(targetUid, out DeviceLinkSinkComponent? targetSink))
  396. {
  397. UpdateLinkUiState(configuratorUid, configurator.ActiveDeviceLink.Value, targetUid.Value, activeSource, targetSink);
  398. }
  399. else if (TryComp(configurator.ActiveDeviceLink, out DeviceLinkSinkComponent? activeSink)
  400. && TryComp(targetUid, out DeviceLinkSourceComponent? targetSource))
  401. {
  402. UpdateLinkUiState(configuratorUid, targetUid.Value, configurator.ActiveDeviceLink.Value, targetSource, activeSink);
  403. }
  404. }
  405. private void UpdateLinkUiState(EntityUid configuratorUid, EntityUid sourceUid, EntityUid sinkUid,
  406. DeviceLinkSourceComponent? sourceComponent = null, DeviceLinkSinkComponent? sinkComponent = null,
  407. DeviceNetworkComponent? sourceNetworkComponent = null, DeviceNetworkComponent? sinkNetworkComponent = null)
  408. {
  409. if (!Resolve(sourceUid, ref sourceComponent, false) || !Resolve(sinkUid, ref sinkComponent, false))
  410. return;
  411. var sources = _deviceLinkSystem.GetSourcePorts(sourceUid, sourceComponent);
  412. var sinks = _deviceLinkSystem.GetSinkPorts(sinkUid, sinkComponent);
  413. var links = _deviceLinkSystem.GetLinks(sourceUid, sinkUid, sourceComponent);
  414. var defaults = _deviceLinkSystem.GetDefaults(sources);
  415. var sourceAddress = Resolve(sourceUid, ref sourceNetworkComponent, false) ? sourceNetworkComponent.Address : "";
  416. var sinkAddress = Resolve(sinkUid, ref sinkNetworkComponent, false) ? sinkNetworkComponent.Address : "";
  417. var state = new DeviceLinkUserInterfaceState(sources, sinks, links, sourceAddress, sinkAddress, defaults);
  418. _uiSystem.SetUiState(configuratorUid, NetworkConfiguratorUiKey.Link, state);
  419. }
  420. /// <summary>
  421. /// Opens the config ui. It can be used to modify the devices in the targets device list.
  422. /// </summary>
  423. private void OpenDeviceListUi(EntityUid configuratorUid, EntityUid? targetUid, EntityUid userUid, NetworkConfiguratorComponent configurator)
  424. {
  425. if (configurator.ActiveDeviceLink == targetUid)
  426. return;
  427. if (Delay(configurator))
  428. return;
  429. if (!targetUid.HasValue || !AccessCheck(targetUid.Value, userUid, configurator))
  430. return;
  431. if (!TryComp(targetUid, out DeviceListComponent? list))
  432. return;
  433. if (TryComp(configurator.ActiveDeviceList, out DeviceListComponent? oldList))
  434. oldList.Configurators.Remove(configuratorUid);
  435. list.Configurators.Add(configuratorUid);
  436. configurator.ActiveDeviceList = targetUid;
  437. Dirty(configuratorUid, configurator);
  438. if (_uiSystem.TryOpenUi(configuratorUid, NetworkConfiguratorUiKey.Configure, userUid))
  439. {
  440. _uiSystem.SetUiState(configuratorUid, NetworkConfiguratorUiKey.Configure, new DeviceListUserInterfaceState(
  441. _deviceListSystem.GetDeviceList(configurator.ActiveDeviceList.Value)
  442. .Select(v => (v.Key, MetaData(v.Value).EntityName)).ToHashSet()
  443. ));
  444. }
  445. }
  446. /// <summary>
  447. /// Sends the list of saved devices to the ui
  448. /// </summary>
  449. private void UpdateListUiState(EntityUid uid, NetworkConfiguratorComponent component)
  450. {
  451. HashSet<(string address, string name)> devices = new();
  452. HashSet<string> invalidDevices = new();
  453. foreach (var pair in component.Devices)
  454. {
  455. if (!Exists(pair.Value))
  456. {
  457. invalidDevices.Add(pair.Key);
  458. continue;
  459. }
  460. devices.Add((pair.Key, Name(pair.Value)));
  461. }
  462. //Remove saved entities that don't exist anymore
  463. foreach (var invalidDevice in invalidDevices)
  464. {
  465. component.Devices.Remove(invalidDevice);
  466. }
  467. _uiSystem.SetUiState(uid, NetworkConfiguratorUiKey.List, new NetworkConfiguratorUserInterfaceState(devices));
  468. }
  469. /// <summary>
  470. /// Clears the active device list when the ui is closed
  471. /// </summary>
  472. private void OnUiClosed(EntityUid uid, NetworkConfiguratorComponent component, BoundUIClosedEvent args)
  473. {
  474. if (!args.UiKey.Equals(NetworkConfiguratorUiKey.Configure)
  475. && !args.UiKey.Equals(NetworkConfiguratorUiKey.Link)
  476. && !args.UiKey.Equals(NetworkConfiguratorUiKey.List))
  477. {
  478. return;
  479. }
  480. if (TryComp(component.ActiveDeviceList, out DeviceListComponent? list))
  481. {
  482. list.Configurators.Remove(uid);
  483. }
  484. component.ActiveDeviceList = null;
  485. if (args.UiKey is NetworkConfiguratorUiKey.Link)
  486. {
  487. component.ActiveDeviceLink = null;
  488. component.DeviceLinkTarget = null;
  489. }
  490. }
  491. public void OnDeviceListShutdown(Entity<NetworkConfiguratorComponent?> conf, Entity<DeviceListComponent> list)
  492. {
  493. list.Comp.Configurators.Remove(conf.Owner);
  494. if (Resolve(conf.Owner, ref conf.Comp))
  495. conf.Comp.ActiveDeviceList = null;
  496. }
  497. /// <summary>
  498. /// Removes a device from the saved devices list
  499. /// </summary>
  500. private void OnRemoveDevice(EntityUid uid, NetworkConfiguratorComponent component, NetworkConfiguratorRemoveDeviceMessage args)
  501. {
  502. if (component.Devices.TryGetValue(args.Address, out var removedDevice))
  503. {
  504. _adminLogger.Add(LogType.DeviceLinking, LogImpact.Low,
  505. $"{ToPrettyString(args.Actor):actor} removed buffered device {ToPrettyString(removedDevice):subject} from {ToPrettyString(uid):tool}");
  506. }
  507. component.Devices.Remove(args.Address);
  508. if (TryComp(removedDevice, out DeviceNetworkComponent? device))
  509. device.Configurators.Remove(uid);
  510. UpdateListUiState(uid, component);
  511. }
  512. /// <summary>
  513. /// Clears the saved devices
  514. /// </summary>
  515. private void OnClearDevice(EntityUid uid, NetworkConfiguratorComponent component, NetworkConfiguratorClearDevicesMessage args)
  516. {
  517. _adminLogger.Add(LogType.DeviceLinking, LogImpact.Low,
  518. $"{ToPrettyString(args.Actor):actor} cleared buffered devices from {ToPrettyString(uid):tool}");
  519. ClearDevices(uid, component);
  520. UpdateListUiState(uid, component);
  521. }
  522. private void ClearDevices(EntityUid uid, NetworkConfiguratorComponent component)
  523. {
  524. var query = GetEntityQuery<DeviceNetworkComponent>();
  525. foreach (var device in component.Devices.Values)
  526. {
  527. if (query.TryGetComponent(device, out var comp))
  528. comp.Configurators.Remove(uid);
  529. }
  530. component.Devices.Clear();
  531. }
  532. private void OnClearLinks(EntityUid uid, NetworkConfiguratorComponent configurator, NetworkConfiguratorClearLinksMessage args)
  533. {
  534. if (!configurator.ActiveDeviceLink.HasValue || !configurator.DeviceLinkTarget.HasValue)
  535. return;
  536. _adminLogger.Add(LogType.DeviceLinking, LogImpact.Low,
  537. $"{ToPrettyString(args.Actor):actor} cleared links between {ToPrettyString(configurator.ActiveDeviceLink.Value):subject} and {ToPrettyString(configurator.DeviceLinkTarget.Value):subject2} with {ToPrettyString(uid):tool}");
  538. if (HasComp<DeviceLinkSourceComponent>(configurator.ActiveDeviceLink) && HasComp<DeviceLinkSinkComponent>(configurator.DeviceLinkTarget))
  539. {
  540. _deviceLinkSystem.RemoveSinkFromSource(
  541. configurator.ActiveDeviceLink.Value,
  542. configurator.DeviceLinkTarget.Value
  543. );
  544. UpdateLinkUiState(
  545. uid,
  546. configurator.ActiveDeviceLink.Value,
  547. configurator.DeviceLinkTarget.Value
  548. );
  549. }
  550. else if (HasComp<DeviceLinkSourceComponent>(configurator.DeviceLinkTarget) && HasComp<DeviceLinkSinkComponent>(configurator.ActiveDeviceLink))
  551. {
  552. _deviceLinkSystem.RemoveSinkFromSource(
  553. configurator.DeviceLinkTarget.Value,
  554. configurator.ActiveDeviceLink.Value
  555. );
  556. UpdateLinkUiState(
  557. uid,
  558. configurator.DeviceLinkTarget.Value,
  559. configurator.ActiveDeviceLink.Value
  560. );
  561. }
  562. }
  563. private void OnToggleLinks(EntityUid uid, NetworkConfiguratorComponent configurator, NetworkConfiguratorToggleLinkMessage args)
  564. {
  565. if (!configurator.ActiveDeviceLink.HasValue || !configurator.DeviceLinkTarget.HasValue)
  566. return;
  567. if (TryComp(configurator.ActiveDeviceLink, out DeviceLinkSourceComponent? activeSource) && TryComp(configurator.DeviceLinkTarget, out DeviceLinkSinkComponent? targetSink))
  568. {
  569. _deviceLinkSystem.ToggleLink(
  570. args.Actor,
  571. configurator.ActiveDeviceLink.Value,
  572. configurator.DeviceLinkTarget.Value,
  573. args.Source, args.Sink,
  574. activeSource, targetSink);
  575. UpdateLinkUiState(uid, configurator.ActiveDeviceLink.Value, configurator.DeviceLinkTarget.Value, activeSource);
  576. }
  577. else if (TryComp(configurator.DeviceLinkTarget, out DeviceLinkSourceComponent? targetSource) && TryComp(configurator.ActiveDeviceLink, out DeviceLinkSinkComponent? activeSink))
  578. {
  579. _deviceLinkSystem.ToggleLink(
  580. args.Actor,
  581. configurator.DeviceLinkTarget.Value,
  582. configurator.ActiveDeviceLink.Value,
  583. args.Source, args.Sink,
  584. targetSource, activeSink
  585. );
  586. UpdateLinkUiState(
  587. uid,
  588. configurator.DeviceLinkTarget.Value,
  589. configurator.ActiveDeviceLink.Value,
  590. targetSource
  591. );
  592. }
  593. }
  594. /// <summary>
  595. /// Saves links set by the device link UI
  596. /// </summary>
  597. private void OnSaveLinks(EntityUid uid, NetworkConfiguratorComponent configurator, NetworkConfiguratorLinksSaveMessage args)
  598. {
  599. if (!configurator.ActiveDeviceLink.HasValue || !configurator.DeviceLinkTarget.HasValue)
  600. return;
  601. if (TryComp(configurator.ActiveDeviceLink, out DeviceLinkSourceComponent? activeSource) && TryComp(configurator.DeviceLinkTarget, out DeviceLinkSinkComponent? targetSink))
  602. {
  603. _deviceLinkSystem.SaveLinks(
  604. args.Actor,
  605. configurator.ActiveDeviceLink.Value,
  606. configurator.DeviceLinkTarget.Value,
  607. args.Links,
  608. activeSource,
  609. targetSink
  610. );
  611. UpdateLinkUiState(
  612. uid,
  613. configurator.ActiveDeviceLink.Value,
  614. configurator.DeviceLinkTarget.Value,
  615. activeSource
  616. );
  617. }
  618. else if (TryComp(configurator.DeviceLinkTarget, out DeviceLinkSourceComponent? targetSource) && TryComp(configurator.ActiveDeviceLink, out DeviceLinkSinkComponent? activeSink))
  619. {
  620. _deviceLinkSystem.SaveLinks(
  621. args.Actor,
  622. configurator.DeviceLinkTarget.Value,
  623. configurator.ActiveDeviceLink.Value,
  624. args.Links,
  625. targetSource,
  626. activeSink
  627. );
  628. UpdateLinkUiState(
  629. uid,
  630. configurator.DeviceLinkTarget.Value,
  631. configurator.ActiveDeviceLink.Value,
  632. targetSource
  633. );
  634. }
  635. }
  636. /// <summary>
  637. /// Handles all the button presses from the config ui.
  638. /// Modifies, copies or visualizes the targets device list
  639. /// </summary>
  640. private void OnConfigButtonPressed(EntityUid uid, NetworkConfiguratorComponent component, NetworkConfiguratorButtonPressedMessage args)
  641. {
  642. if (!component.ActiveDeviceList.HasValue)
  643. return;
  644. var result = DeviceListUpdateResult.NoComponent;
  645. switch (args.ButtonKey)
  646. {
  647. case NetworkConfiguratorButtonKey.Set:
  648. _adminLogger.Add(LogType.DeviceLinking, LogImpact.Low,
  649. $"{ToPrettyString(args.Actor):actor} set device links to {ToPrettyString(component.ActiveDeviceList.Value):subject} with {ToPrettyString(uid):tool}");
  650. result = _deviceListSystem.UpdateDeviceList(component.ActiveDeviceList.Value, new HashSet<EntityUid>(component.Devices.Values));
  651. break;
  652. case NetworkConfiguratorButtonKey.Add:
  653. _adminLogger.Add(LogType.DeviceLinking, LogImpact.Low,
  654. $"{ToPrettyString(args.Actor):actor} added device links to {ToPrettyString(component.ActiveDeviceList.Value):subject} with {ToPrettyString(uid):tool}");
  655. result = _deviceListSystem.UpdateDeviceList(component.ActiveDeviceList.Value, new HashSet<EntityUid>(component.Devices.Values), true);
  656. break;
  657. case NetworkConfiguratorButtonKey.Clear:
  658. _adminLogger.Add(LogType.DeviceLinking, LogImpact.Low,
  659. $"{ToPrettyString(args.Actor):actor} cleared device links from {ToPrettyString(component.ActiveDeviceList.Value):subject} with {ToPrettyString(uid):tool}");
  660. result = _deviceListSystem.UpdateDeviceList(component.ActiveDeviceList.Value, new HashSet<EntityUid>());
  661. break;
  662. case NetworkConfiguratorButtonKey.Copy:
  663. _adminLogger.Add(LogType.DeviceLinking, LogImpact.Low,
  664. $"{ToPrettyString(args.Actor):actor} copied devices from {ToPrettyString(component.ActiveDeviceList.Value):subject} to {ToPrettyString(uid):tool}");
  665. ClearDevices(uid, component);
  666. var query = GetEntityQuery<DeviceNetworkComponent>();
  667. foreach (var (addr, device) in _deviceListSystem.GetDeviceList(component.ActiveDeviceList.Value))
  668. {
  669. if (query.TryGetComponent(device, out var comp))
  670. {
  671. component.Devices.Add(addr, device);
  672. comp.Configurators.Add(uid);
  673. }
  674. }
  675. UpdateListUiState(uid, component);
  676. return;
  677. case NetworkConfiguratorButtonKey.Show:
  678. break;
  679. }
  680. var resultText = result switch
  681. {
  682. DeviceListUpdateResult.TooManyDevices => Loc.GetString("network-configurator-too-many-devices"),
  683. DeviceListUpdateResult.UpdateOk => Loc.GetString("network-configurator-update-ok"),
  684. _ => "error"
  685. };
  686. _popupSystem.PopupCursor(Loc.GetString(resultText), args.Actor, PopupType.Medium);
  687. _uiSystem.SetUiState(
  688. uid,
  689. NetworkConfiguratorUiKey.Configure,
  690. new DeviceListUserInterfaceState(
  691. _deviceListSystem.GetDeviceList(component.ActiveDeviceList.Value)
  692. .Select(v => (v.Key, MetaData(v.Value).EntityName)).ToHashSet()));
  693. }
  694. public void OnDeviceShutdown(Entity<NetworkConfiguratorComponent?> conf, Entity<DeviceNetworkComponent> device)
  695. {
  696. device.Comp.Configurators.Remove(conf.Owner);
  697. if (!Resolve(conf.Owner, ref conf.Comp))
  698. return;
  699. foreach (var (addr, dev) in conf.Comp.Devices)
  700. {
  701. if (device.Owner == dev)
  702. conf.Comp.Devices.Remove(addr);
  703. }
  704. UpdateListUiState(conf, conf.Comp);
  705. }
  706. private void OnUiOpenAttempt(EntityUid uid, NetworkConfiguratorComponent configurator, ActivatableUIOpenAttemptEvent args)
  707. {
  708. if (configurator.LinkModeActive)
  709. args.Cancel();
  710. }
  711. #endregion
  712. }