diff --git a/Content.Client/GameTicking/ClientGameTicker.cs b/Content.Client/GameTicking/ClientGameTicker.cs index cab0608a93..3b58d1c17b 100644 --- a/Content.Client/GameTicking/ClientGameTicker.cs +++ b/Content.Client/GameTicking/ClientGameTicker.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Content.Client.Interfaces; using Content.Client.State; using Content.Client.UserInterface; @@ -8,6 +9,7 @@ using Robust.Client.Interfaces.Graphics; using Robust.Client.Interfaces.State; using Robust.Shared.Interfaces.Network; using Robust.Shared.IoC; +using Robust.Shared.Network; using Robust.Shared.Utility; using Robust.Shared.ViewVariables; @@ -27,9 +29,11 @@ namespace Content.Client.GameTicking [ViewVariables] public string ServerInfoBlob { get; private set; } [ViewVariables] public DateTime StartTime { get; private set; } [ViewVariables] public bool Paused { get; private set; } + [ViewVariables] public Dictionary Ready { get; private set; } public event Action InfoBlobUpdated; public event Action LobbyStatusUpdated; + public event Action LobbyReadyUpdated; public void Initialize() { @@ -40,12 +44,14 @@ namespace Content.Client.GameTicking _netManager.RegisterNetMessage(nameof(MsgTickerLobbyStatus), LobbyStatus); _netManager.RegisterNetMessage(nameof(MsgTickerLobbyInfo), LobbyInfo); _netManager.RegisterNetMessage(nameof(MsgTickerLobbyCountdown), LobbyCountdown); + _netManager.RegisterNetMessage(nameof(MsgTickerLobbyReady), LobbyReady); _netManager.RegisterNetMessage(nameof(MsgRoundEndMessage), RoundEnd); _netManager.RegisterNetMessage(nameof(MsgRequestWindowAttention), msg => { IoCManager.Resolve().RequestWindowAttention(); }); + Ready = new Dictionary(); _initialized = true; } @@ -62,6 +68,8 @@ namespace Content.Client.GameTicking IsGameStarted = message.IsRoundStarted; AreWeReady = message.YouAreReady; Paused = message.Paused; + if (IsGameStarted) + Ready.Clear(); LobbyStatusUpdated?.Invoke(); } @@ -84,9 +92,18 @@ namespace Content.Client.GameTicking Paused = message.Paused; } + private void LobbyReady(MsgTickerLobbyReady message) + { + // Merge the Dictionaries + foreach (var p in message.PlayerReady) + { + Ready[p.Key] = p.Value; + } + LobbyReadyUpdated?.Invoke(); + } + private void RoundEnd(MsgRoundEndMessage message) { - //This is not ideal at all, but I don't see an immediately better fit anywhere else. var roundEnd = new RoundEndSummaryWindow(message.GamemodeTitle, message.RoundDuration, message.AllPlayersEndInfo); diff --git a/Content.Client/Interfaces/IClientGameTicker.cs b/Content.Client/Interfaces/IClientGameTicker.cs index 8d47a0e1dc..0bedce3995 100644 --- a/Content.Client/Interfaces/IClientGameTicker.cs +++ b/Content.Client/Interfaces/IClientGameTicker.cs @@ -1,4 +1,6 @@ +using Robust.Shared.Network; using System; +using System.Collections.Generic; namespace Content.Client.Interfaces { @@ -9,9 +11,11 @@ namespace Content.Client.Interfaces bool AreWeReady { get; } DateTime StartTime { get; } bool Paused { get; } + Dictionary Ready { get; } void Initialize(); event Action InfoBlobUpdated; event Action LobbyStatusUpdated; + event Action LobbyReadyUpdated; } } diff --git a/Content.Client/State/LobbyState.cs b/Content.Client/State/LobbyState.cs index 23f1d28d83..20b4256393 100644 --- a/Content.Client/State/LobbyState.cs +++ b/Content.Client/State/LobbyState.cs @@ -99,7 +99,8 @@ namespace Content.Client.State _playerManager.PlayerListUpdated += PlayerManagerOnPlayerListUpdated; _clientGameTicker.InfoBlobUpdated += UpdateLobbyUi; - _clientGameTicker.LobbyStatusUpdated += UpdateLobbyUi; + _clientGameTicker.LobbyStatusUpdated += LobbyStatusUpdated; + _clientGameTicker.LobbyReadyUpdated += LobbyReadyUpdated; } public override void Shutdown() @@ -149,7 +150,25 @@ namespace Content.Client.State _lobby.StartTime.Text = Loc.GetString("Round Starts In: {0}", text); } - private void PlayerManagerOnPlayerListUpdated(object sender, EventArgs e) => UpdatePlayerList(); + private void PlayerManagerOnPlayerListUpdated(object sender, EventArgs e) + { + // Remove disconnected sessions from the Ready Dict + foreach (var p in _clientGameTicker.Ready) + { + if (!_playerManager.SessionsDict.TryGetValue(p.Key, out _)) + { + _clientGameTicker.Ready.Remove(p.Key); + } + } + UpdatePlayerList(); + } + private void LobbyReadyUpdated() => UpdatePlayerList(); + + private void LobbyStatusUpdated() + { + UpdatePlayerList(); + UpdateLobbyUi(); + } private void UpdateLobbyUi() { @@ -178,10 +197,24 @@ namespace Content.Client.State private void UpdatePlayerList() { _lobby.OnlinePlayerItemList.Clear(); + _lobby.PlayerReadyList.Clear(); foreach (var session in _playerManager.Sessions.OrderBy(s => s.Name)) { _lobby.OnlinePlayerItemList.AddItem(session.Name); + + var readyState = ""; + // Don't show ready state if we're ingame + if (!_clientGameTicker.IsGameStarted) + { + var ready = false; + if (session.SessionId == _playerManager.LocalPlayer.SessionId) + ready = _clientGameTicker.AreWeReady; + else + _clientGameTicker.Ready.TryGetValue(session.SessionId, out ready); + readyState = ready ? Loc.GetString("Ready") : Loc.GetString("Not Ready"); + } + _lobby.PlayerReadyList.AddItem(readyState, null, false); } } @@ -193,6 +226,7 @@ namespace Content.Client.State } _console.ProcessCommand($"toggleready {newReady}"); + UpdatePlayerList(); } } } diff --git a/Content.Client/UserInterface/LobbyGui.cs b/Content.Client/UserInterface/LobbyGui.cs index 2b077446f1..2f8caf3574 100644 --- a/Content.Client/UserInterface/LobbyGui.cs +++ b/Content.Client/UserInterface/LobbyGui.cs @@ -1,4 +1,4 @@ -using Content.Client.Chat; +using Content.Client.Chat; using Content.Client.Interfaces; using Content.Client.UserInterface.Stylesheets; using Content.Client.Utility; @@ -22,6 +22,7 @@ namespace Content.Client.UserInterface public Button LeaveButton { get; } public ChatBox Chat { get; } public ItemList OnlinePlayerItemList { get; } + public ItemList PlayerReadyList { get; } public ServerInfo ServerInfo { get; } public LobbyCharacterPreviewPanel CharacterPreview { get; } @@ -219,7 +220,25 @@ namespace Content.Client.UserInterface MarginBottomOverride = 3, Children = { - (OnlinePlayerItemList = new ItemList()) + new HBoxContainer + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + CustomMinimumSize = (50,50), + Children = + { + (OnlinePlayerItemList = new ItemList + { + SizeFlagsVertical = SizeFlags.FillExpand, + SizeFlagsHorizontal = SizeFlags.FillExpand, + }), + (PlayerReadyList = new ItemList + { + SizeFlagsVertical = SizeFlags.FillExpand, + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsStretchRatio = 0.2f + }), + } + } } }, new NanoHeading diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index 839844f61f..476a7c55f8 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -49,6 +49,7 @@ using Robust.Shared.Localization; using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Maths; +using Robust.Shared.Network; using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Timing; @@ -138,6 +139,7 @@ namespace Content.Server.GameTicking _netManager.RegisterNetMessage(nameof(MsgTickerLobbyStatus)); _netManager.RegisterNetMessage(nameof(MsgTickerLobbyInfo)); _netManager.RegisterNetMessage(nameof(MsgTickerLobbyCountdown)); + _netManager.RegisterNetMessage(nameof(MsgTickerLobbyReady)); _netManager.RegisterNetMessage(nameof(MsgRoundEndMessage)); _netManager.RegisterNetMessage(nameof(MsgRequestWindowAttention)); @@ -381,6 +383,7 @@ namespace Content.Server.GameTicking _playersInLobby[player] = ready; _netManager.ServerSendMessage(_getStatusMsg(player), player.ConnectedClient); + _netManager.ServerSendToAll(GetReadySingle(player, ready)); } public T AddGameRule() where T : GameRule, new() @@ -871,6 +874,7 @@ namespace Content.Server.GameTicking _netManager.ServerSendMessage(_netManager.CreateNetMessage(), session.ConnectedClient); _netManager.ServerSendMessage(_getStatusMsg(session), session.ConnectedClient); _netManager.ServerSendMessage(GetInfoMsg(), session.ConnectedClient); + _netManager.ServerSendMessage(GetReadyStatus(), session.ConnectedClient); } private void _playerJoinGame(IPlayerSession session) @@ -882,6 +886,26 @@ namespace Content.Server.GameTicking _netManager.ServerSendMessage(_netManager.CreateNetMessage(), session.ConnectedClient); } + private MsgTickerLobbyReady GetReadyStatus() + { + var msg = _netManager.CreateNetMessage(); + msg.PlayerReady = new Dictionary(); + foreach (var player in _playersInLobby.Keys) + { + _playersInLobby.TryGetValue(player, out var ready); + msg.PlayerReady.Add(player.SessionId, ready); + } + return msg; + } + + private MsgTickerLobbyReady GetReadySingle(IPlayerSession player, bool ready) + { + var msg = _netManager.CreateNetMessage(); + msg.PlayerReady = new Dictionary(); + msg.PlayerReady.Add(player.SessionId, ready); + return msg; + } + private MsgTickerLobbyStatus _getStatusMsg(IPlayerSession session) { _playersInLobby.TryGetValue(session, out var ready); diff --git a/Content.Shared/SharedGameTicker.cs b/Content.Shared/SharedGameTicker.cs index 669a6acb93..d7100f93e4 100644 --- a/Content.Shared/SharedGameTicker.cs +++ b/Content.Shared/SharedGameTicker.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; +using System.IO; using Lidgren.Network; using Robust.Shared.Interfaces.Network; +using Robust.Shared.Interfaces.Serialization; +using Robust.Shared.IoC; using Robust.Shared.Network; namespace Content.Shared @@ -152,6 +155,57 @@ namespace Content.Shared } } + protected class MsgTickerLobbyReady : NetMessage + { + #region REQUIRED + + public const MsgGroups GROUP = MsgGroups.Command; + public const string NAME = nameof(MsgTickerLobbyReady); + public MsgTickerLobbyReady(INetChannel channel) : base(NAME, GROUP) { } + + #endregion + + /// + /// The Players Ready (SessionID:ready) + /// + public Dictionary PlayerReady { get; set; } + + public override void ReadFromBuffer(NetIncomingMessage buffer) + { + PlayerReady = new Dictionary(); + var length = buffer.ReadInt32(); + for (int i = 0; i < length; i++) + { + var serializer = IoCManager.Resolve(); + var byteLength = buffer.ReadVariableInt32(); + NetSessionId sessionID; + using (var stream = buffer.ReadAsStream(byteLength)) + { + serializer.DeserializeDirect(stream, out sessionID); + } + var ready = buffer.ReadBoolean(); + PlayerReady.Add(sessionID, ready); + } + } + + public override void WriteToBuffer(NetOutgoingMessage buffer) + { + var serializer = IoCManager.Resolve(); + buffer.Write(PlayerReady.Count); + foreach (var p in PlayerReady) + { + using (var stream = new MemoryStream()) + { + serializer.SerializeDirect(stream, p.Key); + buffer.WriteVariableInt32((int) stream.Length); + stream.TryGetBuffer(out var segment); + buffer.Write(segment); + } + buffer.Write(p.Value); + } + } + } + public struct RoundEndPlayerInfo {