diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index f180cf045a..fd3182e65c 100644 --- a/Content.Server/Chat/Systems/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -77,6 +77,7 @@ public sealed partial class ChatSystem : SharedChatSystem base.Shutdown(); ShutdownEmotes(); _configurationManager.UnsubValueChanged(CCVars.LoocEnabled, OnLoocEnabledChanged); + _configurationManager.UnsubValueChanged(CCVars.DeadLoocEnabled, OnDeadLoocEnabledChanged); } private void OnLoocEnabledChanged(bool val) @@ -99,13 +100,17 @@ public sealed partial class ChatSystem : SharedChatSystem private void OnGameChange(GameRunLevelChangedEvent ev) { - if (_configurationManager.GetCVar(CCVars.OocEnableDuringRound)) - return; - - if (ev.New == GameRunLevel.InRound) - _configurationManager.SetCVar(CCVars.OocEnabled, false); - else if (ev.New == GameRunLevel.PostRound) - _configurationManager.SetCVar(CCVars.OocEnabled, true); + switch(ev.New) + { + case GameRunLevel.InRound: + if(!_configurationManager.GetCVar(CCVars.OocEnableDuringRound)) + _configurationManager.SetCVar(CCVars.OocEnabled, false); + break; + case GameRunLevel.PostRound: + if(!_configurationManager.GetCVar(CCVars.OocEnableDuringRound)) + _configurationManager.SetCVar(CCVars.OocEnabled, true); + break; + } } /// diff --git a/Content.Server/Motd/GetMOTDCommand.cs b/Content.Server/Motd/GetMOTDCommand.cs new file mode 100644 index 0000000000..dc310e9235 --- /dev/null +++ b/Content.Server/Motd/GetMOTDCommand.cs @@ -0,0 +1,20 @@ +using Content.Shared.Administration; +using Robust.Shared.Console; + +namespace Content.Server.Motd; + +/// +/// A command that can be used by any player to print the Message of the Day. +/// +[AnyCommand] +public sealed class GetMotdCommand : LocalizedCommands +{ + [Dependency] private readonly IEntityManager _entityManager = default!; + + public override string Command => "get-motd"; + + public override void Execute(IConsoleShell shell, string argStr, string[] args) + { + _entityManager.EntitySysManager.GetEntitySystem().TrySendMOTD(shell); + } +} diff --git a/Content.Server/Motd/MOTDCommand.cs b/Content.Server/Motd/MOTDCommand.cs new file mode 100644 index 0000000000..a1aa4d2df5 --- /dev/null +++ b/Content.Server/Motd/MOTDCommand.cs @@ -0,0 +1,36 @@ +using Content.Server.Administration.Managers; +using Content.Shared.Administration; +using Robust.Server.Player; +using Robust.Shared.Console; + +namespace Content.Server.Motd; + +/// +/// A console command which acts as an alias for or depending on the number of arguments given. +/// +[AnyCommand] +internal sealed class MOTDCommand : LocalizedCommands +{ + [Dependency] private readonly IAdminManager _adminManager = default!; + + public override string Command => "motd"; + + public override void Execute(IConsoleShell shell, string argStr, string[] args) + { + var player = (IPlayerSession?)shell.Player; + if (args.Length < 1 || (player != null && _adminManager is AdminManager aMan && !aMan.CanCommand(player, "set-motd"))) + shell.ConsoleHost.ExecuteCommand(shell.Player, "get-motd"); + else + shell.ConsoleHost.ExecuteCommand(shell.Player, $"set-motd {string.Join(" ", args)}"); + } + + public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + var player = (IPlayerSession?)shell.Player; + if (player != null && _adminManager is AdminManager aMan && !aMan.CanCommand(player, "set-motd")) + return CompletionResult.Empty; + if (args.Length == 1) + return CompletionResult.FromHint(Loc.GetString("cmd-set-motd-hint-head")); + return CompletionResult.FromHint(Loc.GetString("cmd-set-motd-hint-cont")); + } +} diff --git a/Content.Server/Motd/MOTDSystem.cs b/Content.Server/Motd/MOTDSystem.cs new file mode 100644 index 0000000000..e749fe48f3 --- /dev/null +++ b/Content.Server/Motd/MOTDSystem.cs @@ -0,0 +1,101 @@ +using Content.Server.Chat.Managers; +using Content.Server.GameTicking; +using Content.Shared.CCVar; +using Content.Shared.Chat; +using Robust.Server.Player; +using Robust.Shared.Console; +using Robust.Shared.Configuration; + +namespace Content.Server.Motd; + +/// +/// The system that handles broadcasting the Message Of The Day to players when they join the lobby/the MOTD changes/they ask for it to be printed. +/// +public sealed class MOTDSystem : EntitySystem +{ + [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly IConfigurationManager _configurationManager = default!; + + /// + /// The cached value of the Message of the Day. Used for fast access. + /// + private string _messageOfTheDay = ""; + + public override void Initialize() + { + base.Initialize(); + _configurationManager.OnValueChanged(CCVars.MOTD, OnMOTDChanged, invokeImmediately: true); + SubscribeLocalEvent(OnPlayerJoinedLobby); + } + + public override void Shutdown() + { + _configurationManager.UnsubValueChanged(CCVars.MOTD, OnMOTDChanged); + base.Shutdown(); + } + + /// + /// Sends the Message Of The Day, if any, to all connected players. + /// + public void TrySendMOTD() + { + if (string.IsNullOrEmpty(_messageOfTheDay)) + return; + + var wrappedMessage = Loc.GetString("motd-wrap-message", ("motd", _messageOfTheDay)); + _chatManager.ChatMessageToAll(ChatChannel.Server, _messageOfTheDay, wrappedMessage, source: EntityUid.Invalid, hideChat: false, recordReplay: true); + } + + /// + /// Sends the Message Of The Day, if any, to a specific player. + /// + public void TrySendMOTD(IPlayerSession player) + { + if (string.IsNullOrEmpty(_messageOfTheDay)) + return; + + var wrappedMessage = Loc.GetString("motd-wrap-message", ("motd", _messageOfTheDay)); + _chatManager.ChatMessageToOne(ChatChannel.Server, _messageOfTheDay, wrappedMessage, source: EntityUid.Invalid, hideChat: false, client: player.ConnectedClient); + } + + /// + /// Sends the Message Of The Day, if any, to a specific player's console and chat. + /// + /// + /// This is used by the MOTD console command because we can't tell whether the player is using `console or /console so we send the message to both. + /// + public void TrySendMOTD(IConsoleShell shell) + { + if (string.IsNullOrEmpty(_messageOfTheDay)) + return; + + var wrappedMessage = Loc.GetString("motd-wrap-message", ("motd", _messageOfTheDay)); + shell.WriteLine(wrappedMessage); + if (shell.Player is IPlayerSession player) + _chatManager.ChatMessageToOne(ChatChannel.Server, _messageOfTheDay, wrappedMessage, source: EntityUid.Invalid, hideChat: false, client: player.ConnectedClient); + } + + #region Event Handlers + + /// + /// Posts the Message Of The Day to any players who join the lobby. + /// + private void OnPlayerJoinedLobby(PlayerJoinedLobbyEvent ev) + { + TrySendMOTD(ev.PlayerSession); + } + + /// + /// Broadcasts changes to the Message Of The Day to all players. + /// + private void OnMOTDChanged(string val) + { + if (val == _messageOfTheDay) + return; + + _messageOfTheDay = val; + TrySendMOTD(); + } + + #endregion Event Handlers +} diff --git a/Content.Server/Motd/SetMOTDCommand.cs b/Content.Server/Motd/SetMOTDCommand.cs new file mode 100644 index 0000000000..f3f52b0670 --- /dev/null +++ b/Content.Server/Motd/SetMOTDCommand.cs @@ -0,0 +1,55 @@ +using Content.Server.Administration; +using Content.Server.Administration.Logs; +using Content.Shared.Administration; +using Content.Shared.Database; +using Content.Shared.CCVar; +using Content.Server.Chat.Managers; +using Robust.Server.Player; +using Robust.Shared.Configuration; +using Robust.Shared.Console; + +namespace Content.Server.Motd; + +/// +/// A console command usable by any user which prints or sets the Message of the Day. +/// +[AdminCommand(AdminFlags.Admin)] +public sealed class SetMotdCommand : LocalizedCommands +{ + [Dependency] private readonly IAdminLogManager _adminLogManager = default!; + [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly IConfigurationManager _configurationManager = default!; + + public override string Command => "set-motd"; + + public override void Execute(IConsoleShell shell, string argStr, string[] args) + { + string motd = ""; + var player = (IPlayerSession?)shell.Player; + if (args.Length > 0) + { + motd = string.Join(" ", args).Trim(); + if (player != null && _chatManager.MessageCharacterLimit(player, motd)) + return; // check function prints its own error response + } + + _configurationManager.SetCVar(CCVars.MOTD, motd); // A hook in MOTDSystem broadcasts changes to the MOTD to everyone so we don't need to do it here. + if (string.IsNullOrEmpty(motd)) + { + shell.WriteLine(Loc.GetString("cmd-set-motd-cleared-motd-message")); + _adminLogManager.Add(LogType.Chat, LogImpact.Low, $"{(player == null ? "LOCALHOST" : player.ConnectedClient.UserName):Player} cleared the MOTD for the server."); + } + else + { + shell.WriteLine(Loc.GetString("cmd-set-motd-set-motd-message", ("motd", motd))); + _adminLogManager.Add(LogType.Chat, LogImpact.Low, $"{(player == null ? "LOCALHOST" : player.ConnectedClient.UserName):Player} set the MOTD for the server to \"{motd:motd}\""); + } + } + + public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + if (args.Length == 1) + return CompletionResult.FromHint(Loc.GetString("cmd-set-motd-hint-head")); + return CompletionResult.FromHint(Loc.GetString("cmd-set-motd-hint-cont")); + } +} diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 44cfb1d929..9bcaccc48d 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -1214,6 +1214,15 @@ namespace Content.Shared.CCVar public static readonly CVarDef ChatShowTypingIndicator = CVarDef.Create("chat.show_typing_indicator", true, CVar.CLIENTONLY); + /// + /// A message broadcast to each player that joins the lobby. + /// May be changed by admins ingame through use of the "set-motd" command. + /// In this case the new value, if not empty, is broadcast to all connected players and saved between rounds. + /// May be requested by any player through use of the "get-motd" command. + /// + public static readonly CVarDef MOTD = + CVarDef.Create("chat.motd", "", CVar.SERVER | CVar.SERVERONLY | CVar.ARCHIVE, "A message broadcast to each player that joins the lobby."); + /* * AFK */ diff --git a/Resources/Locale/en-US/motd/motd.ftl b/Resources/Locale/en-US/motd/motd.ftl new file mode 100644 index 0000000000..4e982e7bd4 --- /dev/null +++ b/Resources/Locale/en-US/motd/motd.ftl @@ -0,0 +1,11 @@ +cmd-motd-desc = Prints or sets the Message Of The Day. +cmd-motd-help = motd [ message... ] +cmd-get-motd-desc = Prints the Message Of The Day. +cmd-get-motd-help = get-motd +cmd-set-motd-desc = Sets or clears the Message Of The Day. +cmd-set-motd-help = set-motd [ message... ] +cmd-set-motd-hint-head = [ message... ] +cmd-set-motd-hint-cont = [ ...message... ] +cmd-set-motd-cleared-motd-message = Cleared the Message of the Day. +cmd-set-motd-set-motd-message = Set the Message Of The Day to "{$motd}". +motd-wrap-message = Message of the Day: {$motd}