diff --git a/Content.Client/GameTicking/ClientGameTicker.cs b/Content.Client/GameTicking/ClientGameTicker.cs index 77ca609371..1d3d278a67 100644 --- a/Content.Client/GameTicking/ClientGameTicker.cs +++ b/Content.Client/GameTicking/ClientGameTicker.cs @@ -1,6 +1,7 @@ using System; using Content.Client.Interfaces; using Content.Client.State; +using Content.Client.UserInterface; using Content.Shared; using Robust.Client.Interfaces.State; using Robust.Shared.Interfaces.Network; @@ -35,10 +36,13 @@ namespace Content.Client.GameTicking _netManager.RegisterNetMessage(nameof(MsgTickerJoinGame), JoinGame); _netManager.RegisterNetMessage(nameof(MsgTickerLobbyStatus), LobbyStatus); _netManager.RegisterNetMessage(nameof(MsgTickerLobbyInfo), LobbyInfo); + _netManager.RegisterNetMessage(nameof(MsgRoundEndMessage), RoundEnd); _initialized = true; } + + private void JoinLobby(MsgTickerJoinLobby message) { _stateManager.RequestStateChange(); @@ -64,5 +68,13 @@ namespace Content.Client.GameTicking { _stateManager.RequestStateChange(); } + + 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/UserInterface/RoundEndSummaryWindow.cs b/Content.Client/UserInterface/RoundEndSummaryWindow.cs new file mode 100644 index 0000000000..5e8600b188 --- /dev/null +++ b/Content.Client/UserInterface/RoundEndSummaryWindow.cs @@ -0,0 +1,101 @@ +using Robust.Client.Graphics; +using Robust.Client.Interfaces.Input; +using Robust.Client.Interfaces.ResourceManagement; +using Robust.Client.ResourceManagement; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Maths; +using Content.Client.Utility; +using Robust.Client.Player; +using System.Linq; +using System.Collections.Generic; +using static Robust.Client.UserInterface.Controls.ItemList; +using static Content.Shared.SharedGameTicker; +using System; + +namespace Content.Client.UserInterface +{ + public sealed class RoundEndSummaryWindow : SS14Window + { + private VBoxContainer RoundEndSummaryTab { get; } + private VBoxContainer PlayerManifestoTab { get; } + private TabContainer RoundEndWindowTabs { get; } + protected override Vector2? CustomSize => (520, 580); + + public RoundEndSummaryWindow(string gm, TimeSpan roundTimeSpan, List info ) + { + + Title = Loc.GetString("Round End Summary"); + + //Round End Window is split into two tabs, one about the round stats + //and the other is a list of RoundEndPlayerInfo for each player. + //This tab would be a good place for things like: "x many people died.", + //"clown slipped the crew x times.", "x shots were fired this round.", etc. + //Also good for serious info. + RoundEndSummaryTab = new VBoxContainer() + { + Name = Loc.GetString("Round Information") + }; + + //Tab for listing unique info per player. + PlayerManifestoTab = new VBoxContainer() + { + Name = Loc.GetString("Player Manifesto") + }; + + RoundEndWindowTabs = new TabContainer(); + RoundEndWindowTabs.AddChild(RoundEndSummaryTab); + RoundEndWindowTabs.AddChild(PlayerManifestoTab); + + Contents.AddChild(RoundEndWindowTabs); + + //Gamemode Name + var gamemodeLabel = new RichTextLabel(); + gamemodeLabel.SetMarkup(Loc.GetString("Round of [color=white]{0}[/color] has ended.", gm)); + RoundEndSummaryTab.AddChild(gamemodeLabel); + + //Duration + var roundTimeLabel = new RichTextLabel(); + roundTimeLabel.SetMarkup(Loc.GetString("It lasted for [color=yellow]{0} hours, {1} minutes, and {2} seconds.", + roundTimeSpan.Hours,roundTimeSpan.Minutes,roundTimeSpan.Seconds)); + RoundEndSummaryTab.AddChild(roundTimeLabel); + + //Initialize what will be the list of players display. + var scrollContainer = new ScrollContainer(); + scrollContainer.SizeFlagsVertical = SizeFlags.FillExpand; + var innerScrollContainer = new VBoxContainer(); + + //Put antags on top of the list. + var manifestSortedList = info.OrderBy(p => !p.Antag); + //Create labels for each player info. + foreach (var plyinfo in manifestSortedList) + { + + var playerInfoText = new RichTextLabel() + { + SizeFlagsVertical = SizeFlags.Fill + }; + + //TODO: On Hover display a popup detailing more play info. + //For example: their antag goals and if they completed them sucessfully. + var icNameColor = plyinfo.Antag ? "red" : "white"; + playerInfoText.SetMarkup( + Loc.GetString($"[color=gray]{plyinfo.PlayerOOCName}[/color] was [color={icNameColor}]{plyinfo.PlayerICName}[/color] playing role of [color=orange]{plyinfo.Role}[/color].")); + innerScrollContainer.AddChild(playerInfoText); + } + + scrollContainer.AddChild(innerScrollContainer); + //Attach the entire ScrollContainer that holds all the playerinfo. + PlayerManifestoTab.AddChild(scrollContainer); + + //Finally, display the window. + OpenCentered(); + MoveToFront(); + + } + + } + +} diff --git a/Content.Server/GameTicking/GamePreset.cs b/Content.Server/GameTicking/GamePreset.cs index cf7981b725..b880b162a0 100644 --- a/Content.Server/GameTicking/GamePreset.cs +++ b/Content.Server/GameTicking/GamePreset.cs @@ -1,4 +1,4 @@ -namespace Content.Server.GameTicking +namespace Content.Server.GameTicking { /// /// A round-start setup preset, such as which antagonists to spawn. @@ -6,6 +6,7 @@ namespace Content.Server.GameTicking public abstract class GamePreset { public abstract void Start(); + public virtual string ModeTitle => "Sandbox"; public virtual string Description => "Secret!"; } } diff --git a/Content.Server/GameTicking/GamePresets/PresetDeathMatch.cs b/Content.Server/GameTicking/GamePresets/PresetDeathMatch.cs index 11c716442d..5b866dc8fa 100644 --- a/Content.Server/GameTicking/GamePresets/PresetDeathMatch.cs +++ b/Content.Server/GameTicking/GamePresets/PresetDeathMatch.cs @@ -1,4 +1,4 @@ -using Content.Server.GameTicking.GameRules; +using Content.Server.GameTicking.GameRules; using Content.Server.Interfaces.GameTicking; using Robust.Shared.IoC; @@ -15,6 +15,7 @@ namespace Content.Server.GameTicking.GamePresets _gameTicker.AddGameRule(); } - public override string Description => "Deathmatch, go and kill everybody else to win!"; + public override string ModeTitle => "Deathmatch"; + public override string Description => "Kill anything that moves!"; } } diff --git a/Content.Server/GameTicking/GamePresets/PresetSandbox.cs b/Content.Server/GameTicking/GamePresets/PresetSandbox.cs index f80b41ebd0..05f15c6972 100644 --- a/Content.Server/GameTicking/GamePresets/PresetSandbox.cs +++ b/Content.Server/GameTicking/GamePresets/PresetSandbox.cs @@ -1,4 +1,4 @@ -using Content.Server.Sandbox; +using Content.Server.Sandbox; using Robust.Shared.IoC; namespace Content.Server.GameTicking.GamePresets @@ -14,6 +14,7 @@ namespace Content.Server.GameTicking.GamePresets _sandboxManager.IsSandboxEnabled = true; } - public override string Description => "Sandbox, go and build something!"; + public override string ModeTitle => "Sandbox"; + public override string Description => "No stress, build something!"; } } diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index 7231fff4ba..b2f0a862c3 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -53,6 +53,7 @@ namespace Content.Server.GameTicking private const string PlayerPrototypeName = "HumanMob_Content"; private const string ObserverPrototypeName = "MobObserver"; private const string MapFile = "Maps/stationstation.yml"; + private static TimeSpan _roundStartTimeSpan; [ViewVariables] private readonly List _gameRules = new List(); [ViewVariables] private readonly List _manifest = new List(); @@ -110,6 +111,7 @@ namespace Content.Server.GameTicking _netManager.RegisterNetMessage(nameof(MsgTickerJoinGame)); _netManager.RegisterNetMessage(nameof(MsgTickerLobbyStatus)); _netManager.RegisterNetMessage(nameof(MsgTickerLobbyInfo)); + _netManager.RegisterNetMessage(nameof(MsgRoundEndMessage)); SetStartPreset(_configurationManager.GetCVar("game.defaultpreset")); @@ -219,6 +221,7 @@ namespace Content.Server.GameTicking SpawnPlayer(player, job, false); } + _roundStartTimeSpan = IoCManager.Resolve().RealTime; _sendStatusToAll(); } @@ -240,7 +243,33 @@ namespace Content.Server.GameTicking RunLevel = GameRunLevel.PostRound; - SendServerMessage("The round has ended!"); + //Tell every client the round has ended. + var roundEndMessage = _netManager.CreateNetMessage(); + roundEndMessage.GamemodeTitle = MakeGamePreset().ModeTitle; + + //Get the timespan of the round. + roundEndMessage.RoundDuration = IoCManager.Resolve().RealTime.Subtract(_roundStartTimeSpan); + + //Generate a list of basic player info to display in the end round summary. + var listOfPlayerInfo = new List(); + foreach(var ply in _playerManager.GetAllPlayers().OrderBy(p => p.Name)) + { + if(ply.AttachedEntity.TryGetComponent(out var mindComponent) + && mindComponent.HasMind) + { + var playerEndRoundInfo = new RoundEndPlayerInfo() + { + PlayerOOCName = ply.Name, + PlayerICName = mindComponent.Mind.CurrentEntity.Name, + Role = mindComponent.Mind.AllRoles.FirstOrDefault()?.Name ?? Loc.GetString("Unkown"), + Antag = false + }; + listOfPlayerInfo.Add(playerEndRoundInfo); + } + } + + roundEndMessage.AllPlayersEndInfo = listOfPlayerInfo; + _netManager.ServerSendToAll(roundEndMessage); } public void Respawn(IPlayerSession targetPlayer) @@ -654,10 +683,12 @@ namespace Content.Server.GameTicking private string GetInfoText() { - var gameMode = MakeGamePreset().Description; + var gmTitle = MakeGamePreset().ModeTitle; + var desc = MakeGamePreset().Description; return _localization.GetString(@"Hi and welcome to [color=white]Space Station 14![/color] -The current game mode is [color=white]{0}[/color]", gameMode); +The current game mode is: [color=white]{0}[/color]. +[color=yellow]{1}[/color]", gmTitle, desc ); } private void UpdateInfoText() diff --git a/Content.Shared/SharedGameTicker.cs b/Content.Shared/SharedGameTicker.cs index 126a44e0e9..38d3e0985e 100644 --- a/Content.Shared/SharedGameTicker.cs +++ b/Content.Shared/SharedGameTicker.cs @@ -1,5 +1,7 @@ -using System; +using System; +using System.Collections.Generic; using Lidgren.Network; +using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Network; using Robust.Shared.Network; @@ -114,5 +116,79 @@ namespace Content.Shared buffer.Write(TextBlob); } } + public struct RoundEndPlayerInfo + { + public string PlayerOOCName; + public string PlayerICName; + public string Role; + public bool Antag; + + } + + protected class MsgRoundEndMessage : NetMessage + { + + #region REQUIRED + + public const MsgGroups GROUP = MsgGroups.Command; + public const string NAME = nameof(MsgRoundEndMessage); + public MsgRoundEndMessage(INetChannel channel) : base(NAME, GROUP) { } + + #endregion + + public string GamemodeTitle; + public TimeSpan RoundDuration; + + + public uint PlayerCount; + + public List AllPlayersEndInfo; + + public override void ReadFromBuffer(NetIncomingMessage buffer) + { + GamemodeTitle = buffer.ReadString(); + + var hours = buffer.ReadInt32(); + var mins = buffer.ReadInt32(); + var seconds = buffer.ReadInt32(); + RoundDuration = new TimeSpan(hours, mins, seconds); + + PlayerCount = buffer.ReadUInt32(); + AllPlayersEndInfo = new List(); + for(var i = 0; i < PlayerCount + 1; i++) + { + var readPlayerData = new RoundEndPlayerInfo + { + PlayerOOCName = buffer.ReadString(), + PlayerICName = buffer.ReadString(), + Role = buffer.ReadString(), + Antag = buffer.ReadBoolean() + }; + + AllPlayersEndInfo.Add(readPlayerData); + } + + } + + public override void WriteToBuffer(NetOutgoingMessage buffer) + { + buffer.Write(GamemodeTitle); + buffer.Write(RoundDuration.Hours); + buffer.Write(RoundDuration.Minutes); + buffer.Write(RoundDuration.Seconds); + + + buffer.Write(PlayerCount); + foreach(var playerEndInfo in AllPlayersEndInfo) + { + buffer.Write(playerEndInfo.PlayerOOCName); + buffer.Write(playerEndInfo.PlayerICName); + buffer.Write(playerEndInfo.Role); + buffer.Write(playerEndInfo.Antag); + } + } + + } } } +