diff --git a/Content.Client/Chat/ChatBox.cs b/Content.Client/Chat/ChatBox.cs index 87bbebba1b..7f3e1aa3ec 100644 --- a/Content.Client/Chat/ChatBox.cs +++ b/Content.Client/Chat/ChatBox.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Content.Shared.Chat; +using Robust.Client.Console; using Robust.Client.Graphics.Drawing; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; @@ -26,6 +27,7 @@ namespace Content.Client.Chat public Button AllButton { get; } public Button LocalButton { get; } public Button OOCButton { get; } + public Button AdminButton { get; } /// /// Default formatting string for the ClientChatConsole. @@ -59,6 +61,7 @@ namespace Content.Client.Chat outerVBox.AddChild(panelContainer); outerVBox.AddChild(hBox); + var contentMargin = new MarginContainer { MarginLeftOverride = 4, MarginRightOverride = 4, @@ -95,6 +98,17 @@ namespace Content.Client.Chat ToggleMode = true, }; + var groupController = IoCManager.Resolve(); + if(groupController.CanCommand("asay")) + { + AdminButton = new Button + { + Text = _localize.GetString("Admin"), + Name = "Admin", + ToggleMode = true, + }; + } + AllButton.OnToggled += OnFilterToggled; LocalButton.OnToggled += OnFilterToggled; OOCButton.OnToggled += OnFilterToggled; @@ -102,6 +116,11 @@ namespace Content.Client.Chat hBox.AddChild(AllButton); hBox.AddChild(LocalButton); hBox.AddChild(OOCButton); + if(AdminButton != null) + { + AdminButton.OnToggled += OnFilterToggled; + hBox.AddChild(AdminButton); + } AddChild(outerVBox); } diff --git a/Content.Client/Chat/ChatManager.cs b/Content.Client/Chat/ChatManager.cs index 61d1d4ae03..2264931496 100644 --- a/Content.Client/Chat/ChatManager.cs +++ b/Content.Client/Chat/ChatManager.cs @@ -48,6 +48,7 @@ namespace Content.Client.Chat private const char ConCmdSlash = '/'; private const char OOCAlias = '['; private const char MeAlias = '@'; + private const char AdminChatAlias = ']'; private readonly List filteredHistory = new List(); @@ -55,6 +56,7 @@ namespace Content.Client.Chat private bool _allState; private bool _localState; private bool _oocState; + private bool _adminState; // Flag Enums for holding filtered channels private ChatChannel _filteredChannels; @@ -65,6 +67,7 @@ namespace Content.Client.Chat [Dependency] private readonly IEntityManager _entityManager; [Dependency] private readonly IEyeManager _eyeManager; [Dependency] private readonly IUserInterfaceManager _userInterfaceManager; + [Dependency] private readonly IClientConGroupController _groupController = default!; #pragma warning restore 649 private ChatBox _currentChatBox; @@ -150,6 +153,8 @@ namespace Content.Client.Chat _currentChatBox.AllButton.Pressed = !_allState; _currentChatBox.LocalButton.Pressed = !_localState; _currentChatBox.OOCButton.Pressed = !_oocState; + if(chatBox.AdminButton != null) + _currentChatBox.AdminButton.Pressed = !_adminState; } public void RemoveSpeechBubble(EntityUid entityUid, SpeechBubble bubble) @@ -193,6 +198,9 @@ namespace Content.Client.Chat case ChatChannel.Dead: color = Color.MediumPurple; break; + case ChatChannel.AdminChat: + color = Color.Red; + break; } _currentChatBox?.AddLine(messageText, message.Channel, color); @@ -220,6 +228,18 @@ namespace Content.Client.Chat _console.ProcessCommand($"ooc \"{CommandParsing.Escape(conInput)}\""); break; } + case AdminChatAlias: + { + var conInput = text.Substring(1); + if(_groupController.CanCommand("asay")){ + _console.ProcessCommand($"asay \"{CommandParsing.Escape(conInput)}\""); + } + else + { + _console.ProcessCommand($"ooc \"{CommandParsing.Escape(conInput)}\""); + } + break; + } case MeAlias: { var conInput = text.Substring(1); @@ -266,10 +286,23 @@ namespace Content.Client.Chat _filteredChannels &= ~ChatChannel.OOC; break; } + case "Admin": + _adminState = !_adminState; + if (_adminState) + { + _filteredChannels |= ChatChannel.AdminChat; + break; + } + else + { + _filteredChannels &= ~ChatChannel.AdminChat; + break; + } case "ALL": chatBox.LocalButton.Pressed ^= true; chatBox.OOCButton.Pressed ^= true; + chatBox.AdminButton.Pressed ^= true; _allState = !_allState; break; } diff --git a/Content.Client/Input/ContentContexts.cs b/Content.Client/Input/ContentContexts.cs index 5e54b537dc..197774ed33 100644 --- a/Content.Client/Input/ContentContexts.cs +++ b/Content.Client/Input/ContentContexts.cs @@ -14,6 +14,7 @@ namespace Content.Client.Input var common = contexts.GetContext("common"); common.AddFunction(ContentKeyFunctions.FocusChat); common.AddFunction(ContentKeyFunctions.FocusOOC); + common.AddFunction(ContentKeyFunctions.FocusAdminChat); common.AddFunction(ContentKeyFunctions.ExamineEntity); common.AddFunction(ContentKeyFunctions.OpenTutorial); common.AddFunction(ContentKeyFunctions.TakeScreenshot); diff --git a/Content.Client/State/GameScreen.cs b/Content.Client/State/GameScreen.cs index a0d947c4e8..7e36c9cca0 100644 --- a/Content.Client/State/GameScreen.cs +++ b/Content.Client/State/GameScreen.cs @@ -4,6 +4,7 @@ using Content.Client.Chat; using Content.Client.Interfaces.Chat; using Content.Client.UserInterface; using Content.Shared.Input; +using Robust.Client.Console; using Robust.Client.Interfaces.Input; using Robust.Client.Interfaces.State; using Robust.Client.Interfaces.UserInterface; @@ -25,6 +26,7 @@ namespace Content.Client.State [Dependency] private readonly IGameHud _gameHud; [Dependency] private readonly IInputManager _inputManager; [Dependency] private readonly IChatManager _chatManager; + [Dependency] private readonly IClientConGroupController _groupController = default!; #pragma warning restore 649 [ViewVariables] private ChatBox _gameChat; @@ -34,6 +36,7 @@ namespace Content.Client.State base.Startup(); _gameChat = new ChatBox(); + _userInterfaceManager.StateRoot.AddChild(_gameChat); LayoutContainer.SetAnchorAndMarginPreset(_gameChat, LayoutContainer.LayoutPreset.TopRight, margin: 10); LayoutContainer.SetAnchorAndMarginPreset(_gameChat, LayoutContainer.LayoutPreset.TopRight, margin: 10); @@ -50,6 +53,9 @@ namespace Content.Client.State _inputManager.SetInputCommand(ContentKeyFunctions.FocusOOC, InputCmdHandler.FromDelegate(s => FocusOOC(_gameChat))); + + _inputManager.SetInputCommand(ContentKeyFunctions.FocusAdminChat, + InputCmdHandler.FromDelegate(s => FocusAdminChat(_gameChat))); } public override void Shutdown() @@ -81,5 +87,17 @@ namespace Content.Client.State chat.Input.GrabKeyboardFocus(); chat.Input.InsertAtCursor("["); } + + internal static void FocusAdminChat(ChatBox chat) + { + if (chat == null || chat.UserInterfaceManager.KeyboardFocused != null) + { + return; + } + + chat.Input.IgnoreNext = true; + chat.Input.GrabKeyboardFocus(); + chat.Input.InsertAtCursor("]"); + } } } diff --git a/Content.Client/UserInterface/TutorialWindow.cs b/Content.Client/UserInterface/TutorialWindow.cs index d34ac4cb0d..bf6796e789 100644 --- a/Content.Client/UserInterface/TutorialWindow.cs +++ b/Content.Client/UserInterface/TutorialWindow.cs @@ -76,6 +76,7 @@ Open character window: [color=#a4885c]{8}[/color] Open crafting window: [color=#a4885c]{9}[/color] Focus chat: [color=#a4885c]{10}[/color] Focus OOC: [color=#a4885c]{26}[/color] +Focus Admin Chat: [color=#a4885c]{27}[/color] Use hand/object in hand: [color=#a4885c]{22}[/color] Do wide attack: [color=#a4885c]{23}[/color] Use targeted entity: [color=#a4885c]{11}[/color] @@ -112,7 +113,8 @@ Toggle sandbox window: [color=#a4885c]{21}[/color]", Key(WideAttack), Key(SmartEquipBackpack), Key(SmartEquipBelt), - Key(FocusOOC))); + Key(FocusOOC), + Key(FocusAdminChat))); //Gameplay VBox.AddChild(new Label { FontOverride = headerFont, Text = "\nGameplay" }); diff --git a/Content.Server/Chat/ChatCommands.cs b/Content.Server/Chat/ChatCommands.cs index 99a6d06963..051c382bcf 100644 --- a/Content.Server/Chat/ChatCommands.cs +++ b/Content.Server/Chat/ChatCommands.cs @@ -4,6 +4,7 @@ using Content.Server.Interfaces.Chat; using Content.Server.Interfaces.GameObjects; using Content.Server.Players; using Content.Shared.GameObjects; +using Robust.Server.Console; using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; using Robust.Shared.Enums; @@ -79,6 +80,19 @@ namespace Content.Server.Chat } } + internal class AdminChatCommand : IClientCommand + { + public string Command => "asay"; + public string Description => "Send chat messages to the private admin chat channel."; + public string Help => "asay "; + + public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + { + var chat = IoCManager.Resolve(); + chat.SendAdminChat(player, string.Join(" ", args)); + } + } + internal class SuicideCommand : IClientCommand { public string Command => "suicide"; diff --git a/Content.Server/Chat/ChatManager.cs b/Content.Server/Chat/ChatManager.cs index 60c6eb4a64..21dedca1df 100644 --- a/Content.Server/Chat/ChatManager.cs +++ b/Content.Server/Chat/ChatManager.cs @@ -7,6 +7,7 @@ using Content.Server.Observer; using Content.Server.Players; using Content.Shared.Chat; using Content.Shared.GameObjects.EntitySystems; +using Robust.Server.Console; using Robust.Server.Interfaces.Player; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Network; @@ -28,6 +29,7 @@ namespace Content.Server.Chat [Dependency] private readonly IPlayerManager _playerManager; [Dependency] private readonly ILocalizationManager _localizationManager; [Dependency] private readonly IMoMMILink _mommiLink; + [Dependency] private readonly IConGroupController _conGroupController; #pragma warning restore 649 public void Initialize() @@ -112,6 +114,23 @@ namespace Content.Server.Chat _netManager.ServerSendToMany(msg, clients.ToList()); } + public void SendAdminChat(IPlayerSession player, string message) + { + if(!_conGroupController.CanCommand(player, "asay")) + { + SendOOC(player, message); + return; + } + var clients = _playerManager.GetPlayersBy(x => _conGroupController.CanCommand(x, "asay")).Select(p => p.ConnectedClient);; + + var msg = _netManager.CreateNetMessage(); + + msg.Channel = ChatChannel.AdminChat; + msg.Message = message; + msg.MessageWrap = $"{_localizationManager.GetString("ADMIN")}: {player.SessionId}: {{0}}"; + _netManager.ServerSendToMany(msg, clients.ToList()); + } + public void SendHookOOC(string sender, string message) { var msg = _netManager.CreateNetMessage(); diff --git a/Content.Server/Interfaces/Chat/IChatManager.cs b/Content.Server/Interfaces/Chat/IChatManager.cs index 26af31827a..c3c9b6ceb3 100644 --- a/Content.Server/Interfaces/Chat/IChatManager.cs +++ b/Content.Server/Interfaces/Chat/IChatManager.cs @@ -18,6 +18,7 @@ namespace Content.Server.Interfaces.Chat void EntityMe(IEntity source, string action); void SendOOC(IPlayerSession player, string message); + void SendAdminChat(IPlayerSession player, string message); void SendDeadChat(IPlayerSession player, string message); void SendHookOOC(string sender, string message); diff --git a/Content.Shared/Chat/ChatChannel.cs b/Content.Shared/Chat/ChatChannel.cs index 4c9da79f0b..4b63a75b11 100644 --- a/Content.Shared/Chat/ChatChannel.cs +++ b/Content.Shared/Chat/ChatChannel.cs @@ -51,9 +51,14 @@ namespace Content.Shared.Chat /// Dead = 128, + /// + /// Admin chat + /// + AdminChat = 256, + /// /// Unspecified. /// - Unspecified = 256, + Unspecified = 512, } } diff --git a/Content.Shared/Chat/MsgChatMessage.cs b/Content.Shared/Chat/MsgChatMessage.cs index 10e73d881e..049024ccf4 100644 --- a/Content.Shared/Chat/MsgChatMessage.cs +++ b/Content.Shared/Chat/MsgChatMessage.cs @@ -41,7 +41,7 @@ namespace Content.Shared.Chat public override void ReadFromBuffer(NetIncomingMessage buffer) { - Channel = (ChatChannel) buffer.ReadByte(); + Channel = (ChatChannel) buffer.ReadInt16(); Message = buffer.ReadString(); MessageWrap = buffer.ReadString(); @@ -49,6 +49,7 @@ namespace Content.Shared.Chat { case ChatChannel.Local: case ChatChannel.Dead: + case ChatChannel.AdminChat: case ChatChannel.Emotes: SenderEntity = buffer.ReadEntityUid(); break; @@ -57,7 +58,7 @@ namespace Content.Shared.Chat public override void WriteToBuffer(NetOutgoingMessage buffer) { - buffer.Write((byte)Channel); + buffer.Write((short)Channel); buffer.Write(Message); buffer.Write(MessageWrap); @@ -65,6 +66,7 @@ namespace Content.Shared.Chat { case ChatChannel.Local: case ChatChannel.Dead: + case ChatChannel.AdminChat: case ChatChannel.Emotes: buffer.Write(SenderEntity); break; diff --git a/Content.Shared/Input/ContentKeyFunctions.cs b/Content.Shared/Input/ContentKeyFunctions.cs index bdbc7cee49..90b5820089 100644 --- a/Content.Shared/Input/ContentKeyFunctions.cs +++ b/Content.Shared/Input/ContentKeyFunctions.cs @@ -12,6 +12,7 @@ namespace Content.Shared.Input public static readonly BoundKeyFunction ExamineEntity = "ExamineEntity"; public static readonly BoundKeyFunction FocusChat = "FocusChatWindow"; public static readonly BoundKeyFunction FocusOOC = "FocusOOCWindow"; + public static readonly BoundKeyFunction FocusAdminChat = "FocusAdminChatWindow"; public static readonly BoundKeyFunction OpenCharacterMenu = "OpenCharacterMenu"; public static readonly BoundKeyFunction OpenContextMenu = "OpenContextMenu"; public static readonly BoundKeyFunction OpenCraftingMenu = "OpenCraftingMenu"; diff --git a/Resources/Groups/groups.yml b/Resources/Groups/groups.yml index dc76b17683..30af430579 100644 --- a/Resources/Groups/groups.yml +++ b/Resources/Groups/groups.yml @@ -82,6 +82,7 @@ - warp - hostlogin - deleteewc + - asay CanViewVar: true CanAdminPlace: true @@ -152,6 +153,7 @@ - warp - deleteewc - sudo + - asay CanViewVar: true CanAdminPlace: true CanScript: true diff --git a/Resources/keybinds.yml b/Resources/keybinds.yml index 5c2a9da3c3..8a715222da 100644 --- a/Resources/keybinds.yml +++ b/Resources/keybinds.yml @@ -46,6 +46,9 @@ binds: - function: FocusOOCWindow type: State key: LBracket +- function: FocusAdminChatWindow + type: State + key: RBracket - function: EditorLinePlace type: State key: MouseLeft