AdminSystem.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. using System.Linq;
  2. using Content.Server.Administration.Managers;
  3. using Content.Server.Chat.Managers;
  4. using Content.Server.Forensics;
  5. using Content.Server.GameTicking;
  6. using Content.Server.Hands.Systems;
  7. using Content.Server.Mind;
  8. using Content.Server.Players.PlayTimeTracking;
  9. using Content.Server.Popups;
  10. using Content.Server.StationRecords.Systems;
  11. using Content.Shared.Administration;
  12. using Content.Shared.Administration.Events;
  13. using Content.Shared.CCVar;
  14. using Content.Shared.Forensics.Components;
  15. using Content.Shared.GameTicking;
  16. using Content.Shared.Hands.Components;
  17. using Content.Shared.IdentityManagement;
  18. using Content.Shared.Inventory;
  19. using Content.Shared.Mind;
  20. using Content.Shared.PDA;
  21. using Content.Shared.Players.PlayTimeTracking;
  22. using Content.Shared.Popups;
  23. using Content.Shared.Roles;
  24. using Content.Shared.Roles.Jobs;
  25. using Content.Shared.StationRecords;
  26. using Content.Shared.Throwing;
  27. using Robust.Server.GameObjects;
  28. using Robust.Server.Player;
  29. using Robust.Shared.Audio;
  30. using Robust.Shared.Audio.Systems;
  31. using Robust.Shared.Configuration;
  32. using Robust.Shared.Enums;
  33. using Robust.Shared.Network;
  34. using Robust.Shared.Player;
  35. using Robust.Shared.Prototypes;
  36. namespace Content.Server.Administration.Systems;
  37. public sealed class AdminSystem : EntitySystem
  38. {
  39. [Dependency] private readonly IAdminManager _adminManager = default!;
  40. [Dependency] private readonly IChatManager _chat = default!;
  41. [Dependency] private readonly IConfigurationManager _config = default!;
  42. [Dependency] private readonly IPlayerManager _playerManager = default!;
  43. [Dependency] private readonly HandsSystem _hands = default!;
  44. [Dependency] private readonly SharedJobSystem _jobs = default!;
  45. [Dependency] private readonly InventorySystem _inventory = default!;
  46. [Dependency] private readonly MindSystem _minds = default!;
  47. [Dependency] private readonly PopupSystem _popup = default!;
  48. [Dependency] private readonly PhysicsSystem _physics = default!;
  49. [Dependency] private readonly PlayTimeTrackingManager _playTime = default!;
  50. [Dependency] private readonly IPrototypeManager _proto = default!;
  51. [Dependency] private readonly SharedRoleSystem _role = default!;
  52. [Dependency] private readonly GameTicker _gameTicker = default!;
  53. [Dependency] private readonly SharedAudioSystem _audio = default!;
  54. [Dependency] private readonly StationRecordsSystem _stationRecords = default!;
  55. [Dependency] private readonly TransformSystem _transform = default!;
  56. private readonly Dictionary<NetUserId, PlayerInfo> _playerList = new();
  57. /// <summary>
  58. /// Set of players that have participated in this round.
  59. /// </summary>
  60. public IReadOnlySet<NetUserId> RoundActivePlayers => _roundActivePlayers;
  61. private readonly HashSet<NetUserId> _roundActivePlayers = new();
  62. public readonly PanicBunkerStatus PanicBunker = new();
  63. public override void Initialize()
  64. {
  65. base.Initialize();
  66. _playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
  67. _adminManager.OnPermsChanged += OnAdminPermsChanged;
  68. _playTime.SessionPlayTimeUpdated += OnSessionPlayTimeUpdated;
  69. // Panic Bunker Settings
  70. Subs.CVar(_config, CCVars.PanicBunkerEnabled, OnPanicBunkerChanged, true);
  71. Subs.CVar(_config, CCVars.PanicBunkerDisableWithAdmins, OnPanicBunkerDisableWithAdminsChanged, true);
  72. Subs.CVar(_config, CCVars.PanicBunkerEnableWithoutAdmins, OnPanicBunkerEnableWithoutAdminsChanged, true);
  73. Subs.CVar(_config, CCVars.PanicBunkerCountDeadminnedAdmins, OnPanicBunkerCountDeadminnedAdminsChanged, true);
  74. Subs.CVar(_config, CCVars.PanicBunkerShowReason, OnPanicBunkerShowReasonChanged, true);
  75. Subs.CVar(_config, CCVars.PanicBunkerMinAccountAge, OnPanicBunkerMinAccountAgeChanged, true);
  76. Subs.CVar(_config, CCVars.PanicBunkerMinOverallMinutes, OnPanicBunkerMinOverallMinutesChanged, true);
  77. SubscribeLocalEvent<PlayerAttachedEvent>(OnPlayerAttached);
  78. SubscribeLocalEvent<PlayerDetachedEvent>(OnPlayerDetached);
  79. SubscribeLocalEvent<RoleAddedEvent>(OnRoleEvent);
  80. SubscribeLocalEvent<RoleRemovedEvent>(OnRoleEvent);
  81. SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestartCleanup);
  82. SubscribeLocalEvent<ActorComponent, EntityRenamedEvent>(OnPlayerRenamed);
  83. SubscribeLocalEvent<ActorComponent, IdentityChangedEvent>(OnIdentityChanged);
  84. }
  85. private void OnRoundRestartCleanup(RoundRestartCleanupEvent ev)
  86. {
  87. _roundActivePlayers.Clear();
  88. foreach (var (id, data) in _playerList)
  89. {
  90. if (!data.ActiveThisRound)
  91. continue;
  92. if (!_playerManager.TryGetPlayerData(id, out var playerData))
  93. return;
  94. _playerManager.TryGetSessionById(id, out var session);
  95. _playerList[id] = GetPlayerInfo(playerData, session);
  96. }
  97. var updateEv = new FullPlayerListEvent() { PlayersInfo = _playerList.Values.ToList() };
  98. foreach (var admin in _adminManager.ActiveAdmins)
  99. {
  100. RaiseNetworkEvent(updateEv, admin.Channel);
  101. }
  102. }
  103. private void OnPlayerRenamed(Entity<ActorComponent> ent, ref EntityRenamedEvent args)
  104. {
  105. UpdatePlayerList(ent.Comp.PlayerSession);
  106. }
  107. public void UpdatePlayerList(ICommonSession player)
  108. {
  109. _playerList[player.UserId] = GetPlayerInfo(player.Data, player);
  110. var playerInfoChangedEvent = new PlayerInfoChangedEvent
  111. {
  112. PlayerInfo = _playerList[player.UserId]
  113. };
  114. foreach (var admin in _adminManager.ActiveAdmins)
  115. {
  116. RaiseNetworkEvent(playerInfoChangedEvent, admin.Channel);
  117. }
  118. }
  119. public PlayerInfo? GetCachedPlayerInfo(NetUserId? netUserId)
  120. {
  121. if (netUserId == null)
  122. return null;
  123. _playerList.TryGetValue(netUserId.Value, out var value);
  124. return value ?? null;
  125. }
  126. private void OnIdentityChanged(Entity<ActorComponent> ent, ref IdentityChangedEvent ev)
  127. {
  128. UpdatePlayerList(ent.Comp.PlayerSession);
  129. }
  130. private void OnRoleEvent(RoleEvent ev)
  131. {
  132. var session = _minds.GetSession(ev.Mind);
  133. if (!ev.RoleTypeUpdate || session == null)
  134. return;
  135. UpdatePlayerList(session);
  136. }
  137. private void OnAdminPermsChanged(AdminPermsChangedEventArgs obj)
  138. {
  139. UpdatePanicBunker();
  140. if (!obj.IsAdmin)
  141. {
  142. RaiseNetworkEvent(new FullPlayerListEvent(), obj.Player.Channel);
  143. return;
  144. }
  145. SendFullPlayerList(obj.Player);
  146. }
  147. private void OnPlayerDetached(PlayerDetachedEvent ev)
  148. {
  149. // If disconnected then the player won't have a connected entity to get character name from.
  150. // The disconnected state gets sent by OnPlayerStatusChanged.
  151. if (ev.Player.Status == SessionStatus.Disconnected)
  152. return;
  153. UpdatePlayerList(ev.Player);
  154. }
  155. private void OnPlayerAttached(PlayerAttachedEvent ev)
  156. {
  157. if (ev.Player.Status == SessionStatus.Disconnected)
  158. return;
  159. _roundActivePlayers.Add(ev.Player.UserId);
  160. UpdatePlayerList(ev.Player);
  161. }
  162. public override void Shutdown()
  163. {
  164. base.Shutdown();
  165. _playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
  166. _adminManager.OnPermsChanged -= OnAdminPermsChanged;
  167. _playTime.SessionPlayTimeUpdated -= OnSessionPlayTimeUpdated;
  168. }
  169. private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
  170. {
  171. UpdatePlayerList(e.Session);
  172. UpdatePanicBunker();
  173. }
  174. private void SendFullPlayerList(ICommonSession playerSession)
  175. {
  176. var ev = new FullPlayerListEvent();
  177. ev.PlayersInfo = _playerList.Values.ToList();
  178. RaiseNetworkEvent(ev, playerSession.Channel);
  179. }
  180. private PlayerInfo GetPlayerInfo(SessionData data, ICommonSession? session)
  181. {
  182. var name = data.UserName;
  183. var entityName = string.Empty;
  184. var identityName = string.Empty;
  185. // Visible (identity) name can be different from real name
  186. if (session?.AttachedEntity != null)
  187. {
  188. entityName = EntityManager.GetComponent<MetaDataComponent>(session.AttachedEntity.Value).EntityName;
  189. identityName = Identity.Name(session.AttachedEntity.Value, EntityManager);
  190. }
  191. var antag = false;
  192. // Starting role, antagonist status and role type
  193. RoleTypePrototype roleType = new();
  194. var startingRole = string.Empty;
  195. if (_minds.TryGetMind(session, out var mindId, out var mindComp))
  196. {
  197. if (_proto.TryIndex(mindComp.RoleType, out var role))
  198. roleType = role;
  199. else
  200. Log.Error($"{ToPrettyString(mindId)} has invalid Role Type '{mindComp.RoleType}'. Displaying '{Loc.GetString(roleType.Name)}' instead");
  201. antag = _role.MindIsAntagonist(mindId);
  202. startingRole = _jobs.MindTryGetJobName(mindId);
  203. }
  204. // Connection status and playtime
  205. var connected = session != null && session.Status is SessionStatus.Connected or SessionStatus.InGame;
  206. // Start with the last available playtime data
  207. var cachedInfo = GetCachedPlayerInfo(data.UserId);
  208. var overallPlaytime = cachedInfo?.OverallPlaytime;
  209. // Overwrite with current playtime data, unless it's null (such as if the player just disconnected)
  210. if (session != null &&
  211. _playTime.TryGetTrackerTimes(session, out var playTimes) &&
  212. playTimes.TryGetValue(PlayTimeTrackingShared.TrackerOverall, out var playTime))
  213. {
  214. overallPlaytime = playTime;
  215. }
  216. return new PlayerInfo(name, entityName, identityName, startingRole, antag, roleType, GetNetEntity(session?.AttachedEntity), data.UserId,
  217. connected, _roundActivePlayers.Contains(data.UserId), overallPlaytime);
  218. }
  219. private void OnPanicBunkerChanged(bool enabled)
  220. {
  221. PanicBunker.Enabled = enabled;
  222. _chat.SendAdminAlert(Loc.GetString(enabled
  223. ? "admin-ui-panic-bunker-enabled-admin-alert"
  224. : "admin-ui-panic-bunker-disabled-admin-alert"
  225. ));
  226. SendPanicBunkerStatusAll();
  227. }
  228. private void OnPanicBunkerDisableWithAdminsChanged(bool enabled)
  229. {
  230. PanicBunker.DisableWithAdmins = enabled;
  231. UpdatePanicBunker();
  232. }
  233. private void OnPanicBunkerEnableWithoutAdminsChanged(bool enabled)
  234. {
  235. PanicBunker.EnableWithoutAdmins = enabled;
  236. UpdatePanicBunker();
  237. }
  238. private void OnPanicBunkerCountDeadminnedAdminsChanged(bool enabled)
  239. {
  240. PanicBunker.CountDeadminnedAdmins = enabled;
  241. UpdatePanicBunker();
  242. }
  243. private void OnPanicBunkerShowReasonChanged(bool enabled)
  244. {
  245. PanicBunker.ShowReason = enabled;
  246. SendPanicBunkerStatusAll();
  247. }
  248. private void OnPanicBunkerMinAccountAgeChanged(int minutes)
  249. {
  250. PanicBunker.MinAccountAgeMinutes = minutes;
  251. SendPanicBunkerStatusAll();
  252. }
  253. private void OnPanicBunkerMinOverallMinutesChanged(int minutes)
  254. {
  255. PanicBunker.MinOverallMinutes = minutes;
  256. SendPanicBunkerStatusAll();
  257. }
  258. private void UpdatePanicBunker()
  259. {
  260. var hasAdmins = false;
  261. foreach (var admin in _adminManager.AllAdmins)
  262. {
  263. if (_adminManager.HasAdminFlag(admin, AdminFlags.Admin, includeDeAdmin: PanicBunker.CountDeadminnedAdmins))
  264. {
  265. hasAdmins = true;
  266. break;
  267. }
  268. }
  269. // TODO Fix order dependent Cvars
  270. // Please for the sake of my sanity don't make cvars & order dependent.
  271. // Just make a bool field on the system instead of having some cvars automatically modify other cvars.
  272. //
  273. // I.e., this:
  274. // /sudo cvar game.panic_bunker.enabled true
  275. // /sudo cvar game.panic_bunker.disable_with_admins true
  276. // and this:
  277. // /sudo cvar game.panic_bunker.disable_with_admins true
  278. // /sudo cvar game.panic_bunker.enabled true
  279. //
  280. // should have the same effect, but currently setting the disable_with_admins can modify enabled.
  281. if (hasAdmins && PanicBunker.DisableWithAdmins)
  282. {
  283. _config.SetCVar(CCVars.PanicBunkerEnabled, false);
  284. }
  285. else if (!hasAdmins && PanicBunker.EnableWithoutAdmins)
  286. {
  287. _config.SetCVar(CCVars.PanicBunkerEnabled, true);
  288. }
  289. SendPanicBunkerStatusAll();
  290. }
  291. private void SendPanicBunkerStatusAll()
  292. {
  293. var ev = new PanicBunkerChangedEvent(PanicBunker);
  294. foreach (var admin in _adminManager.AllAdmins)
  295. {
  296. RaiseNetworkEvent(ev, admin);
  297. }
  298. }
  299. /// <summary>
  300. /// Erases a player from the round.
  301. /// This removes them and any trace of them from the round, deleting their
  302. /// chat messages and showing a popup to other players.
  303. /// Their items are dropped on the ground.
  304. /// </summary>
  305. public void Erase(NetUserId uid)
  306. {
  307. _chat.DeleteMessagesBy(uid);
  308. if (!_minds.TryGetMind(uid, out var mindId, out var mind) || mind.OwnedEntity == null || TerminatingOrDeleted(mind.OwnedEntity.Value))
  309. return;
  310. var entity = mind.OwnedEntity.Value;
  311. if (TryComp(entity, out TransformComponent? transform))
  312. {
  313. var coordinates = _transform.GetMoverCoordinates(entity, transform);
  314. var name = Identity.Entity(entity, EntityManager);
  315. _popup.PopupCoordinates(Loc.GetString("admin-erase-popup", ("user", name)), coordinates, PopupType.LargeCaution);
  316. var filter = Filter.Pvs(coordinates, 1, EntityManager, _playerManager);
  317. var audioParams = new AudioParams().WithVolume(3);
  318. _audio.PlayStatic("/Audio/Effects/pop_high.ogg", filter, coordinates, true, audioParams);
  319. }
  320. foreach (var item in _inventory.GetHandOrInventoryEntities(entity))
  321. {
  322. if (TryComp(item, out PdaComponent? pda) &&
  323. TryComp(pda.ContainedId, out StationRecordKeyStorageComponent? keyStorage) &&
  324. keyStorage.Key is { } key &&
  325. _stationRecords.TryGetRecord(key, out GeneralStationRecord? record))
  326. {
  327. if (TryComp(entity, out DnaComponent? dna) &&
  328. dna.DNA != record.DNA)
  329. {
  330. continue;
  331. }
  332. if (TryComp(entity, out FingerprintComponent? fingerPrint) &&
  333. fingerPrint.Fingerprint != record.Fingerprint)
  334. {
  335. continue;
  336. }
  337. _stationRecords.RemoveRecord(key);
  338. Del(item);
  339. }
  340. }
  341. if (_inventory.TryGetContainerSlotEnumerator(entity, out var enumerator))
  342. {
  343. while (enumerator.NextItem(out var item, out var slot))
  344. {
  345. if (_inventory.TryUnequip(entity, entity, slot.Name, true, true))
  346. _physics.ApplyAngularImpulse(item, ThrowingSystem.ThrowAngularImpulse);
  347. }
  348. }
  349. if (TryComp(entity, out HandsComponent? hands))
  350. {
  351. foreach (var hand in _hands.EnumerateHands(entity, hands))
  352. {
  353. _hands.TryDrop(entity, hand, checkActionBlocker: false, doDropInteraction: false, handsComp: hands);
  354. }
  355. }
  356. _minds.WipeMind(mindId, mind);
  357. QueueDel(entity);
  358. if (_playerManager.TryGetSessionById(uid, out var session))
  359. _gameTicker.SpawnObserver(session);
  360. }
  361. private void OnSessionPlayTimeUpdated(ICommonSession session)
  362. {
  363. UpdatePlayerList(session);
  364. }
  365. }