PlayTimeTrackingSystem.cs 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. using System.Linq;
  2. using Content.Server.Administration;
  3. using Content.Server.Administration.Managers;
  4. using Content.Server.Afk;
  5. using Content.Server.Afk.Events;
  6. using Content.Server.GameTicking;
  7. using Content.Server.GameTicking.Events;
  8. using Content.Server.Mind;
  9. using Content.Server.Preferences.Managers;
  10. using Content.Server.Station.Events;
  11. using Content.Shared.CCVar;
  12. using Content.Shared.GameTicking;
  13. using Content.Shared.Mobs;
  14. using Content.Shared.Mobs.Components;
  15. using Content.Shared.Players;
  16. using Content.Shared.Players.PlayTimeTracking;
  17. using Content.Shared.Preferences;
  18. using Content.Shared.Roles;
  19. using Robust.Server.Player;
  20. using Robust.Shared.Configuration;
  21. using Robust.Shared.Network;
  22. using Robust.Shared.Player;
  23. using Robust.Shared.Prototypes;
  24. using Robust.Shared.Utility;
  25. namespace Content.Server.Players.PlayTimeTracking;
  26. /// <summary>
  27. /// Connects <see cref="PlayTimeTrackingManager"/> to the simulation state. Reports trackers and such.
  28. /// </summary>
  29. public sealed class PlayTimeTrackingSystem : EntitySystem
  30. {
  31. [Dependency] private readonly IAdminManager _adminManager = default!;
  32. [Dependency] private readonly IAfkManager _afk = default!;
  33. [Dependency] private readonly IConfigurationManager _cfg = default!;
  34. [Dependency] private readonly MindSystem _minds = default!;
  35. [Dependency] private readonly IPlayerManager _playerManager = default!;
  36. [Dependency] private readonly IServerPreferencesManager _preferencesManager = default!;
  37. [Dependency] private readonly IPrototypeManager _prototypes = default!;
  38. [Dependency] private readonly SharedRoleSystem _roles = default!;
  39. [Dependency] private readonly PlayTimeTrackingManager _tracking = default!;
  40. public override void Initialize()
  41. {
  42. base.Initialize();
  43. _tracking.CalcTrackers += CalcTrackers;
  44. SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundEnd);
  45. SubscribeLocalEvent<PlayerAttachedEvent>(OnPlayerAttached);
  46. SubscribeLocalEvent<PlayerDetachedEvent>(OnPlayerDetached);
  47. SubscribeLocalEvent<RoleAddedEvent>(OnRoleEvent);
  48. SubscribeLocalEvent<RoleRemovedEvent>(OnRoleEvent);
  49. SubscribeLocalEvent<AFKEvent>(OnAFK);
  50. SubscribeLocalEvent<UnAFKEvent>(OnUnAFK);
  51. SubscribeLocalEvent<MobStateChangedEvent>(OnMobStateChanged);
  52. SubscribeLocalEvent<PlayerJoinedLobbyEvent>(OnPlayerJoinedLobby);
  53. SubscribeLocalEvent<StationJobsGetCandidatesEvent>(OnStationJobsGetCandidates);
  54. SubscribeLocalEvent<IsJobAllowedEvent>(OnIsJobAllowed);
  55. SubscribeLocalEvent<GetDisallowedJobsEvent>(OnGetDisallowedJobs);
  56. _adminManager.OnPermsChanged += AdminPermsChanged;
  57. }
  58. public override void Shutdown()
  59. {
  60. base.Shutdown();
  61. _tracking.CalcTrackers -= CalcTrackers;
  62. _adminManager.OnPermsChanged -= AdminPermsChanged;
  63. }
  64. private void CalcTrackers(ICommonSession player, HashSet<string> trackers)
  65. {
  66. if (_afk.IsAfk(player))
  67. return;
  68. if (_adminManager.IsAdmin(player))
  69. {
  70. trackers.Add(PlayTimeTrackingShared.TrackerAdmin);
  71. trackers.Add(PlayTimeTrackingShared.TrackerOverall);
  72. return;
  73. }
  74. if (!IsPlayerAlive(player))
  75. return;
  76. trackers.Add(PlayTimeTrackingShared.TrackerOverall);
  77. trackers.UnionWith(GetTimedRoles(player));
  78. }
  79. private bool IsPlayerAlive(ICommonSession session)
  80. {
  81. var attached = session.AttachedEntity;
  82. if (attached == null)
  83. return false;
  84. if (!TryComp<MobStateComponent>(attached, out var state))
  85. return false;
  86. return state.CurrentState is MobState.Alive or MobState.Critical;
  87. }
  88. public IEnumerable<string> GetTimedRoles(EntityUid mindId)
  89. {
  90. foreach (var role in _roles.MindGetAllRoleInfo(mindId))
  91. {
  92. if (string.IsNullOrWhiteSpace(role.PlayTimeTrackerId))
  93. continue;
  94. yield return _prototypes.Index<PlayTimeTrackerPrototype>(role.PlayTimeTrackerId).ID;
  95. }
  96. }
  97. private IEnumerable<string> GetTimedRoles(ICommonSession session)
  98. {
  99. var contentData = _playerManager.GetPlayerData(session.UserId).ContentData();
  100. if (contentData?.Mind == null)
  101. return Enumerable.Empty<string>();
  102. return GetTimedRoles(contentData.Mind.Value);
  103. }
  104. private void OnRoleEvent(RoleEvent ev)
  105. {
  106. if (_minds.TryGetSession(ev.Mind, out var session))
  107. _tracking.QueueRefreshTrackers(session);
  108. }
  109. private void OnRoundEnd(RoundRestartCleanupEvent ev)
  110. {
  111. _tracking.Save();
  112. }
  113. private void OnUnAFK(ref UnAFKEvent ev)
  114. {
  115. _tracking.QueueRefreshTrackers(ev.Session);
  116. }
  117. private void OnAFK(ref AFKEvent ev)
  118. {
  119. _tracking.QueueRefreshTrackers(ev.Session);
  120. }
  121. private void AdminPermsChanged(AdminPermsChangedEventArgs admin)
  122. {
  123. _tracking.QueueRefreshTrackers(admin.Player);
  124. }
  125. private void OnPlayerAttached(PlayerAttachedEvent ev)
  126. {
  127. _tracking.QueueRefreshTrackers(ev.Player);
  128. }
  129. private void OnPlayerDetached(PlayerDetachedEvent ev)
  130. {
  131. // This doesn't fire if the player doesn't leave their body. I guess it's fine?
  132. _tracking.QueueRefreshTrackers(ev.Player);
  133. }
  134. private void OnMobStateChanged(MobStateChangedEvent ev)
  135. {
  136. if (!TryComp(ev.Target, out ActorComponent? actor))
  137. return;
  138. _tracking.QueueRefreshTrackers(actor.PlayerSession);
  139. }
  140. private void OnPlayerJoinedLobby(PlayerJoinedLobbyEvent ev)
  141. {
  142. _tracking.QueueRefreshTrackers(ev.PlayerSession);
  143. // Send timers to client when they join lobby, so the UIs are up-to-date.
  144. _tracking.QueueSendTimers(ev.PlayerSession);
  145. }
  146. private void OnStationJobsGetCandidates(ref StationJobsGetCandidatesEvent ev)
  147. {
  148. RemoveDisallowedJobs(ev.Player, ev.Jobs);
  149. }
  150. private void OnIsJobAllowed(ref IsJobAllowedEvent ev)
  151. {
  152. if (!IsAllowed(ev.Player, ev.JobId))
  153. ev.Cancelled = true;
  154. }
  155. private void OnGetDisallowedJobs(ref GetDisallowedJobsEvent ev)
  156. {
  157. ev.Jobs.UnionWith(GetDisallowedJobs(ev.Player));
  158. }
  159. public bool IsAllowed(ICommonSession player, string role)
  160. {
  161. if (!_prototypes.TryIndex<JobPrototype>(role, out var job) ||
  162. !_cfg.GetCVar(CCVars.GameRoleTimers))
  163. return true;
  164. if (!_tracking.TryGetTrackerTimes(player, out var playTimes))
  165. {
  166. Log.Error($"Unable to check playtimes {Environment.StackTrace}");
  167. playTimes = new Dictionary<string, TimeSpan>();
  168. }
  169. return JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes, (HumanoidCharacterProfile?) _preferencesManager.GetPreferences(player.UserId).SelectedCharacter);
  170. }
  171. public HashSet<ProtoId<JobPrototype>> GetDisallowedJobs(ICommonSession player)
  172. {
  173. var roles = new HashSet<ProtoId<JobPrototype>>();
  174. if (!_cfg.GetCVar(CCVars.GameRoleTimers))
  175. return roles;
  176. if (!_tracking.TryGetTrackerTimes(player, out var playTimes))
  177. {
  178. Log.Error($"Unable to check playtimes {Environment.StackTrace}");
  179. playTimes = new Dictionary<string, TimeSpan>();
  180. }
  181. foreach (var job in _prototypes.EnumeratePrototypes<JobPrototype>())
  182. {
  183. if (JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes, (HumanoidCharacterProfile?) _preferencesManager.GetPreferences(player.UserId).SelectedCharacter))
  184. roles.Add(job.ID);
  185. }
  186. return roles;
  187. }
  188. public void RemoveDisallowedJobs(NetUserId userId, List<ProtoId<JobPrototype>> jobs)
  189. {
  190. if (!_cfg.GetCVar(CCVars.GameRoleTimers))
  191. return;
  192. var player = _playerManager.GetSessionById(userId);
  193. if (!_tracking.TryGetTrackerTimes(player, out var playTimes))
  194. {
  195. // Sorry mate but your playtimes haven't loaded.
  196. Log.Error($"Playtimes weren't ready yet for {player} on roundstart!");
  197. playTimes ??= new Dictionary<string, TimeSpan>();
  198. }
  199. for (var i = 0; i < jobs.Count; i++)
  200. {
  201. if (_prototypes.TryIndex(jobs[i], out var job)
  202. && JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes, (HumanoidCharacterProfile?) _preferencesManager.GetPreferences(userId).SelectedCharacter))
  203. {
  204. continue;
  205. }
  206. jobs.RemoveSwap(i);
  207. i--;
  208. }
  209. }
  210. public void PlayerRolesChanged(ICommonSession player)
  211. {
  212. _tracking.QueueRefreshTrackers(player);
  213. }
  214. }