GameTicker.Player.cs 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. using Content.Shared.Administration;
  2. using Content.Shared.CCVar;
  3. using Content.Shared.GameTicking;
  4. using Content.Shared.GameWindow;
  5. using Content.Shared.Players;
  6. using Content.Shared.Preferences;
  7. using JetBrains.Annotations;
  8. using Robust.Server.Player;
  9. using Robust.Shared.Audio;
  10. using Robust.Shared.Enums;
  11. using Robust.Shared.Player;
  12. using Robust.Shared.Timing;
  13. using Robust.Shared.Utility;
  14. namespace Content.Server.GameTicking
  15. {
  16. [UsedImplicitly]
  17. public sealed partial class GameTicker
  18. {
  19. [Dependency] private readonly IPlayerManager _playerManager = default!;
  20. private void InitializePlayer()
  21. {
  22. _playerManager.PlayerStatusChanged += PlayerStatusChanged;
  23. }
  24. private async void PlayerStatusChanged(object? sender, SessionStatusEventArgs args)
  25. {
  26. var session = args.Session;
  27. if (_mind.TryGetMind(session.UserId, out var mindId, out var mind))
  28. {
  29. if (args.NewStatus != SessionStatus.Disconnected)
  30. {
  31. mind.Session = session;
  32. _pvsOverride.AddSessionOverride(GetNetEntity(mindId.Value), session);
  33. }
  34. DebugTools.Assert(mind.Session == session);
  35. }
  36. DebugTools.Assert(session.GetMind() == mindId);
  37. switch (args.NewStatus)
  38. {
  39. case SessionStatus.Connected:
  40. {
  41. AddPlayerToDb(args.Session.UserId.UserId);
  42. // Always make sure the client has player data.
  43. if (session.Data.ContentDataUncast == null)
  44. {
  45. var data = new ContentPlayerData(session.UserId, args.Session.Name);
  46. data.Mind = mindId;
  47. session.Data.ContentDataUncast = data;
  48. }
  49. // Make the player actually join the game.
  50. // timer time must be > tick length
  51. Timer.Spawn(0, () => _playerManager.JoinGame(args.Session));
  52. var record = await _db.GetPlayerRecordByUserId(args.Session.UserId);
  53. var firstConnection = record != null &&
  54. Math.Abs((record.FirstSeenTime - record.LastSeenTime).TotalMinutes) < 1;
  55. _chatManager.SendAdminAnnouncement(firstConnection
  56. ? Loc.GetString("player-first-join-message", ("name", args.Session.Name))
  57. : Loc.GetString("player-join-message", ("name", args.Session.Name)));
  58. RaiseNetworkEvent(GetConnectionStatusMsg(), session.Channel);
  59. if (firstConnection && _cfg.GetCVar(CCVars.AdminNewPlayerJoinSound))
  60. _audio.PlayGlobal(new SoundPathSpecifier("/Audio/Effects/newplayerping.ogg"),
  61. Filter.Empty().AddPlayers(_adminManager.ActiveAdmins), false,
  62. audioParams: new AudioParams { Volume = -5f });
  63. if (LobbyEnabled && _roundStartCountdownHasNotStartedYetDueToNoPlayers)
  64. {
  65. _roundStartCountdownHasNotStartedYetDueToNoPlayers = false;
  66. _roundStartTime = _gameTiming.CurTime + LobbyDuration;
  67. }
  68. break;
  69. }
  70. case SessionStatus.InGame:
  71. {
  72. _userDb.ClientConnected(session);
  73. if (mind == null)
  74. {
  75. if (LobbyEnabled)
  76. PlayerJoinLobby(session);
  77. else
  78. SpawnWaitDb();
  79. break;
  80. }
  81. if (mind.CurrentEntity == null || Deleted(mind.CurrentEntity))
  82. {
  83. DebugTools.Assert(mind.CurrentEntity == null, "a mind's current entity was deleted without updating the mind");
  84. // This player is joining the game with an existing mind, but the mind has no entity.
  85. // Their entity was probably deleted sometime while they were disconnected, or they were an observer.
  86. // Instead of allowing them to spawn in, we will dump and their existing mind in an observer ghost.
  87. SpawnObserverWaitDb();
  88. }
  89. else
  90. {
  91. if (_playerManager.SetAttachedEntity(session, mind.CurrentEntity))
  92. {
  93. PlayerJoinGame(session);
  94. }
  95. else
  96. {
  97. Log.Error(
  98. $"Failed to attach player {session} with mind {ToPrettyString(mindId)} to its current entity {ToPrettyString(mind.CurrentEntity)}");
  99. SpawnObserverWaitDb();
  100. }
  101. }
  102. break;
  103. }
  104. case SessionStatus.Disconnected:
  105. {
  106. _chatManager.SendAdminAnnouncement(Loc.GetString("player-leave-message", ("name", args.Session.Name)));
  107. if (mind != null)
  108. {
  109. _pvsOverride.ClearOverride(GetNetEntity(mindId!.Value));
  110. mind.Session = null;
  111. }
  112. _userDb.ClientDisconnected(session);
  113. break;
  114. }
  115. }
  116. //When the status of a player changes, update the server info text
  117. UpdateInfoText();
  118. async void SpawnWaitDb()
  119. {
  120. try
  121. {
  122. await _userDb.WaitLoadComplete(session);
  123. }
  124. catch (OperationCanceledException)
  125. {
  126. // Bail, user must've disconnected or something.
  127. Log.Debug($"Database load cancelled while waiting to spawn {session}");
  128. return;
  129. }
  130. SpawnPlayer(session, EntityUid.Invalid);
  131. }
  132. async void SpawnObserverWaitDb()
  133. {
  134. try
  135. {
  136. await _userDb.WaitLoadComplete(session);
  137. }
  138. catch (OperationCanceledException)
  139. {
  140. // Bail, user must've disconnected or something.
  141. Log.Debug($"Database load cancelled while waiting to spawn {session}");
  142. return;
  143. }
  144. JoinAsObserver(session);
  145. }
  146. async void AddPlayerToDb(Guid id)
  147. {
  148. if (RoundId != 0 && _runLevel != GameRunLevel.PreRoundLobby)
  149. {
  150. await _db.AddRoundPlayers(RoundId, id);
  151. }
  152. }
  153. }
  154. public HumanoidCharacterProfile GetPlayerProfile(ICommonSession p)
  155. {
  156. return (HumanoidCharacterProfile)_prefsManager.GetPreferences(p.UserId).SelectedCharacter;
  157. }
  158. public void PlayerJoinGame(ICommonSession session, bool silent = false)
  159. {
  160. if (!silent)
  161. _chatManager.DispatchServerMessage(session, Loc.GetString("game-ticker-player-join-game-message"));
  162. _playerGameStatuses[session.UserId] = PlayerGameStatus.JoinedGame;
  163. _db.AddRoundPlayers(RoundId, session.UserId);
  164. if (_adminManager.HasAdminFlag(session, AdminFlags.Admin))
  165. {
  166. if (_allPreviousGameRules.Count > 0)
  167. {
  168. var rulesMessage = GetGameRulesListMessage(true);
  169. _chatManager.SendAdminAnnouncementMessage(session, Loc.GetString("starting-rule-selected-preset", ("preset", rulesMessage)));
  170. }
  171. }
  172. RaiseNetworkEvent(new TickerJoinGameEvent(), session.Channel);
  173. }
  174. public void PlayerJoinLobby(ICommonSession session)
  175. {
  176. _playerGameStatuses[session.UserId] = LobbyEnabled ? PlayerGameStatus.NotReadyToPlay : PlayerGameStatus.ReadyToPlay;
  177. _db.AddRoundPlayers(RoundId, session.UserId);
  178. var client = session.Channel;
  179. RaiseNetworkEvent(new TickerJoinLobbyEvent(), client);
  180. RaiseNetworkEvent(GetStatusMsg(session), client);
  181. RaiseNetworkEvent(GetInfoMsg(), client);
  182. RaiseLocalEvent(new PlayerJoinedLobbyEvent(session));
  183. }
  184. private void ReqWindowAttentionAll()
  185. {
  186. RaiseNetworkEvent(new RequestWindowAttentionEvent());
  187. }
  188. }
  189. public sealed class PlayerJoinedLobbyEvent : EntityEventArgs
  190. {
  191. public readonly ICommonSession PlayerSession;
  192. public PlayerJoinedLobbyEvent(ICommonSession playerSession)
  193. {
  194. PlayerSession = playerSession;
  195. }
  196. }
  197. }