AdminVerbSystem.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631
  1. using Content.Server.Administration.Logs;
  2. using Content.Server.Administration.Managers;
  3. using Content.Server.Administration.UI;
  4. using Content.Server.Disposal.Tube;
  5. using Content.Server.Disposal.Tube.Components;
  6. using Content.Server.EUI;
  7. using Content.Server.GameTicking;
  8. using Content.Server.Ghost.Roles;
  9. using Content.Server.Mind;
  10. using Content.Server.Mind.Commands;
  11. using Content.Server.Prayer;
  12. using Content.Server.Station.Systems;
  13. using Content.Server.Xenoarchaeology.XenoArtifacts;
  14. using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
  15. using Content.Shared.Administration;
  16. using Content.Shared.Chemistry.Components.SolutionManager;
  17. using Content.Shared.Chemistry.EntitySystems;
  18. using Content.Shared.Configurable;
  19. using Content.Shared.Database;
  20. using Content.Shared.Examine;
  21. using Content.Shared.GameTicking;
  22. using Content.Shared.Inventory;
  23. using Content.Shared.Mind.Components;
  24. using Content.Shared.Popups;
  25. using Content.Shared.Verbs;
  26. using Robust.Server.Console;
  27. using Robust.Server.GameObjects;
  28. using Robust.Shared.Console;
  29. using Robust.Shared.Map;
  30. using Robust.Shared.Map.Components;
  31. using Robust.Shared.Player;
  32. using Robust.Shared.Prototypes;
  33. using Robust.Shared.Timing;
  34. using Robust.Shared.Toolshed;
  35. using Robust.Shared.Utility;
  36. using System.Linq;
  37. using Content.Server.Silicons.Laws;
  38. using Content.Shared.Movement.Components;
  39. using Content.Shared.Silicons.Laws.Components;
  40. using Robust.Server.Player;
  41. using Content.Shared.Silicons.StationAi;
  42. using Robust.Shared.Physics.Components;
  43. using static Content.Shared.Configurable.ConfigurationComponent;
  44. namespace Content.Server.Administration.Systems
  45. {
  46. /// <summary>
  47. /// System to provide various global admin/debug verbs
  48. /// </summary>
  49. public sealed partial class AdminVerbSystem : EntitySystem
  50. {
  51. [Dependency] private readonly IConGroupController _groupController = default!;
  52. [Dependency] private readonly IConsoleHost _console = default!;
  53. [Dependency] private readonly IAdminManager _adminManager = default!;
  54. [Dependency] private readonly IGameTiming _gameTiming = default!;
  55. [Dependency] private readonly IMapManager _mapManager = default!;
  56. [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
  57. [Dependency] private readonly AdminSystem _adminSystem = default!;
  58. [Dependency] private readonly DisposalTubeSystem _disposalTubes = default!;
  59. [Dependency] private readonly EuiManager _euiManager = default!;
  60. [Dependency] private readonly GameTicker _ticker = default!;
  61. [Dependency] private readonly GhostRoleSystem _ghostRoleSystem = default!;
  62. [Dependency] private readonly ArtifactSystem _artifactSystem = default!;
  63. [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
  64. [Dependency] private readonly PrayerSystem _prayerSystem = default!;
  65. [Dependency] private readonly MindSystem _mindSystem = default!;
  66. [Dependency] private readonly ToolshedManager _toolshed = default!;
  67. [Dependency] private readonly RejuvenateSystem _rejuvenate = default!;
  68. [Dependency] private readonly SharedPopupSystem _popup = default!;
  69. [Dependency] private readonly StationSystem _stations = default!;
  70. [Dependency] private readonly StationSpawningSystem _spawning = default!;
  71. [Dependency] private readonly ExamineSystemShared _examine = default!;
  72. [Dependency] private readonly AdminFrozenSystem _freeze = default!;
  73. [Dependency] private readonly IPlayerManager _playerManager = default!;
  74. [Dependency] private readonly SiliconLawSystem _siliconLawSystem = default!;
  75. private readonly Dictionary<ICommonSession, List<EditSolutionsEui>> _openSolutionUis = new();
  76. public override void Initialize()
  77. {
  78. SubscribeLocalEvent<GetVerbsEvent<Verb>>(GetVerbs);
  79. SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
  80. SubscribeLocalEvent<SolutionContainerManagerComponent, SolutionContainerChangedEvent>(OnSolutionChanged);
  81. }
  82. private void GetVerbs(GetVerbsEvent<Verb> ev)
  83. {
  84. AddAdminVerbs(ev);
  85. AddDebugVerbs(ev);
  86. AddSmiteVerbs(ev);
  87. AddTricksVerbs(ev);
  88. AddAntagVerbs(ev);
  89. }
  90. private void AddAdminVerbs(GetVerbsEvent<Verb> args)
  91. {
  92. if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor))
  93. return;
  94. var player = actor.PlayerSession;
  95. if (_adminManager.IsAdmin(player))
  96. {
  97. Verb mark = new();
  98. mark.Text = Loc.GetString("toolshed-verb-mark");
  99. mark.Message = Loc.GetString("toolshed-verb-mark-description");
  100. mark.Category = VerbCategory.Admin;
  101. mark.Act = () => _toolshed.InvokeCommand(player, "=> $marked", new List<EntityUid> {args.Target}, out _);
  102. mark.Impact = LogImpact.Low;
  103. args.Verbs.Add(mark);
  104. if (TryComp(args.Target, out ActorComponent? targetActor))
  105. {
  106. // AdminHelp
  107. Verb verb = new();
  108. verb.Text = Loc.GetString("ahelp-verb-get-data-text");
  109. verb.Category = VerbCategory.Admin;
  110. verb.Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/gavel.svg.192dpi.png"));
  111. verb.Act = () =>
  112. _console.RemoteExecuteCommand(player, $"openahelp \"{targetActor.PlayerSession.UserId}\"");
  113. verb.Impact = LogImpact.Low;
  114. args.Verbs.Add(verb);
  115. // Subtle Messages
  116. Verb prayerVerb = new();
  117. prayerVerb.Text = Loc.GetString("prayer-verbs-subtle-message");
  118. prayerVerb.Category = VerbCategory.Admin;
  119. prayerVerb.Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/pray.svg.png"));
  120. prayerVerb.Act = () =>
  121. {
  122. _quickDialog.OpenDialog(player, "Subtle Message", "Message", "Popup Message", (string message, string popupMessage) =>
  123. {
  124. _prayerSystem.SendSubtleMessage(targetActor.PlayerSession, player, message, popupMessage == "" ? Loc.GetString("prayer-popup-subtle-default") : popupMessage);
  125. });
  126. };
  127. prayerVerb.Impact = LogImpact.Low;
  128. args.Verbs.Add(prayerVerb);
  129. // Spawn - Like respawn but on the spot.
  130. args.Verbs.Add(new Verb()
  131. {
  132. Text = Loc.GetString("admin-player-actions-spawn"),
  133. Category = VerbCategory.Admin,
  134. Act = () =>
  135. {
  136. if (!_transformSystem.TryGetMapOrGridCoordinates(args.Target, out var coords))
  137. {
  138. _popup.PopupEntity(Loc.GetString("admin-player-spawn-failed"), args.User, args.User);
  139. return;
  140. }
  141. var stationUid = _stations.GetOwningStation(args.Target);
  142. var profile = _ticker.GetPlayerProfile(targetActor.PlayerSession);
  143. var mobUid = _spawning.SpawnPlayerMob(coords.Value, null, profile, stationUid);
  144. var targetMind = _mindSystem.GetMind(args.Target);
  145. if (targetMind != null)
  146. {
  147. _mindSystem.TransferTo(targetMind.Value, mobUid, true);
  148. }
  149. },
  150. ConfirmationPopup = true,
  151. Impact = LogImpact.High,
  152. });
  153. // Clone - Spawn but without the mind transfer, also spawns at the user's coordinates not the target's
  154. args.Verbs.Add(new Verb()
  155. {
  156. Text = Loc.GetString("admin-player-actions-clone"),
  157. Category = VerbCategory.Admin,
  158. Act = () =>
  159. {
  160. if (!_transformSystem.TryGetMapOrGridCoordinates(args.User, out var coords))
  161. {
  162. _popup.PopupEntity(Loc.GetString("admin-player-spawn-failed"), args.User, args.User);
  163. return;
  164. }
  165. var stationUid = _stations.GetOwningStation(args.Target);
  166. var profile = _ticker.GetPlayerProfile(targetActor.PlayerSession);
  167. _spawning.SpawnPlayerMob(coords.Value, null, profile, stationUid);
  168. },
  169. ConfirmationPopup = true,
  170. Impact = LogImpact.High,
  171. });
  172. // PlayerPanel
  173. args.Verbs.Add(new Verb
  174. {
  175. Text = Loc.GetString("admin-player-actions-player-panel"),
  176. Category = VerbCategory.Admin,
  177. Act = () => _console.ExecuteCommand(player, $"playerpanel \"{targetActor.PlayerSession.UserId}\""),
  178. Impact = LogImpact.Low
  179. });
  180. }
  181. if (_mindSystem.TryGetMind(args.Target, out _, out var mind) && mind.UserId != null)
  182. {
  183. // Erase
  184. args.Verbs.Add(new Verb
  185. {
  186. Text = Loc.GetString("admin-verbs-erase"),
  187. Message = Loc.GetString("admin-verbs-erase-description"),
  188. Category = VerbCategory.Admin,
  189. Icon = new SpriteSpecifier.Texture(
  190. new("/Textures/Interface/VerbIcons/delete_transparent.svg.192dpi.png")),
  191. Act = () =>
  192. {
  193. _adminSystem.Erase(mind.UserId.Value);
  194. },
  195. Impact = LogImpact.Extreme,
  196. ConfirmationPopup = true
  197. });
  198. // Respawn
  199. args.Verbs.Add(new Verb
  200. {
  201. Text = Loc.GetString("admin-player-actions-respawn"),
  202. Category = VerbCategory.Admin,
  203. Act = () =>
  204. {
  205. _console.ExecuteCommand(player, $"respawn \"{mind.UserId}\"");
  206. },
  207. ConfirmationPopup = true,
  208. // No logimpact as the command does it internally.
  209. });
  210. }
  211. // Freeze
  212. var frozen = TryComp<AdminFrozenComponent>(args.Target, out var frozenComp);
  213. var frozenAndMuted = frozenComp?.Muted ?? false;
  214. if (!frozen)
  215. {
  216. args.Verbs.Add(new Verb
  217. {
  218. Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze.
  219. Text = Loc.GetString("admin-verbs-freeze"),
  220. Category = VerbCategory.Admin,
  221. Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")),
  222. Act = () =>
  223. {
  224. EnsureComp<AdminFrozenComponent>(args.Target);
  225. },
  226. Impact = LogImpact.Medium,
  227. });
  228. }
  229. if (!frozenAndMuted)
  230. {
  231. // allow you to additionally mute someone when they are already frozen
  232. args.Verbs.Add(new Verb
  233. {
  234. Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze.
  235. Text = Loc.GetString("admin-verbs-freeze-and-mute"),
  236. Category = VerbCategory.Admin,
  237. Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")),
  238. Act = () =>
  239. {
  240. _freeze.FreezeAndMute(args.Target);
  241. },
  242. Impact = LogImpact.Medium,
  243. });
  244. }
  245. if (frozen)
  246. {
  247. args.Verbs.Add(new Verb
  248. {
  249. Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze.
  250. Text = Loc.GetString("admin-verbs-unfreeze"),
  251. Category = VerbCategory.Admin,
  252. Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")),
  253. Act = () =>
  254. {
  255. RemComp<AdminFrozenComponent>(args.Target);
  256. },
  257. Impact = LogImpact.Medium,
  258. });
  259. }
  260. // Admin Logs
  261. if (_adminManager.HasAdminFlag(player, AdminFlags.Logs))
  262. {
  263. Verb logsVerbEntity = new()
  264. {
  265. Priority = -2,
  266. Text = Loc.GetString("admin-verbs-admin-logs-entity"),
  267. Category = VerbCategory.Admin,
  268. Act = () =>
  269. {
  270. var ui = new AdminLogsEui();
  271. _euiManager.OpenEui(ui, player);
  272. ui.SetLogFilter(search:args.Target.Id.ToString());
  273. },
  274. Impact = LogImpact.Low
  275. };
  276. args.Verbs.Add(logsVerbEntity);
  277. }
  278. // TeleportTo
  279. args.Verbs.Add(new Verb
  280. {
  281. Text = Loc.GetString("admin-verbs-teleport-to"),
  282. Category = VerbCategory.Admin,
  283. Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/open.svg.192dpi.png")),
  284. Act = () =>
  285. {
  286. _console.ExecuteCommand(player, $"tpto {GetNetEntity(args.Target)}");
  287. },
  288. Impact = LogImpact.Low
  289. });
  290. // TeleportHere
  291. args.Verbs.Add(new Verb
  292. {
  293. Text = Loc.GetString("admin-verbs-teleport-here"),
  294. Category = VerbCategory.Admin,
  295. Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/close.svg.192dpi.png")),
  296. Act = () =>
  297. {
  298. if (HasComp<MapGridComponent>(args.Target))
  299. {
  300. if (player.AttachedEntity != null)
  301. {
  302. var mapPos = _transformSystem.GetMapCoordinates(player.AttachedEntity.Value);
  303. if (TryComp(args.Target, out PhysicsComponent? targetPhysics))
  304. {
  305. var offset = targetPhysics.LocalCenter;
  306. var rotation = _transformSystem.GetWorldRotation(args.Target);
  307. offset = rotation.RotateVec(offset);
  308. mapPos = mapPos.Offset(-offset);
  309. }
  310. _console.ExecuteCommand(player, $"tpgrid {GetNetEntity(args.Target)} {mapPos.X} {mapPos.Y} {mapPos.MapId}");
  311. }
  312. }
  313. else
  314. {
  315. _console.ExecuteCommand(player, $"tpto {args.User} {args.Target}");
  316. }
  317. },
  318. Impact = LogImpact.Low
  319. });
  320. // This logic is needed to be able to modify the AI's laws through its core and eye.
  321. EntityUid? target = null;
  322. SiliconLawBoundComponent? lawBoundComponent = null;
  323. if (TryComp(args.Target, out lawBoundComponent))
  324. {
  325. target = args.Target;
  326. }
  327. // When inspecting the core we can find the entity with its laws by looking at the AiHolderComponent.
  328. else if (TryComp<StationAiHolderComponent>(args.Target, out var holder) && holder.Slot.Item != null
  329. && TryComp(holder.Slot.Item, out lawBoundComponent))
  330. {
  331. target = holder.Slot.Item.Value;
  332. // For the eye we can find the entity with its laws as the source of the movement relay since the eye
  333. // is just a proxy for it to move around and look around the station.
  334. }
  335. else if (TryComp<MovementRelayTargetComponent>(args.Target, out var relay)
  336. && TryComp(relay.Source, out lawBoundComponent))
  337. {
  338. target = relay.Source;
  339. }
  340. if (lawBoundComponent != null && target != null && _adminManager.HasAdminFlag(player, AdminFlags.Moderator))
  341. {
  342. args.Verbs.Add(new Verb()
  343. {
  344. Text = Loc.GetString("silicon-law-ui-verb"),
  345. Category = VerbCategory.Admin,
  346. Act = () =>
  347. {
  348. var ui = new SiliconLawEui(_siliconLawSystem, EntityManager, _adminManager);
  349. if (!_playerManager.TryGetSessionByEntity(args.User, out var session))
  350. {
  351. return;
  352. }
  353. _euiManager.OpenEui(ui, session);
  354. ui.UpdateLaws(lawBoundComponent, target.Value);
  355. },
  356. Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Interface/Actions/actions_borg.rsi"), "state-laws"),
  357. });
  358. }
  359. }
  360. }
  361. private void AddDebugVerbs(GetVerbsEvent<Verb> args)
  362. {
  363. if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor))
  364. return;
  365. var player = actor.PlayerSession;
  366. // Delete verb
  367. if (_toolshed.ActivePermissionController?.CheckInvokable(new CommandSpec(_toolshed.DefaultEnvironment.GetCommand("delete"), null), player, out _) ?? false)
  368. {
  369. Verb verb = new()
  370. {
  371. Text = Loc.GetString("delete-verb-get-data-text"),
  372. Category = VerbCategory.Debug,
  373. Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/delete_transparent.svg.192dpi.png")),
  374. Act = () => EntityManager.DeleteEntity(args.Target),
  375. Impact = LogImpact.Medium,
  376. ConfirmationPopup = true
  377. };
  378. args.Verbs.Add(verb);
  379. }
  380. // Rejuvenate verb
  381. if (_toolshed.ActivePermissionController?.CheckInvokable(new CommandSpec(_toolshed.DefaultEnvironment.GetCommand("rejuvenate"), null), player, out _) ?? false)
  382. {
  383. Verb verb = new()
  384. {
  385. Text = Loc.GetString("rejuvenate-verb-get-data-text"),
  386. Category = VerbCategory.Debug,
  387. Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/rejuvenate.svg.192dpi.png")),
  388. Act = () => _rejuvenate.PerformRejuvenate(args.Target),
  389. Impact = LogImpact.Medium
  390. };
  391. args.Verbs.Add(verb);
  392. }
  393. // Control mob verb
  394. if (_toolshed.ActivePermissionController?.CheckInvokable(new CommandSpec(_toolshed.DefaultEnvironment.GetCommand("mind"), "control"), player, out _) ?? false &&
  395. args.User != args.Target)
  396. {
  397. Verb verb = new()
  398. {
  399. Text = Loc.GetString("control-mob-verb-get-data-text"),
  400. Category = VerbCategory.Debug,
  401. // TODO VERB ICON control mob icon
  402. Act = () =>
  403. {
  404. _mindSystem.ControlMob(args.User, args.Target);
  405. },
  406. Impact = LogImpact.High,
  407. ConfirmationPopup = true
  408. };
  409. args.Verbs.Add(verb);
  410. }
  411. // XenoArcheology
  412. if (_adminManager.IsAdmin(player) && TryComp<ArtifactComponent>(args.Target, out var artifact))
  413. {
  414. // make artifact always active (by adding timer trigger)
  415. args.Verbs.Add(new Verb()
  416. {
  417. Text = Loc.GetString("artifact-verb-make-always-active"),
  418. Category = VerbCategory.Debug,
  419. Act = () => EntityManager.AddComponent<ArtifactTimerTriggerComponent>(args.Target),
  420. Disabled = EntityManager.HasComponent<ArtifactTimerTriggerComponent>(args.Target),
  421. Impact = LogImpact.High
  422. });
  423. // force to activate artifact ignoring timeout
  424. args.Verbs.Add(new Verb()
  425. {
  426. Text = Loc.GetString("artifact-verb-activate"),
  427. Category = VerbCategory.Debug,
  428. Act = () => _artifactSystem.ForceActivateArtifact(args.Target, component: artifact),
  429. Impact = LogImpact.High
  430. });
  431. }
  432. // Make Sentient verb
  433. if (_groupController.CanCommand(player, "makesentient") &&
  434. args.User != args.Target &&
  435. !EntityManager.HasComponent<MindContainerComponent>(args.Target))
  436. {
  437. Verb verb = new()
  438. {
  439. Text = Loc.GetString("make-sentient-verb-get-data-text"),
  440. Category = VerbCategory.Debug,
  441. Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/sentient.svg.192dpi.png")),
  442. Act = () => MakeSentientCommand.MakeSentient(args.Target, EntityManager),
  443. Impact = LogImpact.Medium
  444. };
  445. args.Verbs.Add(verb);
  446. }
  447. // Set clothing verb
  448. if (_groupController.CanCommand(player, "setoutfit") &&
  449. EntityManager.HasComponent<InventoryComponent>(args.Target))
  450. {
  451. Verb verb = new()
  452. {
  453. Text = Loc.GetString("set-outfit-verb-get-data-text"),
  454. Category = VerbCategory.Debug,
  455. Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/outfit.svg.192dpi.png")),
  456. Act = () => _euiManager.OpenEui(new SetOutfitEui(GetNetEntity(args.Target)), player),
  457. Impact = LogImpact.Medium
  458. };
  459. args.Verbs.Add(verb);
  460. }
  461. // In range unoccluded verb
  462. if (_groupController.CanCommand(player, "inrangeunoccluded"))
  463. {
  464. Verb verb = new()
  465. {
  466. Text = Loc.GetString("in-range-unoccluded-verb-get-data-text"),
  467. Category = VerbCategory.Debug,
  468. Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/information.svg.192dpi.png")),
  469. Act = () =>
  470. {
  471. var message = _examine.InRangeUnOccluded(args.User, args.Target)
  472. ? Loc.GetString("in-range-unoccluded-verb-on-activate-not-occluded")
  473. : Loc.GetString("in-range-unoccluded-verb-on-activate-occluded");
  474. _popup.PopupEntity(message, args.Target, args.User);
  475. }
  476. };
  477. args.Verbs.Add(verb);
  478. }
  479. // Get Disposal tube direction verb
  480. if (_groupController.CanCommand(player, "tubeconnections") &&
  481. EntityManager.TryGetComponent(args.Target, out DisposalTubeComponent? tube))
  482. {
  483. Verb verb = new()
  484. {
  485. Text = Loc.GetString("tube-direction-verb-get-data-text"),
  486. Category = VerbCategory.Debug,
  487. Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/information.svg.192dpi.png")),
  488. Act = () => _disposalTubes.PopupDirections(args.Target, tube, args.User)
  489. };
  490. args.Verbs.Add(verb);
  491. }
  492. // Make ghost role verb
  493. if (_groupController.CanCommand(player, "makeghostrole") &&
  494. !(EntityManager.GetComponentOrNull<MindContainerComponent>(args.Target)?.HasMind ?? false))
  495. {
  496. Verb verb = new();
  497. verb.Text = Loc.GetString("make-ghost-role-verb-get-data-text");
  498. verb.Category = VerbCategory.Debug;
  499. // TODO VERB ICON add ghost icon
  500. // Where is the national park service icon for haunted forests?
  501. verb.Act = () => _ghostRoleSystem.OpenMakeGhostRoleEui(player, args.Target);
  502. verb.Impact = LogImpact.Medium;
  503. args.Verbs.Add(verb);
  504. }
  505. if (_groupController.CanAdminMenu(player) &&
  506. EntityManager.TryGetComponent(args.Target, out ConfigurationComponent? config))
  507. {
  508. Verb verb = new()
  509. {
  510. Text = Loc.GetString("configure-verb-get-data-text"),
  511. Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")),
  512. Category = VerbCategory.Debug,
  513. Act = () => _uiSystem.OpenUi(args.Target, ConfigurationUiKey.Key, actor.PlayerSession)
  514. };
  515. args.Verbs.Add(verb);
  516. }
  517. // Add verb to open Solution Editor
  518. if (_groupController.CanCommand(player, "addreagent") &&
  519. EntityManager.HasComponent<SolutionContainerManagerComponent>(args.Target))
  520. {
  521. Verb verb = new()
  522. {
  523. Text = Loc.GetString("edit-solutions-verb-get-data-text"),
  524. Category = VerbCategory.Debug,
  525. Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/spill.svg.192dpi.png")),
  526. Act = () => OpenEditSolutionsEui(player, args.Target),
  527. Impact = LogImpact.Medium // maybe high depending on WHAT reagents they add...
  528. };
  529. args.Verbs.Add(verb);
  530. }
  531. }
  532. #region SolutionsEui
  533. private void OnSolutionChanged(Entity<SolutionContainerManagerComponent> entity, ref SolutionContainerChangedEvent args)
  534. {
  535. foreach (var list in _openSolutionUis.Values)
  536. {
  537. foreach (var eui in list)
  538. {
  539. if (eui.Target == entity.Owner)
  540. eui.StateDirty();
  541. }
  542. }
  543. }
  544. public void OpenEditSolutionsEui(ICommonSession session, EntityUid uid)
  545. {
  546. if (session.AttachedEntity == null)
  547. return;
  548. var eui = new EditSolutionsEui(uid);
  549. _euiManager.OpenEui(eui, session);
  550. eui.StateDirty();
  551. if (!_openSolutionUis.ContainsKey(session)) {
  552. _openSolutionUis[session] = new List<EditSolutionsEui>();
  553. }
  554. _openSolutionUis[session].Add(eui);
  555. }
  556. public void OnEditSolutionsEuiClosed(ICommonSession session, EditSolutionsEui eui)
  557. {
  558. _openSolutionUis[session].Remove(eui);
  559. if (_openSolutionUis[session].Count == 0)
  560. _openSolutionUis.Remove(session);
  561. }
  562. private void Reset(RoundRestartCleanupEvent ev)
  563. {
  564. foreach (var euis in _openSolutionUis.Values)
  565. {
  566. foreach (var eui in euis.ToList())
  567. {
  568. eui.Close();
  569. }
  570. }
  571. _openSolutionUis.Clear();
  572. }
  573. #endregion
  574. }
  575. }