diff --git a/Content.Client/GameTicking/ClientGameTicker.cs b/Content.Client/GameTicking/ClientGameTicker.cs index c7fc4e4c25..17e46432f2 100644 --- a/Content.Client/GameTicking/ClientGameTicker.cs +++ b/Content.Client/GameTicking/ClientGameTicker.cs @@ -22,6 +22,7 @@ namespace Content.Client.GameTicking [Dependency] private readonly IStateManager _stateManager = default!; [ViewVariables] private bool _initialized; + private readonly List _jobsAvailable = new List(); [ViewVariables] public bool AreWeReady { get; private set; } [ViewVariables] public bool IsGameStarted { get; private set; } @@ -30,11 +31,13 @@ namespace Content.Client.GameTicking [ViewVariables] public DateTime StartTime { get; private set; } [ViewVariables] public bool Paused { get; private set; } [ViewVariables] public Dictionary Status { get; private set; } + [ViewVariables] public IReadOnlyList JobsAvailable => _jobsAvailable; public event Action InfoBlobUpdated; public event Action LobbyStatusUpdated; public event Action LobbyReadyUpdated; public event Action LobbyLateJoinStatusUpdated; + public event Action> LobbyJobsAvailableUpdated; public void Initialize() { @@ -52,6 +55,7 @@ namespace Content.Client.GameTicking IoCManager.Resolve().RequestWindowAttention(); }); _netManager.RegisterNetMessage(nameof(MsgTickerLateJoinStatus), LateJoinStatus); + _netManager.RegisterNetMessage(nameof(MsgTickerJobsAvailable), UpdateJobsAvailable); Status = new Dictionary(); _initialized = true; @@ -63,6 +67,12 @@ namespace Content.Client.GameTicking LobbyLateJoinStatusUpdated?.Invoke(); } + private void UpdateJobsAvailable(MsgTickerJobsAvailable message) + { + _jobsAvailable.Clear(); + _jobsAvailable.AddRange(message.JobsAvailable); + LobbyJobsAvailableUpdated?.Invoke(JobsAvailable); + } private void JoinLobby(MsgTickerJoinLobby message) { diff --git a/Content.Client/Interfaces/IClientGameTicker.cs b/Content.Client/Interfaces/IClientGameTicker.cs index 5612c03613..ecb54004b0 100644 --- a/Content.Client/Interfaces/IClientGameTicker.cs +++ b/Content.Client/Interfaces/IClientGameTicker.cs @@ -14,11 +14,13 @@ namespace Content.Client.Interfaces DateTime StartTime { get; } bool Paused { get; } Dictionary Status { get; } + IReadOnlyList JobsAvailable { get; } void Initialize(); event Action InfoBlobUpdated; event Action LobbyStatusUpdated; event Action LobbyReadyUpdated; event Action LobbyLateJoinStatusUpdated; + event Action> LobbyJobsAvailableUpdated; } } diff --git a/Content.Client/UserInterface/LateJoinGui.cs b/Content.Client/UserInterface/LateJoinGui.cs index dfeb49eb81..0924a4eb94 100644 --- a/Content.Client/UserInterface/LateJoinGui.cs +++ b/Content.Client/UserInterface/LateJoinGui.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Linq; +using Content.Client.Interfaces; using Content.Shared.Roles; using Robust.Client.Console; using Robust.Client.UserInterface.Controls; @@ -18,11 +20,14 @@ namespace Content.Client.UserInterface { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IClientConsole _console = default!; + [Dependency] private readonly IClientGameTicker _gameTicker = default!; protected override Vector2? CustomSize => (360, 560); public event Action SelectedId; + private Dictionary JobButtons = new Dictionary(); + public LateJoinGui() { IoCManager.InjectDependencies(this); @@ -84,6 +89,13 @@ namespace Content.Client.UserInterface { SelectedId?.Invoke(jobButton.JobId); }; + + if (!_gameTicker.JobsAvailable.Contains(job.ID)) + { + jobButton.Disabled = true; + } + + JobButtons[job.ID] = jobButton; } SelectedId += jobId => @@ -93,7 +105,7 @@ namespace Content.Client.UserInterface Close(); }; - + _gameTicker.LobbyJobsAvailableUpdated += JobsAvailableUpdated; } public string ReturnId() @@ -101,7 +113,26 @@ namespace Content.Client.UserInterface return SelectedId.ToString(); } + private void JobsAvailableUpdated(IReadOnlyList jobs) + { + foreach (var (id, button) in JobButtons) + { + button.Disabled = !jobs.Contains(id); + } + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _gameTicker.LobbyJobsAvailableUpdated -= JobsAvailableUpdated; + JobButtons.Clear(); + } + } } + class JobButton : ContainerButton { public string JobId { get; set; } diff --git a/Content.Server/GameTicking/GameTicker.JobController.cs b/Content.Server/GameTicking/GameTicker.JobController.cs index 0d57f10768..cd097b7ebe 100644 --- a/Content.Server/GameTicking/GameTicker.JobController.cs +++ b/Content.Server/GameTicking/GameTicker.JobController.cs @@ -196,5 +196,11 @@ namespace Content.Server.GameTicking { _spawnedPositions[jobId] = _spawnedPositions.GetValueOrDefault(jobId, 0) + 1; } + + private void UpdateJobsAvailable() + { + var lobbyPlayers = _playersInLobby.Keys.Select(p => p.ConnectedClient).ToList(); + _netManager.ServerSendToMany(GetJobsAvailable(), lobbyPlayers); + } } } diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index da07e2eed2..e0d5ca11eb 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -143,6 +143,7 @@ namespace Content.Server.GameTicking _netManager.RegisterNetMessage(nameof(MsgRoundEndMessage)); _netManager.RegisterNetMessage(nameof(MsgRequestWindowAttention)); _netManager.RegisterNetMessage(nameof(MsgTickerLateJoinStatus)); + _netManager.RegisterNetMessage(nameof(MsgTickerJobsAvailable)); SetStartPreset(_configurationManager.GetCVar(CCVars.GameLobbyDefaultPreset)); @@ -307,6 +308,7 @@ namespace Content.Server.GameTicking _sendStatusToAll(); ReqWindowAttentionAll(); UpdateLateJoinStatus(); + UpdateJobsAvailable(); } private void UpdateLateJoinStatus() @@ -420,6 +422,7 @@ namespace Content.Server.GameTicking { DisallowLateJoin = disallowLateJoin; UpdateLateJoinStatus(); + UpdateJobsAvailable(); } public T AddGameRule() where T : GameRule, new() @@ -817,6 +820,7 @@ namespace Content.Server.GameTicking var character = GetPlayerProfile(session); SpawnPlayer(session, character, jobId, lateJoin); + UpdateJobsAvailable(); } private void SpawnPlayer(IPlayerSession session, @@ -915,6 +919,7 @@ namespace Content.Server.GameTicking _netManager.ServerSendMessage(_getStatusMsg(session), session.ConnectedClient); _netManager.ServerSendMessage(GetInfoMsg(), session.ConnectedClient); _netManager.ServerSendMessage(GetPlayerStatus(), session.ConnectedClient); + _netManager.ServerSendMessage(GetJobsAvailable(), session.ConnectedClient); } private void _playerJoinGame(IPlayerSession session) @@ -938,6 +943,22 @@ namespace Content.Server.GameTicking return msg; } + private MsgTickerJobsAvailable GetJobsAvailable() + { + var message = _netManager.CreateNetMessage(); + + // If late join is disallowed, return no available jobs. + if (DisallowLateJoin) + return message; + + message.JobsAvailable = GetAvailablePositions() + .Where(e => e.Value > 0) + .Select(e => e.Key) + .ToArray(); + + return message; + } + private MsgTickerLobbyReady GetStatusSingle(IPlayerSession player, PlayerStatus status) { var msg = _netManager.CreateNetMessage(); diff --git a/Content.Server/GameTicking/GameTickerCommands.cs b/Content.Server/GameTicking/GameTickerCommands.cs index 9d017a4bb5..c84e448785 100644 --- a/Content.Server/GameTicking/GameTickerCommands.cs +++ b/Content.Server/GameTicking/GameTickerCommands.cs @@ -262,7 +262,15 @@ namespace Content.Server.GameTicking var ticker = IoCManager.Resolve(); - ticker.ToggleDisallowLateJoin(bool.Parse(args[0])); + if (bool.TryParse(args[0], out var result)) + { + ticker.ToggleDisallowLateJoin(bool.Parse(args[0])); + shell.SendText(player, result ? "Late joining has been disabled." : "Late joining has been enabled."); + } + else + { + shell.SendText(player, "Invalid argument."); + } } } diff --git a/Content.Shared/GameTicking/SharedGameTicker.cs b/Content.Shared/GameTicking/SharedGameTicker.cs index 85273bb423..882ed0b095 100644 --- a/Content.Shared/GameTicking/SharedGameTicker.cs +++ b/Content.Shared/GameTicking/SharedGameTicker.cs @@ -231,6 +231,42 @@ namespace Content.Shared.GameTicking } } + protected class MsgTickerJobsAvailable : NetMessage + { + #region REQUIRED + + public const MsgGroups GROUP = MsgGroups.Command; + public const string NAME = nameof(MsgTickerJobsAvailable); + public MsgTickerJobsAvailable(INetChannel channel) : base(NAME, GROUP) { } + + #endregion + + /// + /// The Status of the Player in the lobby (ready, observer, ...) + /// + public string[] JobsAvailable { get; set; } = Array.Empty(); + + public override void ReadFromBuffer(NetIncomingMessage buffer) + { + var amount = buffer.ReadInt32(); + JobsAvailable = new string[amount]; + + for (var i = 0; i < amount; i++) + { + JobsAvailable[i] = buffer.ReadString(); + } + } + + public override void WriteToBuffer(NetOutgoingMessage buffer) + { + buffer.Write(JobsAvailable.Length); + + foreach (var job in JobsAvailable) + { + buffer.Write(job); + } + } + } public struct RoundEndPlayerInfo { diff --git a/Resources/Groups/groups.yml b/Resources/Groups/groups.yml index d0f4df462d..360cbb95f5 100644 --- a/Resources/Groups/groups.yml +++ b/Resources/Groups/groups.yml @@ -108,6 +108,7 @@ - godmode - deleteewi - hurt + - toggledisallowlatejoin CanViewVar: true CanAdminPlace: true CanAdminMenu: true @@ -209,6 +210,7 @@ - godmode - deleteewi - hurt + - toggledisallowlatejoin CanViewVar: true CanAdminPlace: true CanScript: true