diff --git a/Content.Client/Administration/UI/Tabs/ServerTab.xaml b/Content.Client/Administration/UI/Tabs/ServerTab.xaml index 81e8916949..4691a9da9f 100644 --- a/Content.Client/Administration/UI/Tabs/ServerTab.xaml +++ b/Content.Client/Administration/UI/Tabs/ServerTab.xaml @@ -8,5 +8,6 @@ + diff --git a/Content.Client/Chat/ChatHelper.cs b/Content.Client/Chat/ChatHelper.cs index 275c7d2069..fdf4544b1c 100644 --- a/Content.Client/Chat/ChatHelper.cs +++ b/Content.Client/Chat/ChatHelper.cs @@ -10,7 +10,8 @@ namespace Content.Client.Chat { ChatChannel.Server => Color.Orange, ChatChannel.Radio => Color.LimeGreen, - ChatChannel.OOC => Color.LightSkyBlue, + ChatChannel.LOOC => Color.LightSkyBlue, + ChatChannel.OOC => Color.RoyalBlue, ChatChannel.Dead => Color.MediumPurple, ChatChannel.Admin => Color.Red, _ => Color.DarkGray diff --git a/Content.Client/Chat/Managers/ChatManager.cs b/Content.Client/Chat/Managers/ChatManager.cs index 60ea578aa1..5088ae9b28 100644 --- a/Content.Client/Chat/Managers/ChatManager.cs +++ b/Content.Client/Chat/Managers/ChatManager.cs @@ -188,6 +188,8 @@ namespace Content.Client.Chat.Managers // can always send/recieve OOC SelectableChannels |= ChatSelectChannel.OOC; FilterableChannels |= ChatChannel.OOC; + SelectableChannels |= ChatSelectChannel.LOOC; + FilterableChannels |= ChatChannel.LOOC; // can always hear server (nobody can actually send server messages). FilterableChannels |= ChatChannel.Server; @@ -318,6 +320,10 @@ namespace Content.Client.Chat.Managers _consoleHost.ExecuteCommand(text.ToString()); break; + case ChatSelectChannel.LOOC: + _consoleHost.ExecuteCommand($"looc \"{CommandParsing.Escape(str)}\""); + break; + case ChatSelectChannel.OOC: _consoleHost.ExecuteCommand($"ooc \"{CommandParsing.Escape(str)}\""); break; diff --git a/Content.Client/Chat/UI/ChatBox.xaml.cs b/Content.Client/Chat/UI/ChatBox.xaml.cs index 0199d0666a..945d8e494d 100644 --- a/Content.Client/Chat/UI/ChatBox.xaml.cs +++ b/Content.Client/Chat/UI/ChatBox.xaml.cs @@ -44,6 +44,7 @@ namespace Content.Client.Chat.UI ChatSelectChannel.Local, ChatSelectChannel.Emotes, ChatSelectChannel.Radio, + ChatSelectChannel.LOOC, ChatSelectChannel.OOC, ChatSelectChannel.Dead, ChatSelectChannel.Admin @@ -491,7 +492,8 @@ namespace Content.Client.Chat.UI return channel switch { ChatSelectChannel.Radio => Color.LimeGreen, - ChatSelectChannel.OOC => Color.LightSkyBlue, + ChatSelectChannel.LOOC => Color.LightSkyBlue, + ChatSelectChannel.OOC => Color.RoyalBlue, ChatSelectChannel.Dead => Color.MediumPurple, ChatSelectChannel.Admin => Color.Red, _ => Color.DarkGray diff --git a/Content.Server/Chat/Commands/LOOCCommand.cs b/Content.Server/Chat/Commands/LOOCCommand.cs new file mode 100644 index 0000000000..5d627eee15 --- /dev/null +++ b/Content.Server/Chat/Commands/LOOCCommand.cs @@ -0,0 +1,56 @@ +using Content.Server.Administration; +using Content.Server.Chat.Managers; +using Content.Server.Players; +using Content.Shared.Administration; +using Robust.Server.Player; +using Robust.Shared.Console; +using Robust.Shared.Enums; +using Robust.Shared.IoC; + +namespace Content.Server.Chat.Commands +{ + [AnyCommand] + internal class LOOCCommand : IConsoleCommand + { + public string Command => "looc"; + public string Description => "Send Local Out Of Character chat messages."; + public string Help => "looc "; + + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + var player = shell.Player as IPlayerSession; + if (player == null) + { + shell.WriteLine("This command cannot be run from the server."); + return; + } + + if (player.Status != SessionStatus.InGame || player.AttachedEntity == null) + return; + + if (args.Length < 1) + return; + + var message = string.Join(" ", args).Trim(); + if (string.IsNullOrEmpty(message)) + return; + + var chat = IoCManager.Resolve(); + var mindComponent = player.ContentData()?.Mind; + + if (mindComponent == null) + { + shell.WriteError("You don't have a mind!"); + return; + } + + if (mindComponent.OwnedEntity == null) + { + shell.WriteError("You don't have an entity!"); + return; + } + + chat.EntityLOOC(mindComponent.OwnedEntity.Value, message); + } + } +} diff --git a/Content.Server/Chat/Commands/SetLOOCCommand.cs b/Content.Server/Chat/Commands/SetLOOCCommand.cs new file mode 100644 index 0000000000..3ed206b054 --- /dev/null +++ b/Content.Server/Chat/Commands/SetLOOCCommand.cs @@ -0,0 +1,44 @@ +using Content.Server.Administration; +using Content.Shared.Administration; +using Content.Shared.CCVar; +using Robust.Shared.Configuration; +using Robust.Shared.Console; +using Robust.Shared.IoC; +using Robust.Shared.Localization; + +namespace Content.Server.Chat.Commands; + +[AdminCommand(AdminFlags.Server)] +public class SetLOOCCommand : IConsoleCommand +{ + public string Command => "setlooc"; + public string Description => Loc.GetString("set-looc-command-description"); + public string Help => Loc.GetString("set-looc-command-help"); + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + var cfg = IoCManager.Resolve(); + + if (args.Length > 1) + { + shell.WriteError(Loc.GetString("set-looc-command-too-many-arguments-error")); + return; + } + + var looc = cfg.GetCVar(CCVars.LoocEnabled); + + if (args.Length == 0) + { + looc = !looc; + } + + if (args.Length == 1 && !bool.TryParse(args[0], out looc)) + { + shell.WriteError(Loc.GetString("set-looc-command-invalid-argument-error")); + return; + } + + cfg.SetCVar(CCVars.LoocEnabled, looc); + + shell.WriteLine(Loc.GetString(looc ? "set-looc-command-looc-enabled" : "set-looc-command-looc-disabled")); + } +} diff --git a/Content.Server/Chat/Managers/ChatManager.cs b/Content.Server/Chat/Managers/ChatManager.cs index cb1576e6e9..18df74e465 100644 --- a/Content.Server/Chat/Managers/ChatManager.cs +++ b/Content.Server/Chat/Managers/ChatManager.cs @@ -59,12 +59,15 @@ namespace Content.Server.Chat.Managers private readonly List _chatTransformHandlers = new(); private bool _oocEnabled = true; private bool _adminOocEnabled = true; + private bool _loocEnabled = true; + private bool _adminLoocEnabled = true; public void Initialize() { _netManager.RegisterNetMessage(); _configurationManager.OnValueChanged(CCVars.OocEnabled, OnOocEnabledChanged, true); + _configurationManager.OnValueChanged(CCVars.LoocEnabled, OnLoocEnabledChanged, true); _configurationManager.OnValueChanged(CCVars.AdminOocEnabled, OnAdminOocEnabledChanged, true); } @@ -74,6 +77,12 @@ namespace Content.Server.Chat.Managers DispatchServerAnnouncement(Loc.GetString(val ? "chat-manager-ooc-chat-enabled-message" : "chat-manager-ooc-chat-disabled-message")); } + private void OnLoocEnabledChanged(bool val) + { + _loocEnabled = val; + DispatchServerAnnouncement(Loc.GetString(val ? "chat-manager-looc-chat-enabled-message" : "chat-manager-looc-chat-disabled-message")); + } + private void OnAdminOocEnabledChanged(bool val) { _adminOocEnabled = val; @@ -238,6 +247,47 @@ namespace Content.Server.Chat.Managers _netManager.ServerSendToMany(msg, clients); } + public void EntityLOOC(EntityUid source, string message) + { + // Check if entity is a player + if (!_entManager.TryGetComponent(source, out ActorComponent? actor)) + { + return; + } + + if (_adminManager.IsAdmin(actor.PlayerSession)) + { + if (!_adminLoocEnabled) + { + return; + } + } + else if (!_loocEnabled) + { + return; + } + + // Check if message exceeds the character limit + if (message.Length > MaxMessageLength) + { + DispatchServerMessage(actor.PlayerSession, Loc.GetString("chat-manager-max-message-length-exceeded-message", ("limit", MaxMessageLength))); + return; + } + + message = FormattedMessage.EscapeText(message); + + var clients = Filter.Empty() + .AddInRange(_entManager.GetComponent(source).MapPosition, VoiceRange) + .Recipients + .Select(p => p.ConnectedClient) + .ToList(); + + var msg = _netManager.CreateNetMessage(); + msg.Channel = ChatChannel.LOOC; + msg.Message = message; + msg.MessageWrap = Loc.GetString("chat-manager-entity-looc-wrap-message", ("entityName", Name: _entManager.GetComponent(source).EntityName)); + _netManager.ServerSendToMany(msg, clients); + } public void SendOOC(IPlayerSession player, string message) { if (_adminManager.IsAdmin(player)) diff --git a/Content.Server/Chat/Managers/IChatManager.cs b/Content.Server/Chat/Managers/IChatManager.cs index abf7cd2032..2c533aa8dc 100644 --- a/Content.Server/Chat/Managers/IChatManager.cs +++ b/Content.Server/Chat/Managers/IChatManager.cs @@ -25,6 +25,7 @@ namespace Content.Server.Chat.Managers /// If true, message will not be logged to chat boxes but will still produce a speech bubble. void EntitySay(EntityUid source, string message, bool hideChat=false); void EntityMe(EntityUid source, string action); + void EntityLOOC(EntityUid source, string message); void SendOOC(IPlayerSession player, string message); void SendAdminChat(IPlayerSession player, string message); diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index a9de00a556..1b6bda71b8 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -485,6 +485,15 @@ namespace Content.Shared.CCVar public static readonly CVarDef AdminOocEnabled = CVarDef.Create("ooc.enabled_admin", true, CVar.NOTIFY); + /* + * LOOC + */ + + public static readonly CVarDef LoocEnabled = CVarDef.Create("looc.enabled", true, CVar.NOTIFY); + + public static readonly CVarDef AdminLoocEnabled = + CVarDef.Create("looc.enabled_admin", true, CVar.NOTIFY); + /* * Entity Menu Grouping Types */ diff --git a/Content.Shared/Chat/ChatChannel.cs b/Content.Shared/Chat/ChatChannel.cs index fb93d172db..311cadc7ec 100644 --- a/Content.Shared/Chat/ChatChannel.cs +++ b/Content.Shared/Chat/ChatChannel.cs @@ -30,36 +30,41 @@ namespace Content.Shared.Chat /// Radio = 1 << 3, + /// + /// Local out-of-character channel + /// + LOOC = 1 << 4, + /// /// Out-of-character channel /// - OOC = 1 << 4, + OOC = 1 << 5, /// /// Visual events the player can see. /// Basically like visual_message in SS13. /// - Visual = 1 << 5, + Visual = 1 << 6, /// /// Emotes /// - Emotes = 1 << 6, + Emotes = 1 << 7, /// /// Deadchat /// - Dead = 1 << 7, + Dead = 1 << 8, /// /// Admin chat /// - Admin = 1 << 8, + Admin = 1 << 9, /// /// Unspecified. /// - Unspecified = 1 << 9, + Unspecified = 1 << 10, /// /// Channels considered to be IC. diff --git a/Content.Shared/Chat/ChatSelectChannel.cs b/Content.Shared/Chat/ChatSelectChannel.cs index 56d70cf1d1..1f7d6bb362 100644 --- a/Content.Shared/Chat/ChatSelectChannel.cs +++ b/Content.Shared/Chat/ChatSelectChannel.cs @@ -23,6 +23,11 @@ namespace Content.Shared.Chat /// Radio = ChatChannel.Radio, + /// + /// Local out-of-character channel + /// + LOOC = ChatChannel.LOOC, + /// /// Out-of-character channel /// diff --git a/Resources/Locale/en-US/administration/commands/set-looc-command.ftl b/Resources/Locale/en-US/administration/commands/set-looc-command.ftl new file mode 100644 index 0000000000..2125939052 --- /dev/null +++ b/Resources/Locale/en-US/administration/commands/set-looc-command.ftl @@ -0,0 +1,6 @@ +set-looc-command-description = Allows you to enable or disable LOOC. +set-looc-command-help = Usage: setlooc OR setlooc [value] +set-looc-command-too-many-arguments-error = Too many arguments. +set-looc-command-invalid-argument-error = Invalid argument. +set-looc-command-looc-enabled = LOOC chat has been enabled. +set-looc-command-looc-disabled = LOOC chat has been disabled. diff --git a/Resources/Locale/en-US/administration/ui/tabs/server-tab.ftl b/Resources/Locale/en-US/administration/ui/tabs/server-tab.ftl index 581a2a1c87..41999beaf0 100644 --- a/Resources/Locale/en-US/administration/ui/tabs/server-tab.ftl +++ b/Resources/Locale/en-US/administration/ui/tabs/server-tab.ftl @@ -1,3 +1,4 @@ server-reboot = Reboot server-shutdown = Shutdown server-ooc-toggle = Toggle OOC +server-looc-toggle = Toggle LOOC diff --git a/Resources/Locale/en-US/chat/managers/chat-manager.ftl b/Resources/Locale/en-US/chat/managers/chat-manager.ftl index 01b43a67c6..91544a604b 100644 --- a/Resources/Locale/en-US/chat/managers/chat-manager.ftl +++ b/Resources/Locale/en-US/chat/managers/chat-manager.ftl @@ -3,6 +3,8 @@ chat-manager-max-message-length = Your message exceeds {$maxMessageLength} character limit chat-manager-ooc-chat-enabled-message = OOC chat has been enabled. chat-manager-ooc-chat-disabled-message = OOC chat has been disabled. +chat-manager-looc-chat-enabled-message = LOOC chat has been enabled. +chat-manager-looc-chat-disabled-message = LOOC chat has been disabled. chat-manager-admin-ooc-chat-enabled-message = Admin OOC chat has been enabled. chat-manager-admin-ooc-chat-disabled-message = Admin OOC chat has been disabled. chat-manager-max-message-length-exceeded-message = Your message exceeded {$limit} character limit @@ -12,6 +14,7 @@ chat-manager-sender-announcement-wrap-message = {$sender} Announcement: {"{0}"} chat-manager-entity-say-wrap-message = {$entityName} says: "{"{0}"}" chat-manager-entity-me-wrap-message = {$entityName} {"{0}"} +chat-manager-entity-looc-wrap-message = LOOC: {$entityName}: {"{0}"} chat-manager-send-ooc-wrap-message = OOC: {$playerName}: {"{0}"} chat-manager-send-ooc-patron-wrap-message = OOC: [color={$patronColor}]{$playerName}[/color]: {"{0}"} chat-manager-send-dead-chat-wrap-message = {$deadChannelName}: {$playerName}: {"{0}"} diff --git a/Resources/Locale/en-US/chat/ui/chat-box.ftl b/Resources/Locale/en-US/chat/ui/chat-box.ftl index 84a7d09283..b78117df8f 100644 --- a/Resources/Locale/en-US/chat/ui/chat-box.ftl +++ b/Resources/Locale/en-US/chat/ui/chat-box.ftl @@ -6,6 +6,7 @@ hud-chatbox-select-channel-Console = Console hud-chatbox-select-channel-Dead = Dead hud-chatbox-select-channel-Emotes = Emotes hud-chatbox-select-channel-Local = Local +hud-chatbox-select-channel-LOOC = LOOC hud-chatbox-select-channel-OOC = OOC hud-chatbox-select-channel-Radio = Radio @@ -13,6 +14,7 @@ hud-chatbox-channel-Admin = Admin hud-chatbox-channel-Dead = Dead hud-chatbox-channel-Emotes = Emotes hud-chatbox-channel-Local = Local +hud-chatbox-channel-LOOC = LOOC hud-chatbox-channel-OOC = OOC hud-chatbox-channel-Radio = Radio hud-chatbox-channel-Server = Server