From 0840a29ff3cd9b2ab94d37456b64b4a55bfaf276 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Sun, 3 Dec 2017 22:54:28 +0200 Subject: [PATCH 1/9] Started overhauling the client permission system to make it a bit more flexible. Now the clients can have the permission to use specific console commands (atm these commands are relayed to the server as-is). TODO: make it possible to give clients console command permissions via the menus and the console, save command permissions, deal with commands that don't work as intended when simply relayed to the server and executed server-side, add "ranks" (preconfigured or custom sets of permissions, e.g. moderator, admin, etc). --- .../BarotraumaClient/Source/DebugConsole.cs | 2 +- .../Source/Networking/GameClient.cs | 43 +++++++++++++++++-- .../BarotraumaShared/Source/DebugConsole.cs | 23 +++++++--- .../Source/Networking/Client.cs | 5 ++- .../Source/Networking/GameServer.cs | 12 ++++++ .../Source/Networking/GameServerSettings.cs | 8 +++- 6 files changed, 80 insertions(+), 13 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs index 0283ec3d6..626969eb5 100644 --- a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs @@ -134,7 +134,7 @@ namespace Barotrauma case "entitylist": return true; default: - return false; + return client.HasConsoleCommandPermission(command); } } diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs index aafe2d4ac..0acf9b10e 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs @@ -19,6 +19,7 @@ namespace Barotrauma.Networking private GUITickBox endVoteTickBox; private ClientPermissions permissions = ClientPermissions.None; + private List permittedConsoleCommands = new List(); private bool connected; @@ -596,14 +597,24 @@ namespace Barotrauma.Networking private void ReadPermissions(NetIncomingMessage inc) { + List permittedConsoleCommands = new List(); ClientPermissions newPermissions = (ClientPermissions)inc.ReadByte(); + if (newPermissions.HasFlag(ClientPermissions.ConsoleCommands)) + { + UInt16 consoleCommandCount = inc.ReadUInt16(); + for (int i = 0; i < consoleCommandCount; i++) + { + permittedConsoleCommands.Add(inc.ReadString()); + } + } + if (newPermissions != permissions) { - SetPermissions(newPermissions); + SetPermissions(newPermissions, permittedConsoleCommands); } } - private void SetPermissions(ClientPermissions newPermissions) + private void SetPermissions(ClientPermissions newPermissions, List permittedConsoleCommands) { if (newPermissions == permissions) return; GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData as string == "permissions"); @@ -623,6 +634,8 @@ namespace Barotrauma.Networking DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); msg += " - " + attributes[0].Description + "\n"; } + + //TODO: display permitted console commands } permissions = newPermissions; new GUIMessageBox("Permissions changed", msg).UserData = "permissions"; @@ -800,7 +813,7 @@ namespace Barotrauma.Networking gameStarted = inc.ReadBoolean(); bool allowSpectating = inc.ReadBoolean(); - SetPermissions((ClientPermissions)inc.ReadByte()); + ReadPermissions(inc); if (gameStarted) { @@ -1161,6 +1174,14 @@ namespace Barotrauma.Networking return permissions.HasFlag(permission); } + public bool HasConsoleCommandPermission(string command) + { + if (!permissions.HasFlag(ClientPermissions.ConsoleCommands)) return false; + + command = command.ToLowerInvariant(); + return permittedConsoleCommands.Any(c => c.ToLowerInvariant() == command); + } + public override void Draw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch) { base.Draw(spriteBatch); @@ -1351,6 +1372,22 @@ namespace Barotrauma.Networking client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); } + public void SendConsoleCommand(string command) + { + if (string.IsNullOrWhiteSpace(command)) + { + DebugConsole.ThrowError("Cannot send an empty console command to the server!\n" + Environment.StackTrace); + return; + } + + NetOutgoingMessage msg = client.CreateMessage(); + msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); + msg.Write((byte)ClientPermissions.ConsoleCommands); + msg.Write(command); + + client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); + } + /// /// Tell the server to select a submarine (permission required) /// diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs index 7c237cb9d..6c9f0105c 100644 --- a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs @@ -27,12 +27,14 @@ namespace Barotrauma static partial class DebugConsole { - class Command + public class Command { public readonly string[] names; public readonly string help; private Action onExecute; + private bool relayToServer; + public Command(string name, string help, Action onExecute) { names = name.Split('|'); @@ -40,7 +42,7 @@ namespace Barotrauma this.onExecute = onExecute; } - + public void Execute(string[] args) { onExecute(args); @@ -967,10 +969,19 @@ namespace Barotrauma } #if !DEBUG && CLIENT - if (GameMain.Client != null && !IsCommandPermitted(splitCommand[0].ToLowerInvariant(), GameMain.Client)) + if (GameMain.Client != null) { - ThrowError("You're not permitted to use the command \"" + splitCommand[0].ToLowerInvariant() + "\"!"); - return; + if (GameMain.Client.HasConsoleCommandPermission(splitCommand[0].ToLowerInvariant())) + { + GameMain.Client.SendConsoleCommand(command); + NewMessage("Server command: "+command, Color.White); + return; + } + if (!IsCommandPermitted(splitCommand[0].ToLowerInvariant(), GameMain.Client)) + { + ThrowError("You're not permitted to use the command \"" + splitCommand[0].ToLowerInvariant() + "\"!"); + return; + } } #endif @@ -987,7 +998,7 @@ namespace Barotrauma if (!commandFound) { - ThrowError("Command \""+splitCommand[0]+"\" not found."); + ThrowError("Command \"" + splitCommand[0] + "\" not found."); } } diff --git a/Barotrauma/BarotraumaShared/Source/Networking/Client.cs b/Barotrauma/BarotraumaShared/Source/Networking/Client.cs index 5a0caf60e..9497ddd10 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/Client.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/Client.cs @@ -21,7 +21,9 @@ namespace Barotrauma.Networking [Description("Select game mode")] SelectMode = 16, [Description("Manage campaign")] - ManageCampaign = 32 + ManageCampaign = 32, + [Description("Console commands")] + ConsoleCommands = 64 } class Client @@ -79,6 +81,7 @@ namespace Barotrauma.Networking public float DeleteDisconnectedTimer; public ClientPermissions Permissions = ClientPermissions.None; + public List PermittedConsoleCommands = new List(); public bool SpectateOnly; diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs index e62b9a155..5248ddf13 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs @@ -1928,6 +1928,18 @@ namespace Barotrauma.Networking var msg = server.CreateMessage(); msg.Write((byte)ServerPacketHeader.PERMISSIONS); msg.Write((byte)client.Permissions); + if (client.Permissions.HasFlag(ClientPermissions.ConsoleCommands)) + { + msg.Write((UInt16)client.PermittedConsoleCommands.Sum(c => c.names.Length)); + foreach (DebugConsole.Command command in client.PermittedConsoleCommands) + { + foreach (string commandName in command.names) + { + msg.Write(commandName); + } + } + } + server.SendMessage(msg, client.Connection, NetDeliveryMethod.ReliableUnordered); SaveClientPermissions(); diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs index 9034eb514..44558c72c 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs @@ -316,6 +316,8 @@ namespace Barotrauma.Networking public void LoadClientPermissions() { + //TODO: load console command permissions + if (!File.Exists(ClientPermissionsFile)) return; string[] lines; @@ -348,13 +350,15 @@ namespace Barotrauma.Networking public void SaveClientPermissions() { - GameServer.Log("Saving client permissions", ServerLog.MessageType.ServerMessage); + //TODO: save console command permissions + + Log("Saving client permissions", ServerLog.MessageType.ServerMessage); List lines = new List(); foreach (SavedClientPermission clientPermission in clientPermissions) { - lines.Add(clientPermission.Name + "|" + clientPermission.IP+"|"+clientPermission.Permissions.ToString()); + lines.Add(clientPermission.Name + "|" + clientPermission.IP + "|" + clientPermission.Permissions.ToString()); } try From 91a8e6d0c60152172530e37fe4d988e8b7775c55 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 6 Dec 2017 14:04:35 +0200 Subject: [PATCH 2/9] Saving & loading client console command permissions, the permissions are saved as an xml file --- .../BarotraumaShared/Source/DebugConsole.cs | 6 + .../Source/Networking/GameServer.cs | 3 +- .../Source/Networking/GameServerSettings.cs | 103 +++++++++++++++--- 3 files changed, 97 insertions(+), 15 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs index 6c9f0105c..4cc69c304 100644 --- a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs @@ -1135,6 +1135,12 @@ namespace Barotrauma return true; } + public static Command FindCommand(string commandName) + { + commandName = commandName.ToLowerInvariant(); + return commands.Find(c => c.names.Any(n => n.ToLowerInvariant() == commandName)); + } + public static void Log(string message) { diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs index 5248ddf13..c9130c9d8 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs @@ -1922,7 +1922,8 @@ namespace Barotrauma.Networking clientPermissions.Add(new SavedClientPermission( client.Name, client.Connection.RemoteEndPoint.Address.ToString(), - client.Permissions)); + client.Permissions, + client.PermittedConsoleCommands)); } var msg = server.CreateMessage(); diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs index 44558c72c..7279dea03 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs @@ -25,20 +25,22 @@ namespace Barotrauma.Networking { public readonly string IP; public readonly string Name; + public List PermittedCommands; public ClientPermissions Permissions; - public SavedClientPermission(string name, string ip, ClientPermissions permissions) + public SavedClientPermission(string name, string ip, ClientPermissions permissions, List permittedCommands) { this.Name = name; this.IP = ip; this.Permissions = permissions; + this.PermittedCommands = permittedCommands; } } public const string SettingsFile = "serversettings.xml"; - public static readonly string ClientPermissionsFile = "Data" + Path.DirectorySeparatorChar + "clientpermissions.txt"; + public static readonly string ClientPermissionsFile = "Data" + Path.DirectorySeparatorChar + "clientpermissions.xml"; public Dictionary SerializableProperties { @@ -316,14 +318,68 @@ namespace Barotrauma.Networking public void LoadClientPermissions() { - //TODO: load console command permissions + clientPermissions.Clear(); + + if (File.Exists("Data/clientpermissions.txt") && !File.Exists(ClientPermissionsFile)) + { + LoadClientPermissionsOld("Data/clientpermissions.txt"); + return; + } + + XDocument doc = XMLExtensions.TryLoadXml(ClientPermissionsFile); + foreach (XElement clientElement in doc.Root.Elements()) + { + string clientName = clientElement.GetAttributeString("name", ""); + string clientIP = clientElement.GetAttributeString("ip", ""); + if (string.IsNullOrWhiteSpace(clientName) || string.IsNullOrWhiteSpace(clientIP)) + { + DebugConsole.ThrowError("Error in " + ClientPermissionsFile + " - all clients must have a name and an IP address."); + continue; + } + + string permissionsStr = clientElement.GetAttributeString("permissions", ""); + ClientPermissions permissions; + if (!Enum.TryParse(permissionsStr, out permissions)) + { + DebugConsole.ThrowError("Error in " + ClientPermissionsFile + " - \"" + permissionsStr + "\" is not a valid client permission."); + continue; + } + + List permittedCommands = new List(); + if (permissions.HasFlag(ClientPermissions.ConsoleCommands)) + { + foreach (XElement commandElement in clientElement.Elements()) + { + if (commandElement.Name.ToString().ToLowerInvariant() != "command") continue; + + string commandName = commandElement.GetAttributeString("name", ""); + DebugConsole.Command command = DebugConsole.FindCommand(commandName); + if (command == null) + { + DebugConsole.ThrowError("Error in " + ClientPermissionsFile + " - \"" + commandName + "\" is not a valid console command."); + continue; + } + + permittedCommands.Add(command); + } + } + + new SavedClientPermission(clientName, clientIP, permissions, permittedCommands); + } + } + + /// + /// Method for loading old .txt client permission files to provide backwards compatibility + /// + /// + private void LoadClientPermissionsOld(string file) + { + if (!File.Exists(file)) return; - if (!File.Exists(ClientPermissionsFile)) return; - string[] lines; try { - lines = File.ReadAllLines(ClientPermissionsFile); + lines = File.ReadAllLines(file); } catch (Exception e) { @@ -332,38 +388,57 @@ namespace Barotrauma.Networking } clientPermissions.Clear(); + foreach (string line in lines) { string[] separatedLine = line.Split('|'); if (separatedLine.Length < 3) continue; - string name = String.Join("|", separatedLine.Take(separatedLine.Length - 2)); + string name = string.Join("|", separatedLine.Take(separatedLine.Length - 2)); string ip = separatedLine[separatedLine.Length - 2]; ClientPermissions permissions = ClientPermissions.None; - if (Enum.TryParse(separatedLine.Last(), out permissions)) + if (Enum.TryParse(separatedLine.Last(), out permissions)) { - clientPermissions.Add(new SavedClientPermission(name, ip, permissions)); + clientPermissions.Add(new SavedClientPermission(name, ip, permissions, new List())); } } } public void SaveClientPermissions() { - //TODO: save console command permissions - Log("Saving client permissions", ServerLog.MessageType.ServerMessage); - List lines = new List(); + XDocument doc = new XDocument(new XElement("ClientPermissions")); foreach (SavedClientPermission clientPermission in clientPermissions) { - lines.Add(clientPermission.Name + "|" + clientPermission.IP + "|" + clientPermission.Permissions.ToString()); + XElement clientElement = new XElement("Client", + new XAttribute("name", clientPermission.Name), + new XAttribute("ip", clientPermission.IP), + new XAttribute("permissions", clientPermission.Permissions.ToString())); + + if (clientPermission.Permissions.HasFlag(ClientPermissions.ConsoleCommands)) + { + foreach (DebugConsole.Command command in clientPermission.PermittedCommands) + { + clientElement.Add(new XElement("command", new XAttribute("name", command.names[0]))); + } + } + + doc.Root.Add(clientElement); } try { - File.WriteAllLines(ClientPermissionsFile, lines); + XmlWriterSettings settings = new XmlWriterSettings(); + settings.Indent = true; + settings.NewLineOnAttributes = true; + + using (var writer = XmlWriter.Create(ClientPermissionsFile, settings)) + { + doc.Save(writer); + } } catch (Exception e) { From 9bc0931be541f1c835b80bd79227e80e75d96a00 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 6 Dec 2017 19:52:57 +0200 Subject: [PATCH 3/9] Clients can execute permitted console commands server-side now. The console commands have three different actions: the default action, one that's executed client-side when a client uses it, and one that's executed server-side when a clients requests it. If the client-side action is omitted, the client relays the command to the server as-is. If the third action is omitted, the server executes the default action. --- .../BarotraumaClient/Source/DebugConsole.cs | 2 +- .../Source/Networking/GameClient.cs | 17 +- .../BarotraumaServer/Source/DebugConsole.cs | 2 +- .../BarotraumaShared/Source/DebugConsole.cs | 663 +++++++++++++----- .../Source/Networking/Client.cs | 3 +- .../Source/Networking/GameServer.cs | 26 +- .../Source/Networking/GameServerLogin.cs | 4 +- .../Source/Networking/GameServerSettings.cs | 9 +- 8 files changed, 551 insertions(+), 175 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs index 626969eb5..00b0b4bed 100644 --- a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs @@ -105,7 +105,7 @@ namespace Barotrauma if (PlayerInput.KeyHit(Keys.Enter)) { - ExecuteCommand(textBox.Text, game); + ExecuteCommand(textBox.Text); textBox.Text = ""; } } diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs index 0acf9b10e..00b0f248d 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs @@ -608,15 +608,17 @@ namespace Barotrauma.Networking } } - if (newPermissions != permissions) - { - SetPermissions(newPermissions, permittedConsoleCommands); - } + SetPermissions(newPermissions, permittedConsoleCommands); } private void SetPermissions(ClientPermissions newPermissions, List permittedConsoleCommands) { - if (newPermissions == permissions) return; + if (!(this.permittedConsoleCommands.Any(c => !permittedConsoleCommands.Contains(c)) || + permittedConsoleCommands.Any(c => !this.permittedConsoleCommands.Contains(c)))) + { + if (newPermissions == permissions) return; + } + GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData as string == "permissions"); string msg = ""; @@ -637,7 +639,9 @@ namespace Barotrauma.Networking //TODO: display permitted console commands } + permissions = newPermissions; + this.permittedConsoleCommands = new List(permittedConsoleCommands); new GUIMessageBox("Permissions changed", msg).UserData = "permissions"; GameMain.NetLobbyScreen.SubList.Enabled = Voting.AllowSubVoting || HasPermission(ClientPermissions.SelectSub); @@ -1384,6 +1388,9 @@ namespace Barotrauma.Networking msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); msg.Write((byte)ClientPermissions.ConsoleCommands); msg.Write(command); + Vector2 cursorWorldPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition); + msg.Write(cursorWorldPos.X); + msg.Write(cursorWorldPos.Y); client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); } diff --git a/Barotrauma/BarotraumaServer/Source/DebugConsole.cs b/Barotrauma/BarotraumaServer/Source/DebugConsole.cs index 948deee36..bdbf0b014 100644 --- a/Barotrauma/BarotraumaServer/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaServer/Source/DebugConsole.cs @@ -16,7 +16,7 @@ namespace Barotrauma { while (QueuedCommands.Count>0) { - ExecuteCommand(QueuedCommands[0], GameMain.Instance); + ExecuteCommand(QueuedCommands[0]); QueuedCommands.RemoveAt(0); } } diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs index 4cc69c304..ba7b3a779 100644 --- a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs @@ -31,22 +31,73 @@ namespace Barotrauma { public readonly string[] names; public readonly string help; + private Action onExecute; - private bool relayToServer; + /// + /// Executed when a client uses the command. If not set, the command is relayed to the server as-is. + /// + private Action onClientExecute; + /// + /// Executed server-side when a client attempts to use the command. + /// + private Action onClientRequestExecute; + + public bool RelayToServer + { + get { return onClientExecute == null; } + } + + /// The name of the command. Use | to give multiple names/aliases to the command. + /// The text displayed when using the help command. + /// The default action when executing the command. + /// The action when a client attempts to execute the command. If null, the command is relayed to the server as-is. + /// The server-side action when a client requests executing the command. If null, the default action is executed. + public Command(string name, string help, Action onExecute, Action onClientExecute, Action onClientRequestExecute) + { + names = name.Split('|'); + this.help = help; + + this.onExecute = onExecute; + this.onClientExecute = onClientExecute; + this.onClientRequestExecute = onClientRequestExecute; + } + + + /// + /// Use this constructor to create a command that executes the same action regardless of whether it's executed by a client or the server. + /// public Command(string name, string help, Action onExecute) { names = name.Split('|'); this.help = help; this.onExecute = onExecute; + this.onClientExecute = onExecute; } - + public void Execute(string[] args) { onExecute(args); } + + public void ClientExecute(string[] args) + { + onClientExecute(args); + } + + public void ServerExecuteOnClientRequest(Client client, Vector2 cursorWorldPos, string[] args) + { + if (onClientRequestExecute == null) + { + onExecute(args); + } + else + { + onClientRequestExecute(client, cursorWorldPos, args); + } + } } const int MaxMessages = 200; @@ -102,6 +153,15 @@ namespace Barotrauma NewMessage("- " + c.ID.ToString() + ": " + c.Name + ", " + c.Connection.RemoteEndPoint.Address.ToString(), Color.Cyan); } NewMessage("***************", Color.Cyan); + }, null, + (Client client, Vector2 cursorWorldPos, string[] args) => + { + GameMain.Server.SendChatMessage("***************", client); + foreach (Client c in GameMain.Server.ConnectedClients) + { + GameMain.Server.SendChatMessage("- " + c.ID.ToString() + ": " + c.Name + ", " + c.Connection.RemoteEndPoint.Address.ToString(), client); + } + GameMain.Server.SendChatMessage("***************", client); })); @@ -112,145 +172,42 @@ namespace Barotrauma commands.Add(new Command("spawn|spawncharacter", "spawn [creaturename] [near/inside/outside]: Spawn a creature at a random spawnpoint (use the second parameter to only select spawnpoints near/inside/outside the submarine).", (string[] args) => { - if (args.Length == 0) return; - - Character spawnedCharacter = null; - - Vector2 spawnPosition = Vector2.Zero; - WayPoint spawnPoint = null; - - if (args.Length > 1) + string errorMsg; + SpawnCharacter(args, GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), out errorMsg); + if (!string.IsNullOrWhiteSpace(errorMsg)) { - switch (args[1].ToLowerInvariant()) - { - case "inside": - spawnPoint = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub); - break; - case "outside": - spawnPoint = WayPoint.GetRandom(SpawnType.Enemy); - break; - case "near": - case "close": - float closestDist = -1.0f; - foreach (WayPoint wp in WayPoint.WayPointList) - { - if (wp.Submarine != null) continue; - - //don't spawn inside hulls - if (Hull.FindHull(wp.WorldPosition, null) != null) continue; - - float dist = Vector2.Distance(wp.WorldPosition, GameMain.GameScreen.Cam.WorldViewCenter); - - if (closestDist < 0.0f || dist < closestDist) - { - spawnPoint = wp; - closestDist = dist; - } - } - break; - case "cursor": - spawnPosition = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition); - break; - default: - spawnPoint = WayPoint.GetRandom(args[0].ToLowerInvariant() == "human" ? SpawnType.Human : SpawnType.Enemy); - break; - } + ThrowError(errorMsg); } - else + }, + null, + (Client client, Vector2 cursorPos, string[] args) => + { + string errorMsg; + SpawnCharacter(args, cursorPos, out errorMsg); + if (!string.IsNullOrWhiteSpace(errorMsg)) { - spawnPoint = WayPoint.GetRandom(args[0].ToLowerInvariant() == "human" ? SpawnType.Human : SpawnType.Enemy); - } - - if (string.IsNullOrWhiteSpace(args[0])) return; - - if (spawnPoint != null) spawnPosition = spawnPoint.WorldPosition; - - if (args[0].ToLowerInvariant() == "human") - { - spawnedCharacter = Character.Create(Character.HumanConfigFile, spawnPosition); - -#if CLIENT - if (GameMain.GameSession != null) - { - SinglePlayerCampaign mode = GameMain.GameSession.GameMode as SinglePlayerCampaign; - if (mode != null) - { - Character.Controlled = spawnedCharacter; - GameMain.GameSession.CrewManager.AddCharacter(Character.Controlled); - GameMain.GameSession.CrewManager.SelectCharacter(null, Character.Controlled); - } - } -#endif - } - else - { - List characterFiles = GameMain.Config.SelectedContentPackage.GetFilesOfType(ContentType.Character); - - foreach (string characterFile in characterFiles) - { - if (Path.GetFileNameWithoutExtension(characterFile).ToLowerInvariant() == args[0].ToLowerInvariant()) - { - Character.Create(characterFile, spawnPosition); - return; - } - } - - ThrowError("No character matching the name \"" + args[0] + "\" found in the selected content package."); - - //attempt to open the config from the default path (the file may still be present even if it isn't included in the content package) - string configPath = "Content/Characters/" - + args[0].First().ToString().ToUpper() + args[0].Substring(1) - + "/" + args[0].ToLower() + ".xml"; - Character.Create(configPath, spawnPosition); + ThrowError(errorMsg); } })); - commands.Add(new Command("spawnitem", "spawnitem [itemname] [cursor/inventory]: Spawn an item at the position of the cursor, in the inventory of the controlled character or at a random spawnpoint if the last parameter is omitted.", (string[] args) => + commands.Add(new Command("spawnitem", "spawnitem [itemname] [cursor/inventory]: Spawn an item at the position of the cursor, in the inventory of the controlled character or at a random spawnpoint if the last parameter is omitted.", + (string[] args) => { - if (args.Length < 1) return; - - Vector2? spawnPos = null; - Inventory spawnInventory = null; - - int extraParams = 0; - switch (args.Last()) + string errorMsg; + SpawnItem(args, GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), out errorMsg); + if (!string.IsNullOrWhiteSpace(errorMsg)) { - case "cursor": - extraParams = 1; - spawnPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition); - break; - case "inventory": - extraParams = 1; - spawnInventory = Character.Controlled == null ? null : Character.Controlled.Inventory; - break; - default: - extraParams = 0; - break; + ThrowError(errorMsg); } - - string itemName = string.Join(" ", args.Take(args.Length - extraParams)).ToLowerInvariant(); - - var itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab; - if (itemPrefab == null) + }, + null, + (Client client, Vector2 cursorWorldPos, string[] args) => + { + string errorMsg; + SpawnItem(args, cursorWorldPos, out errorMsg); + if (!string.IsNullOrWhiteSpace(errorMsg)) { - ThrowError("Item \"" + itemName + "\" not found!"); - return; - } - - if (spawnPos == null && spawnInventory == null) - { - var wp = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub); - spawnPos = wp == null ? Vector2.Zero : wp.WorldPosition; - } - - if (spawnPos != null) - { - Entity.Spawner.AddToSpawnQueue(itemPrefab, (Vector2)spawnPos); - - } - else if (spawnInventory != null) - { - Entity.Spawner.AddToSpawnQueue(itemPrefab, spawnInventory); + ThrowError(errorMsg); } })); @@ -258,12 +215,26 @@ namespace Barotrauma { HumanAIController.DisableCrewAI = true; NewMessage("Crew AI disabled", Color.White); + }, + null, + (Client client, Vector2 cursorWorldPos, string[] args) => + { + HumanAIController.DisableCrewAI = true; + NewMessage("Crew AI disabled by \"" + client.Name + "\"", Color.White); + GameMain.Server.SendChatMessage("Crew AI disabled", client); })); commands.Add(new Command("enablecrewai", "enablecrewai: Enable the AI of the NPCs in the crew.", (string[] args) => { HumanAIController.DisableCrewAI = false; NewMessage("Crew AI enabled", Color.White); + }, + null, + (Client client, Vector2 cursorWorldPos, string[] args) => + { + HumanAIController.DisableCrewAI = false; + NewMessage("Crew AI enabled by \"" + client.Name + "\"", Color.White); + GameMain.Server.SendChatMessage("Crew AI enabled", client); })); commands.Add(new Command("autorestart", "autorestart [true/false]: Enable or disable round auto-restart.", (string[] args) => @@ -289,7 +260,7 @@ namespace Barotrauma GameMain.NetLobbyScreen.LastUpdateID++; } NewMessage(GameMain.Server.AutoRestart ? "Automatic restart enabled." : "Automatic restart disabled.", Color.White); - })); + }, null, null)); commands.Add(new Command("autorestartinterval", "autorestartinterval [seconds]: Set how long the server waits between rounds before automatically starting a new one. If set to 0, autorestart is disabled.", (string[] args) => { @@ -317,7 +288,7 @@ namespace Barotrauma GameMain.NetLobbyScreen.LastUpdateID++; } } - })); + }, null, null)); commands.Add(new Command("autorestarttimer", "autorestarttimer [seconds]: Set the current autorestart countdown to the specified value.", (string[] args) => { @@ -346,10 +317,12 @@ namespace Barotrauma GameMain.NetLobbyScreen.LastUpdateID++; } } - })); + }, null, null)); commands.Add(new Command("giveperm", "giveperm [id]: Grants administrative permissions to the player with the specified client ID.", (string[] args) => { + //todo: allow client usage + if (GameMain.Server == null) return; if (args.Length < 1) return; @@ -367,20 +340,23 @@ namespace Barotrauma ClientPermissions permission = ClientPermissions.None; if (perm.ToLower() == "all") { - permission = ClientPermissions.EndRound | ClientPermissions.Kick | ClientPermissions.Ban | ClientPermissions.SelectSub | ClientPermissions.SelectMode | ClientPermissions.ManageCampaign; + permission = ClientPermissions.EndRound | ClientPermissions.Kick | ClientPermissions.Ban | + ClientPermissions.SelectSub | ClientPermissions.SelectMode | ClientPermissions.ManageCampaign | ClientPermissions.ConsoleCommands; } else { - Enum.TryParse(perm, out permission); + Enum.TryParse(perm, out permission); } - client.SetPermissions(client.Permissions | permission); + client.GivePermission(permission); GameMain.Server.UpdateClientPermissions(client); - DebugConsole.NewMessage("Granted "+perm+" permissions to "+client.Name+".",Color.White); + NewMessage("Granted " + perm + " permissions to " + client.Name + ".", Color.White); }); })); commands.Add(new Command("revokeperm", "revokeperm [id]: Revokes administrative permissions to the player with the specified client ID.", (string[] args) => { + //todo: allow client usage + if (GameMain.Server == null) return; if (args.Length < 1) return; @@ -402,11 +378,11 @@ namespace Barotrauma } else { - Enum.TryParse(perm, out permission); + Enum.TryParse(perm, out permission); } - client.SetPermissions(client.Permissions & ~permission); + client.RemovePermission(permission); GameMain.Server.UpdateClientPermissions(client); - DebugConsole.NewMessage("Revoked " + perm + " permissions from " + client.Name + ".", Color.White); + NewMessage("Revoked " + perm + " permissions from " + client.Name + ".", Color.White); }); })); @@ -521,7 +497,7 @@ namespace Barotrauma } banDuration = parsedBanDuration; } - + var client = GameMain.Server.ConnectedClients.Find(c => c.Connection.RemoteEndPoint.Address.ToString() == args[0]); if (client == null) { @@ -556,6 +532,28 @@ namespace Barotrauma tpCharacter.Submarine = null; tpCharacter.AnimController.SetPosition(ConvertUnits.ToSimUnits(cam.ScreenToWorld(PlayerInput.MousePosition))); tpCharacter.AnimController.FindHull(cam.ScreenToWorld(PlayerInput.MousePosition), true); + }, + null, + (Client client, Vector2 cursorWorldPos, string[] args) => + { + Character tpCharacter = null; + + if (args.Length == 0) + { + tpCharacter = client.Character; + } + else + { + tpCharacter = FindMatchingCharacter(args, false); + } + + if (tpCharacter == null) return; + + var cam = GameMain.GameScreen.Cam; + tpCharacter.AnimController.CurrentHull = null; + tpCharacter.Submarine = null; + tpCharacter.AnimController.SetPosition(ConvertUnits.ToSimUnits(cursorWorldPos)); + tpCharacter.AnimController.FindHull(cursorWorldPos, true); })); commands.Add(new Command("godmode", "godmode: Toggle submarine godmode. Makes the main submarine invulnerable to damage.", (string[] args) => @@ -564,17 +562,26 @@ namespace Barotrauma Submarine.MainSub.GodMode = !Submarine.MainSub.GodMode; NewMessage(Submarine.MainSub.GodMode ? "Godmode on" : "Godmode off", Color.White); + }, + null, + (Client client, Vector2 cursorWorldPos, string[] args) => + { + if (Submarine.MainSub == null) return; + + Submarine.MainSub.GodMode = !Submarine.MainSub.GodMode; + NewMessage((Submarine.MainSub.GodMode ? "Godmode turned on by \"" : "Godmode off by \"") + client.Name+"\"", Color.White); + GameMain.Server.SendChatMessage(Submarine.MainSub.GodMode ? "Godmode on" : "Godmode off", client); })); commands.Add(new Command("lockx", "lockx: Lock horizontal movement of the main submarine.", (string[] args) => { Submarine.LockX = !Submarine.LockX; - })); + }, null, null)); commands.Add(new Command("locky", "loxky: Lock vertical movement of the main submarine.", (string[] args) => { Submarine.LockY = !Submarine.LockY; - })); + }, null, null)); commands.Add(new Command("dumpids", "", (string[] args) => { @@ -601,6 +608,27 @@ namespace Barotrauma healedCharacter = FindMatchingCharacter(args); } + if (healedCharacter != null) + { + healedCharacter.AddDamage(CauseOfDeath.Damage, -healedCharacter.MaxHealth, null); + healedCharacter.Oxygen = 100.0f; + healedCharacter.Bleeding = 0.0f; + healedCharacter.SetStun(0.0f, true); + } + }, + null, + (Client client, Vector2 cursorWorldPos, string[] args) => + { + Character healedCharacter = null; + if (args.Length == 0) + { + healedCharacter = client.Character; + } + else + { + healedCharacter = FindMatchingCharacter(args); + } + if (healedCharacter != null) { healedCharacter.AddDamage(CauseOfDeath.Damage, -healedCharacter.MaxHealth, null); @@ -636,11 +664,44 @@ namespace Barotrauma break; } } + }, + null, + (Client client, Vector2 cursorWorldPos, string[] args) => + { + Character revivedCharacter = null; + if (args.Length == 0) + { + revivedCharacter = client.Character; + } + else + { + revivedCharacter = FindMatchingCharacter(args); + } + + if (revivedCharacter == null) return; + + revivedCharacter.Revive(false); + if (GameMain.Server != null) + { + foreach (Client c in GameMain.Server.ConnectedClients) + { + if (c.Character != revivedCharacter) continue; + + //clients stop controlling the character when it dies, force control back + GameMain.Server.SetClientCharacter(c, revivedCharacter); + break; + } + } })); commands.Add(new Command("freeze", "", (string[] args) => { if (Character.Controlled != null) Character.Controlled.AnimController.Frozen = !Character.Controlled.AnimController.Frozen; + }, + null, + (Client client, Vector2 cursorWorldPos, string[] args) => + { + if (client.Character != null) client.Character.AnimController.Frozen = !client.Character.AnimController.Frozen; })); commands.Add(new Command("freecamera|freecam", "freecam: Detach the camera from the controlled character.", (string[] args) => @@ -676,6 +737,17 @@ namespace Barotrauma if (args.Length > 2) float.TryParse(args[2], out damage); if (args.Length > 3) float.TryParse(args[3], out structureDamage); new Explosion(range, force, damage, structureDamage).Explode(explosionPos); + }, + null, + (Client client, Vector2 cursorWorldPos, string[] args) => + { + Vector2 explosionPos = cursorWorldPos; + float range = 500, force = 10, damage = 50, structureDamage = 10; + if (args.Length > 0) float.TryParse(args[0], out range); + if (args.Length > 1) float.TryParse(args[1], out force); + if (args.Length > 2) float.TryParse(args[2], out damage); + if (args.Length > 3) float.TryParse(args[3], out structureDamage); + new Explosion(range, force, damage, structureDamage).Explode(explosionPos); })); commands.Add(new Command("fixitems", "fixitems: Repairs all items and restores them to full condition.", (string[] args) => @@ -684,7 +756,7 @@ namespace Barotrauma { it.Condition = it.Prefab.Health; } - })); + }, null, null)); commands.Add(new Command("fixhulls|fixwalls", "fixwalls/fixhulls: Fixes all walls.", (string[] args) => { @@ -695,7 +767,7 @@ namespace Barotrauma w.AddDamage(i, -100000.0f); } } - })); + }, null, null)); commands.Add(new Command("power", "power [temperature]: Immediately sets the temperature of the nuclear reactor to the specified value.", (string[] args) => { @@ -714,7 +786,7 @@ namespace Barotrauma { reactorItem.CreateServerEvent(reactor); } - })); + }, null, null)); commands.Add(new Command("oxygen|air", "oxygen/air: Replenishes the oxygen levels in every room to 100%.", (string[] args) => { @@ -722,7 +794,7 @@ namespace Barotrauma { hull.OxygenPercentage = 100.0f; } - })); + }, null, null)); commands.Add(new Command("killmonsters", "killmonsters: Immediately kills all AI-controlled enemies in the level.", (string[] args) => { @@ -731,7 +803,7 @@ namespace Barotrauma if (!(c.AIController is EnemyAIController)) continue; c.AddDamage(CauseOfDeath.Damage, 10000.0f, null); } - })); + }, null, null)); commands.Add(new Command("netstats", "netstats: Toggles the visibility of the network statistics UI.", (string[] args) => { @@ -744,7 +816,6 @@ namespace Barotrauma if (GameMain.Server == null) return; int separatorIndex = Array.IndexOf(args, ";"); - if (separatorIndex == -1 || args.Length < 3) { ThrowError("Invalid parameters. The command should be formatted as \"setclientcharacter [client] ; [character]\""); @@ -753,8 +824,7 @@ namespace Barotrauma string[] argsLeft = args.Take(separatorIndex).ToArray(); string[] argsRight = args.Skip(separatorIndex + 1).ToArray(); - - string clientName = String.Join(" ", argsLeft); + string clientName = string.Join(" ", argsLeft); var client = GameMain.Server.ConnectedClients.Find(c => c.Name == clientName); if (client == null) @@ -762,6 +832,29 @@ namespace Barotrauma ThrowError("Client \"" + clientName + "\" not found."); } + var character = FindMatchingCharacter(argsRight, false); + GameMain.Server.SetClientCharacter(client, character); + }, + null, + (Client senderClient, Vector2 cursorWorldPos, string[] args) => + { + int separatorIndex = Array.IndexOf(args, ";"); + if (separatorIndex == -1 || args.Length < 3) + { + GameMain.Server.SendChatMessage("Invalid parameters. The command should be formatted as \"setclientcharacter [client] ; [character]\"", senderClient); + return; + } + + string[] argsLeft = args.Take(separatorIndex).ToArray(); + string[] argsRight = args.Skip(separatorIndex + 1).ToArray(); + string clientName = string.Join(" ", argsLeft); + + var client = GameMain.Server.ConnectedClients.Find(c => c.Name == clientName); + if (client == null) + { + GameMain.Server.SendChatMessage("Client \"" + clientName + "\" not found.", senderClient); + } + var character = FindMatchingCharacter(argsRight, false); GameMain.Server.SetClientCharacter(client, character); })); @@ -822,6 +915,69 @@ namespace Barotrauma campaign.Map.SelectLocation(location); NewMessage(location.Name + " selected.", Color.White); } + }, + (string[] args) => + { +#if CLIENT + var campaign = GameMain.GameSession?.GameMode as CampaignMode; + if (campaign == null) + { + ThrowError("No campaign active!"); + return; + } + + if (args.Length == 0) + { + int i = 0; + foreach (LocationConnection connection in campaign.Map.CurrentLocation.Connections) + { + NewMessage(" " + i + ". " + connection.OtherLocation(campaign.Map.CurrentLocation).Name, Color.White); + i++; + } + ShowQuestionPrompt("Select a destination (0 - " + (campaign.Map.CurrentLocation.Connections.Count - 1) + "):", (string selectedDestination) => + { + int destinationIndex = -1; + if (!int.TryParse(selectedDestination, out destinationIndex)) return; + if (destinationIndex < 0 || destinationIndex >= campaign.Map.CurrentLocation.Connections.Count) + { + NewMessage("Index out of bounds!", Color.Red); + return; + } + GameMain.Client.SendConsoleCommand("campaigndestination " + destinationIndex); + }); + } + else + { + int destinationIndex = -1; + if (!int.TryParse(args[0], out destinationIndex)) return; + if (destinationIndex < 0 || destinationIndex >= campaign.Map.CurrentLocation.Connections.Count) + { + NewMessage("Index out of bounds!", Color.Red); + return; + } + GameMain.Client.SendConsoleCommand("campaigndestination " + destinationIndex); + } +#endif + }, + (Client senderClient, Vector2 cursorWorldPos, string[] args) => + { + var campaign = GameMain.GameSession?.GameMode as CampaignMode; + if (campaign == null) + { + GameMain.Server.SendChatMessage("No campaign active!", senderClient); + return; + } + + int destinationIndex = -1; + if (args.Length < 1 || !int.TryParse(args[0], out destinationIndex)) return; + if (destinationIndex < 0 || destinationIndex >= campaign.Map.CurrentLocation.Connections.Count) + { + GameMain.Server.SendChatMessage("Index out of bounds!", senderClient); + return; + } + Location location = campaign.Map.CurrentLocation.Connections[destinationIndex].OtherLocation(campaign.Map.CurrentLocation); + campaign.Map.SelectLocation(location); + GameMain.Server.SendChatMessage(location.Name + " selected.", senderClient); })); #if DEBUG @@ -856,7 +1012,7 @@ namespace Barotrauma { GameMain.Server.CreateEntityEvent(wall); } - })); + }, null, null)); #endif InitProjectSpecific(); @@ -944,7 +1100,7 @@ namespace Barotrauma return Messages[selectedIndex].Text; } - public static void ExecuteCommand(string command, GameMain game) + public static void ExecuteCommand(string command) { if (activeQuestionCallback != null) { @@ -967,14 +1123,25 @@ namespace Barotrauma { NewMessage(command, Color.White); } - + #if !DEBUG && CLIENT if (GameMain.Client != null) { if (GameMain.Client.HasConsoleCommandPermission(splitCommand[0].ToLowerInvariant())) { - GameMain.Client.SendConsoleCommand(command); - NewMessage("Server command: "+command, Color.White); + Command matchingCommand = commands.Find(c => c.names.Contains(splitCommand[0].ToLowerInvariant())); + + //if the command is not defined client-side, we'll relay it anyway because it may be a custom command at the server's side + if (matchingCommand == null || matchingCommand.RelayToServer) + { + GameMain.Client.SendConsoleCommand(command); + } + else + { + matchingCommand.ClientExecute(splitCommand.Skip(1).ToArray()); + } + + NewMessage("Server command: " + command, Color.White); return; } if (!IsCommandPermitted(splitCommand[0].ToLowerInvariant(), GameMain.Client)) @@ -1001,7 +1168,41 @@ namespace Barotrauma ThrowError("Command \"" + splitCommand[0] + "\" not found."); } } - + + public static void ExecuteClientCommand(Client client, Vector2 cursorWorldPos, string command) + { + if (GameMain.Server == null) return; + if (string.IsNullOrWhiteSpace(command)) return; + if (!client.HasPermission(ClientPermissions.ConsoleCommands)) + { + GameMain.Server.SendChatMessage("You are not permitted to use console commands!", client); + return; + } + + string[] splitCommand = SplitCommand(command); + Command matchingCommand = commands.Find(c => c.names.Contains(splitCommand[0].ToLowerInvariant())); + if (matchingCommand != null && !client.PermittedConsoleCommands.Contains(matchingCommand)) + { + GameMain.Server.SendChatMessage("You are not permitted to use the command\"" + matchingCommand.names[0] + "\"!", client); + return; + } + else if (matchingCommand == null) + { + GameMain.Server.SendChatMessage("Command \"" + splitCommand[0] + "\" not found.", client); + return; + } + + try + { + matchingCommand.ServerExecuteOnClientRequest(client, cursorWorldPos, splitCommand.Skip(1).ToArray()); + } + catch (Exception e) + { + ThrowError("Executing the command \"" + matchingCommand.names[0]+"\" by request from \""+client.Name+"\" failed.", e); + } + } + + private static Character FindMatchingCharacter(string[] args, bool ignoreRemotePlayers = false) { if (args.Length == 0) return null; @@ -1049,6 +1250,152 @@ namespace Barotrauma return null; } + private static void SpawnCharacter(string[] args, Vector2 cursorWorldPos, out string errorMsg) + { + errorMsg = ""; + if (args.Length == 0) return; + + Character spawnedCharacter = null; + + Vector2 spawnPosition = Vector2.Zero; + WayPoint spawnPoint = null; + + if (args.Length > 1) + { + switch (args[1].ToLowerInvariant()) + { + case "inside": + spawnPoint = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub); + break; + case "outside": + spawnPoint = WayPoint.GetRandom(SpawnType.Enemy); + break; + case "near": + case "close": + float closestDist = -1.0f; + foreach (WayPoint wp in WayPoint.WayPointList) + { + if (wp.Submarine != null) continue; + + //don't spawn inside hulls + if (Hull.FindHull(wp.WorldPosition, null) != null) continue; + + float dist = Vector2.Distance(wp.WorldPosition, GameMain.GameScreen.Cam.WorldViewCenter); + + if (closestDist < 0.0f || dist < closestDist) + { + spawnPoint = wp; + closestDist = dist; + } + } + break; + case "cursor": + spawnPosition = cursorWorldPos; + break; + default: + spawnPoint = WayPoint.GetRandom(args[0].ToLowerInvariant() == "human" ? SpawnType.Human : SpawnType.Enemy); + break; + } + } + else + { + spawnPoint = WayPoint.GetRandom(args[0].ToLowerInvariant() == "human" ? SpawnType.Human : SpawnType.Enemy); + } + + if (string.IsNullOrWhiteSpace(args[0])) return; + + if (spawnPoint != null) spawnPosition = spawnPoint.WorldPosition; + + if (args[0].ToLowerInvariant() == "human") + { + spawnedCharacter = Character.Create(Character.HumanConfigFile, spawnPosition); + +#if CLIENT + if (GameMain.GameSession != null) + { + SinglePlayerCampaign mode = GameMain.GameSession.GameMode as SinglePlayerCampaign; + if (mode != null) + { + Character.Controlled = spawnedCharacter; + GameMain.GameSession.CrewManager.AddCharacter(Character.Controlled); + GameMain.GameSession.CrewManager.SelectCharacter(null, Character.Controlled); + } + } +#endif + } + else + { + List characterFiles = GameMain.Config.SelectedContentPackage.GetFilesOfType(ContentType.Character); + + foreach (string characterFile in characterFiles) + { + if (Path.GetFileNameWithoutExtension(characterFile).ToLowerInvariant() == args[0].ToLowerInvariant()) + { + Character.Create(characterFile, spawnPosition); + return; + } + } + + errorMsg = "No character matching the name \"" + args[0] + "\" found in the selected content package."; + + //attempt to open the config from the default path (the file may still be present even if it isn't included in the content package) + string configPath = "Content/Characters/" + + args[0].First().ToString().ToUpper() + args[0].Substring(1) + + "/" + args[0].ToLower() + ".xml"; + Character.Create(configPath, spawnPosition); + } + } + + private static void SpawnItem(string[] args, Vector2 cursorPos, out string errorMsg) + { + errorMsg = ""; + if (args.Length < 1) return; + + Vector2? spawnPos = null; + Inventory spawnInventory = null; + + int extraParams = 0; + switch (args.Last()) + { + case "cursor": + extraParams = 1; + spawnPos = cursorPos; + break; + case "inventory": + extraParams = 1; + spawnInventory = Character.Controlled == null ? null : Character.Controlled.Inventory; + break; + default: + extraParams = 0; + break; + } + + string itemName = string.Join(" ", args.Take(args.Length - extraParams)).ToLowerInvariant(); + + var itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab; + if (itemPrefab == null) + { + errorMsg = "Item \"" + itemName + "\" not found!"; + return; + } + + if (spawnPos == null && spawnInventory == null) + { + var wp = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub); + spawnPos = wp == null ? Vector2.Zero : wp.WorldPosition; + } + + if (spawnPos != null) + { + Entity.Spawner.AddToSpawnQueue(itemPrefab, (Vector2)spawnPos); + + } + else if (spawnInventory != null) + { + Entity.Spawner.AddToSpawnQueue(itemPrefab, spawnInventory); + } + } + public static void NewMessage(string msg, Color color) { if (string.IsNullOrEmpty((msg))) return; diff --git a/Barotrauma/BarotraumaShared/Source/Networking/Client.cs b/Barotrauma/BarotraumaShared/Source/Networking/Client.cs index 9497ddd10..b646d5ea6 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/Client.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/Client.cs @@ -157,9 +157,10 @@ namespace Barotrauma.Networking return rName; } - public void SetPermissions(ClientPermissions permissions) + public void SetPermissions(ClientPermissions permissions, List permittedConsoleCommands) { this.Permissions = permissions; + this.PermittedConsoleCommands = permittedConsoleCommands; } public void GivePermission(ClientPermissions permission) diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs index c9130c9d8..d84994295 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs @@ -795,6 +795,11 @@ namespace Barotrauma.Networking campaign.ServerRead(inc, sender); } break; + case ClientPermissions.ConsoleCommands: + string consoleCommand = inc.ReadString(); + Vector2 clientCursorPos = new Vector2(inc.ReadSingle(), inc.ReadSingle()); + DebugConsole.ExecuteClientCommand(sender, clientCursorPos, consoleCommand); + break; } inc.ReadPadBits(); @@ -853,7 +858,7 @@ namespace Barotrauma.Networking outmsg.Write(GameStarted); outmsg.Write(AllowSpectating); - outmsg.Write((byte)c.Permissions); + WritePermissions(outmsg, c); } private void ClientWriteIngame(Client c) @@ -1622,6 +1627,12 @@ namespace Barotrauma.Networking } } + public void SendChatMessage(string txt, Client recipient) + { + ChatMessage msg = ChatMessage.Create("", txt, ChatMessageType.Server, null); + SendChatMessage(msg, recipient); + } + public void SendChatMessage(ChatMessage msg, Client recipient) { msg.NetStateID = recipient.ChatMsgQueue.Count > 0 ? @@ -1928,6 +1939,15 @@ namespace Barotrauma.Networking var msg = server.CreateMessage(); msg.Write((byte)ServerPacketHeader.PERMISSIONS); + WritePermissions(msg, client); + + server.SendMessage(msg, client.Connection, NetDeliveryMethod.ReliableUnordered); + + SaveClientPermissions(); + } + + private void WritePermissions(NetBuffer msg, Client client) + { msg.Write((byte)client.Permissions); if (client.Permissions.HasFlag(ClientPermissions.ConsoleCommands)) { @@ -1940,10 +1960,6 @@ namespace Barotrauma.Networking } } } - - server.SendMessage(msg, client.Connection, NetDeliveryMethod.ReliableUnordered); - - SaveClientPermissions(); } public void SetClientCharacter(Client client, Character newCharacter) diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServerLogin.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServerLogin.cs index 3208348b0..f0b4d65e4 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServerLogin.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServerLogin.cs @@ -218,11 +218,11 @@ namespace Barotrauma.Networking var savedPermissions = clientPermissions.Find(cp => cp.IP == newClient.Connection.RemoteEndPoint.Address.ToString()); if (savedPermissions != null) { - newClient.SetPermissions(savedPermissions.Permissions); + newClient.SetPermissions(savedPermissions.Permissions, savedPermissions.PermittedCommands); } else { - newClient.SetPermissions(ClientPermissions.None); + newClient.SetPermissions(ClientPermissions.None, new List()); } } diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs index 7279dea03..dc1d78385 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs @@ -364,14 +364,13 @@ namespace Barotrauma.Networking } } - new SavedClientPermission(clientName, clientIP, permissions, permittedCommands); + clientPermissions.Add(new SavedClientPermission(clientName, clientIP, permissions, permittedCommands)); } } /// /// Method for loading old .txt client permission files to provide backwards compatibility /// - /// private void LoadClientPermissionsOld(string file) { if (!File.Exists(file)) return; @@ -407,6 +406,12 @@ namespace Barotrauma.Networking public void SaveClientPermissions() { + //delete old client permission file + if (File.Exists("Data/clientpermissions.txt")) + { + File.Delete("Data/clientpermissions.txt"); + } + Log("Saving client permissions", ServerLog.MessageType.ServerMessage); XDocument doc = new XDocument(new XElement("ClientPermissions")); From 9599137e83f90025b042f7a35dcd6de7f4413864 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 7 Dec 2017 18:25:08 +0200 Subject: [PATCH 4/9] Console command permissions can be changed in the client permission menu, permitted commands are displayed in the client-side permission popup --- .../Source/GUI/GUIMessageBox.cs | 8 ++--- .../BarotraumaClient/Source/GUI/GUITickBox.cs | 4 +-- .../Source/Networking/GameClient.cs | 16 +++++++-- .../Source/Screens/NetLobbyScreen.cs | 36 +++++++++++++++++-- .../BarotraumaShared/Source/DebugConsole.cs | 4 +++ 5 files changed, 54 insertions(+), 14 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUIMessageBox.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUIMessageBox.cs index 77e91591c..5ab8aff9f 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUIMessageBox.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUIMessageBox.cs @@ -7,12 +7,8 @@ namespace Barotrauma { public static List MessageBoxes = new List(); - const int DefaultWidth=400, DefaultHeight=250; - - //public delegate bool OnClickedHandler(GUIButton button, object obj); - //public OnClickedHandler OnClicked; - - //GUIFrame frame; + public const int DefaultWidth = 400, DefaultHeight = 250; + public GUIButton[] Buttons; public static GUIComponent VisibleBox diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUITickBox.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUITickBox.cs index 27dea057a..e6e0f5734 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUITickBox.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUITickBox.cs @@ -50,8 +50,8 @@ namespace Barotrauma { base.Rect = value; - box.Rect = new Rectangle(value.X,value.Y,box.Rect.Width,box.Rect.Height); - text.Rect = new Rectangle(box.Rect.Right, box.Rect.Y + 2, 20, box.Rect.Height); + if (box != null) box.Rect = new Rectangle(value.X,value.Y,box.Rect.Width,box.Rect.Height); + if (text != null) text.Rect = new Rectangle(box.Rect.Right, box.Rect.Y + 2, 20, box.Rect.Height); } } diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs index 00b0f248d..8a6150318 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs @@ -636,13 +636,23 @@ namespace Barotrauma.Networking DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); msg += " - " + attributes[0].Description + "\n"; } - - //TODO: display permitted console commands } permissions = newPermissions; this.permittedConsoleCommands = new List(permittedConsoleCommands); - new GUIMessageBox("Permissions changed", msg).UserData = "permissions"; + GUIMessageBox msgBox = new GUIMessageBox("Permissions changed", msg, GUIMessageBox.DefaultWidth, 0); + msgBox.UserData = "permissions"; + + if (newPermissions.HasFlag(ClientPermissions.ConsoleCommands)) + { + int listBoxWidth = (int)(msgBox.InnerFrame.Rect.Width - msgBox.InnerFrame.Padding.X - msgBox.InnerFrame.Padding.Z) / 2 - 30; + new GUITextBlock(new Rectangle(0, 0, listBoxWidth, 15), "Permitted console commands:", "", Alignment.TopRight, Alignment.TopLeft, msgBox.InnerFrame, true, GUI.SmallFont); + var commandList = new GUIListBox(new Rectangle(0, 20, listBoxWidth, 0), "", Alignment.BottomRight, msgBox.InnerFrame); + foreach (string permittedCommand in permittedConsoleCommands) + { + new GUITextBlock(new Rectangle(0, 0, 0, 15), permittedCommand, "", commandList, GUI.SmallFont).CanBeFocused = false; + } + } GameMain.NetLobbyScreen.SubList.Enabled = Voting.AllowSubVoting || HasPermission(ClientPermissions.SelectSub); GameMain.NetLobbyScreen.ModeList.Enabled = Voting.AllowModeVoting || HasPermission(ClientPermissions.SelectMode); diff --git a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs index 8b1e9e6b4..193614b40 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs @@ -857,7 +857,7 @@ namespace Barotrauma playerFrame = new GUIFrame(new Rectangle(0, 0, 0, 0), Color.Black * 0.6f); - var playerFrameInner = new GUIFrame(new Rectangle(0, 0, 300, 280), null, Alignment.Center, "", playerFrame); + var playerFrameInner = new GUIFrame(new Rectangle(0, 0, 300, 370), null, Alignment.Center, "", playerFrame); playerFrameInner.Padding = new Vector4(20.0f, 20.0f, 20.0f, 20.0f); new GUITextBlock(new Rectangle(0, 0, 200, 20), component.UserData.ToString(), @@ -870,7 +870,7 @@ namespace Barotrauma new GUITextBlock(new Rectangle(0, 25, 150, 15), selectedClient.Connection.RemoteEndPoint.Address.ToString(), "", playerFrameInner); - var permissionsBox = new GUIFrame(new Rectangle(0, 60, 0, 90), null, playerFrameInner); + var permissionsBox = new GUIFrame(new Rectangle(0, 40, 0, 110), null, playerFrameInner); permissionsBox.Padding = new Vector4(5.0f, 5.0f, 5.0f, 5.0f); permissionsBox.UserData = selectedClient; @@ -911,9 +911,39 @@ namespace Barotrauma if (y >= permissionsBox.Rect.Height - 40) { y = 0; - x += 100; + x += 120; } } + + + new GUITextBlock(new Rectangle(0, 145, 0, 15), "Permitted console commands:", "", playerFrameInner); + var commandList = new GUIListBox(new Rectangle(0,170,0, 80), "", playerFrameInner); + commandList.UserData = selectedClient; + foreach (DebugConsole.Command command in DebugConsole.Commands) + { + var commandTickBox = new GUITickBox(new Rectangle(0,0,15,15), command.names[0], Alignment.TopLeft, GUI.SmallFont, commandList); + commandTickBox.Selected = selectedClient.PermittedConsoleCommands.Contains(command); + commandTickBox.ToolTip = command.help; + commandTickBox.UserData = command; + commandTickBox.OnSelected += (GUITickBox tickBox) => + { + Client client = tickBox.Parent.UserData as Client; + DebugConsole.Command selectedCommand = tickBox.UserData as DebugConsole.Command; + if (client == null) return false; + + if (!tickBox.Selected) + { + client.PermittedConsoleCommands.Remove(selectedCommand); + } + else if (!client.PermittedConsoleCommands.Contains(selectedCommand)) + { + client.PermittedConsoleCommands.Add(selectedCommand); + } + + GameMain.Server.UpdateClientPermissions(client); + return true; + }; + } } if (GameMain.Server != null || GameMain.Client.HasPermission(ClientPermissions.Kick)) diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs index ba7b3a779..e5c70d132 100644 --- a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs @@ -111,6 +111,10 @@ namespace Barotrauma #endif private static List commands = new List(); + public static List Commands + { + get { return commands; } + } private static string currentAutoCompletedCommand; private static int currentAutoCompletedIndex; From 0a8c79c1cb0fffe85f2338bc67544585c2f953af Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 19 Dec 2017 22:22:42 +0200 Subject: [PATCH 5/9] Fixed exceptions in GUIListBox.Select if any of the children have null userdata, option to add tooltips to GUIDropDown items --- .../BarotraumaClient/Source/GUI/GUIDropDown.cs | 3 ++- Barotrauma/BarotraumaClient/Source/GUI/GUIListBox.cs | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUIDropDown.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUIDropDown.cs index ce95757ca..8516d994c 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUIDropDown.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUIDropDown.cs @@ -115,10 +115,11 @@ namespace Barotrauma listBox.AddChild(child); } - public void AddItem(string text, object userData = null) + public void AddItem(string text, object userData = null, string toolTip = "") { GUITextBlock textBlock = new GUITextBlock(new Rectangle(0,0,0,20), text, "ListBoxElement", Alignment.TopLeft, Alignment.CenterLeft, listBox); textBlock.UserData = userData; + textBlock.ToolTip = toolTip; } public override void ClearChildren() diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUIListBox.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUIListBox.cs index 1ffd0eb98..f524012fd 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUIListBox.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUIListBox.cs @@ -171,12 +171,12 @@ namespace Barotrauma { for (int i = 0; i < children.Count; i++) { - if (!children[i].UserData.Equals(userData)) continue; - - Select(i, force); - - //if (OnSelected != null) OnSelected(Selected, Selected.UserData); - if (!SelectMultiple) return; + if ((children[i].UserData != null && children[i].UserData.Equals(userData)) || + (children[i].UserData == null && userData == null)) + { + Select(i, force); + if (!SelectMultiple) return; + } } } From 0204bc2c497a3444d188daec9560cde7241003b5 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 19 Dec 2017 22:27:07 +0200 Subject: [PATCH 6/9] Added permission presets (or ranks). Current presets are none (no special permissions), moderator (round management & kicking) and admin (almost everything permitted). --- .../Source/Screens/NetLobbyScreen.cs | 68 +++++++++++---- .../BarotraumaShared.projitems | 4 + .../Data/permissionpresets.xml | 56 +++++++++++++ .../Source/Networking/Client.cs | 30 ++----- .../Source/Networking/ClientPermissions.cs | 82 +++++++++++++++++++ .../Source/Networking/GameServer.cs | 3 +- .../Source/Networking/GameServerSettings.cs | 1 + 7 files changed, 203 insertions(+), 41 deletions(-) create mode 100644 Barotrauma/BarotraumaShared/Data/permissionpresets.xml create mode 100644 Barotrauma/BarotraumaShared/Source/Networking/ClientPermissions.cs diff --git a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs index 193614b40..a6195abd3 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs @@ -857,24 +857,54 @@ namespace Barotrauma playerFrame = new GUIFrame(new Rectangle(0, 0, 0, 0), Color.Black * 0.6f); - var playerFrameInner = new GUIFrame(new Rectangle(0, 0, 300, 370), null, Alignment.Center, "", playerFrame); + var playerFrameInner = new GUIFrame(GameMain.Server != null ? new Rectangle(0, 0, 450, 370) : new Rectangle(0, 0, 450, 150), null, Alignment.Center, "", playerFrame); playerFrameInner.Padding = new Vector4(20.0f, 20.0f, 20.0f, 20.0f); - new GUITextBlock(new Rectangle(0, 0, 200, 20), component.UserData.ToString(), + new GUITextBlock(new Rectangle(0, 0, 200, 20), obj.ToString(), "", Alignment.TopLeft, Alignment.TopLeft, playerFrameInner, false, GUI.LargeFont); if (GameMain.Server != null) { - var selectedClient = GameMain.Server.ConnectedClients.Find(c => c.Name == component.UserData.ToString()); + var selectedClient = GameMain.Server.ConnectedClients.Find(c => c.Name == obj.ToString()); + playerFrame.UserData = selectedClient; new GUITextBlock(new Rectangle(0, 25, 150, 15), selectedClient.Connection.RemoteEndPoint.Address.ToString(), "", playerFrameInner); - var permissionsBox = new GUIFrame(new Rectangle(0, 40, 0, 110), null, playerFrameInner); + new GUITextBlock(new Rectangle(0, 45, 0, 15), "Rank", "", playerFrameInner); + var rankDropDown = new GUIDropDown(new Rectangle(0, 70, 150, 20), "Rank", "", playerFrameInner); + rankDropDown.UserData = selectedClient; + foreach (PermissionPreset permissionPreset in PermissionPreset.List) + { + rankDropDown.AddItem(permissionPreset.Name, permissionPreset, permissionPreset.Description); + } + rankDropDown.AddItem("Custom", null); + + PermissionPreset currentPreset = PermissionPreset.List.Find(p => + p.Permissions == selectedClient.Permissions && + p.PermittedCommands.Count == selectedClient.PermittedConsoleCommands.Count && !p.PermittedCommands.Except(selectedClient.PermittedConsoleCommands).Any()); + rankDropDown.SelectItem(currentPreset); + + rankDropDown.OnSelected += (c, userdata) => + { + PermissionPreset selectedPreset = (PermissionPreset)userdata; + if (selectedPreset != null) + { + var client = playerFrame.UserData as Client; + client.SetPermissions(selectedPreset.Permissions, selectedPreset.PermittedCommands); + GameMain.Server.UpdateClientPermissions(client); + + playerFrame = null; + SelectPlayer(null, client.Name); + } + return true; + }; + + var permissionsBox = new GUIFrame(new Rectangle(0, 125, (int)(playerFrameInner.Rect.Width * 0.5f), 160), null, playerFrameInner); permissionsBox.Padding = new Vector4(5.0f, 5.0f, 5.0f, 5.0f); permissionsBox.UserData = selectedClient; - new GUITextBlock(new Rectangle(0, 0, 0, 15), "Permissions:", "", permissionsBox); + new GUITextBlock(new Rectangle(0, 100, permissionsBox.Rect.Width, 15), "Permissions:", "", playerFrameInner); int x = 0, y = 0; foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) { @@ -885,13 +915,16 @@ namespace Barotrauma string permissionStr = attributes.Length > 0 ? attributes[0].Description : permission.ToString(); - var permissionTick = new GUITickBox(new Rectangle(x, y + 25, 15, 15), permissionStr, Alignment.TopLeft, GUI.SmallFont, permissionsBox); + var permissionTick = new GUITickBox(new Rectangle(x, y, 15, 15), permissionStr, Alignment.TopLeft, GUI.SmallFont, permissionsBox); permissionTick.UserData = permission; permissionTick.Selected = selectedClient.HasPermission(permission); permissionTick.OnSelected = (tickBox) => { - var client = tickBox.Parent.UserData as Client; + //reset rank to custom + rankDropDown.SelectItem(null); + + var client = playerFrame.UserData as Client; if (client == null) return false; var thisPermission = (ClientPermissions)tickBox.UserData; @@ -906,28 +939,29 @@ namespace Barotrauma return true; }; - y += 20; - if (y >= permissionsBox.Rect.Height - 40) + if (y >= permissionsBox.Rect.Height - 15) { y = 0; x += 120; } } - - new GUITextBlock(new Rectangle(0, 145, 0, 15), "Permitted console commands:", "", playerFrameInner); - var commandList = new GUIListBox(new Rectangle(0,170,0, 80), "", playerFrameInner); + new GUITextBlock(new Rectangle(0, 100, (int)(playerFrameInner.Rect.Width * 0.5f), 15), "Permitted console commands:", "", Alignment.TopRight, Alignment.TopLeft, playerFrameInner, true); + var commandList = new GUIListBox(new Rectangle(0, 125, (int)(playerFrameInner.Rect.Width * 0.5f), 160), "", Alignment.TopRight, playerFrameInner); commandList.UserData = selectedClient; foreach (DebugConsole.Command command in DebugConsole.Commands) { - var commandTickBox = new GUITickBox(new Rectangle(0,0,15,15), command.names[0], Alignment.TopLeft, GUI.SmallFont, commandList); + var commandTickBox = new GUITickBox(new Rectangle(0, 0, 15, 15), command.names[0], Alignment.TopLeft, GUI.SmallFont, commandList); commandTickBox.Selected = selectedClient.PermittedConsoleCommands.Contains(command); commandTickBox.ToolTip = command.help; commandTickBox.UserData = command; commandTickBox.OnSelected += (GUITickBox tickBox) => { - Client client = tickBox.Parent.UserData as Client; + //reset rank to custom + rankDropDown.SelectItem(null); + + Client client = playerFrame.UserData as Client; DebugConsole.Command selectedCommand = tickBox.UserData as DebugConsole.Command; if (client == null) return false; @@ -948,7 +982,7 @@ namespace Barotrauma if (GameMain.Server != null || GameMain.Client.HasPermission(ClientPermissions.Kick)) { - var kickButton = new GUIButton(new Rectangle(0, -50, 100, 20), "Kick", Alignment.BottomLeft, "", playerFrameInner); + var kickButton = new GUIButton(new Rectangle(0, 0, 80, 20), "Kick", Alignment.BottomLeft, "", playerFrameInner); kickButton.UserData = obj; kickButton.OnClicked += KickPlayer; kickButton.OnClicked += ClosePlayerFrame; @@ -956,12 +990,12 @@ namespace Barotrauma if (GameMain.Server != null || GameMain.Client.HasPermission(ClientPermissions.Ban)) { - var banButton = new GUIButton(new Rectangle(0, 0, 100, 20), "Ban", Alignment.BottomLeft, "", playerFrameInner); + var banButton = new GUIButton(new Rectangle(90, 0, 80, 20), "Ban", Alignment.BottomLeft, "", playerFrameInner); banButton.UserData = obj; banButton.OnClicked += BanPlayer; banButton.OnClicked += ClosePlayerFrame; - var rangebanButton = new GUIButton(new Rectangle(0, -25, 100, 20), "Ban range", Alignment.BottomLeft, "", playerFrameInner); + var rangebanButton = new GUIButton(new Rectangle(180, 0, 80, 20), "Ban range", Alignment.BottomLeft, "", playerFrameInner); rangebanButton.UserData = obj; rangebanButton.OnClicked += BanPlayerRange; rangebanButton.OnClicked += ClosePlayerFrame; diff --git a/Barotrauma/BarotraumaShared/BarotraumaShared.projitems b/Barotrauma/BarotraumaShared/BarotraumaShared.projitems index b2beaca84..e63890598 100644 --- a/Barotrauma/BarotraumaShared/BarotraumaShared.projitems +++ b/Barotrauma/BarotraumaShared/BarotraumaShared.projitems @@ -739,6 +739,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -1479,6 +1482,7 @@ + diff --git a/Barotrauma/BarotraumaShared/Data/permissionpresets.xml b/Barotrauma/BarotraumaShared/Data/permissionpresets.xml new file mode 100644 index 000000000..e909124d0 --- /dev/null +++ b/Barotrauma/BarotraumaShared/Data/permissionpresets.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/Source/Networking/Client.cs b/Barotrauma/BarotraumaShared/Source/Networking/Client.cs index b646d5ea6..7d034d4c3 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/Client.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/Client.cs @@ -1,31 +1,10 @@ using Lidgren.Network; using System; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; namespace Barotrauma.Networking { - [Flags] - enum ClientPermissions - { - None = 0, - [Description("End round")] - EndRound = 1, - [Description("Kick")] - Kick = 2, - [Description("Ban")] - Ban = 4, - [Description("Select submarine")] - SelectSub = 8, - [Description("Select game mode")] - SelectMode = 16, - [Description("Manage campaign")] - ManageCampaign = 32, - [Description("Console commands")] - ConsoleCommands = 64 - } - class Client { public string Name; @@ -81,7 +60,11 @@ namespace Barotrauma.Networking public float DeleteDisconnectedTimer; public ClientPermissions Permissions = ClientPermissions.None; - public List PermittedConsoleCommands = new List(); + public List PermittedConsoleCommands + { + get; + private set; + } public bool SpectateOnly; @@ -116,6 +99,7 @@ namespace Barotrauma.Networking this.Name = name; this.ID = ID; + PermittedConsoleCommands = new List(); kickVoters = new List(); votes = new object[Enum.GetNames(typeof(VoteType)).Length]; @@ -160,7 +144,7 @@ namespace Barotrauma.Networking public void SetPermissions(ClientPermissions permissions, List permittedConsoleCommands) { this.Permissions = permissions; - this.PermittedConsoleCommands = permittedConsoleCommands; + this.PermittedConsoleCommands = new List(permittedConsoleCommands); } public void GivePermission(ClientPermissions permission) diff --git a/Barotrauma/BarotraumaShared/Source/Networking/ClientPermissions.cs b/Barotrauma/BarotraumaShared/Source/Networking/ClientPermissions.cs new file mode 100644 index 000000000..262503795 --- /dev/null +++ b/Barotrauma/BarotraumaShared/Source/Networking/ClientPermissions.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Xml.Linq; + +namespace Barotrauma.Networking +{ + [Flags] + enum ClientPermissions + { + None = 0, + [Description("End round")] + EndRound = 1, + [Description("Kick")] + Kick = 2, + [Description("Ban")] + Ban = 4, + [Description("Select submarine")] + SelectSub = 8, + [Description("Select game mode")] + SelectMode = 16, + [Description("Manage campaign")] + ManageCampaign = 32, + [Description("Console commands")] + ConsoleCommands = 64 + } + + class PermissionPreset + { + public static List List = new List(); + + public readonly string Name; + public readonly string Description; + public readonly ClientPermissions Permissions; + public readonly List PermittedCommands; + + public PermissionPreset(XElement element) + { + Name = element.GetAttributeString("name", ""); + Description = element.GetAttributeString("description", ""); + + string permissionsStr = element.GetAttributeString("permissions", ""); + if (!Enum.TryParse(permissionsStr, out Permissions)) + { + DebugConsole.ThrowError("Error in permission preset \"" + Name + "\" - " + permissionsStr + " is not a valid permission!"); + } + + PermittedCommands = new List(); + if (Permissions.HasFlag(ClientPermissions.ConsoleCommands)) + { + foreach (XElement subElement in element.Elements()) + { + if (subElement.Name.ToString().ToLowerInvariant() != "command") continue; + string commandName = subElement.GetAttributeString("name", ""); + + DebugConsole.Command command = DebugConsole.FindCommand(commandName); + if (command == null) + { + DebugConsole.ThrowError("Error in permission preset \"" + Name + "\" - " + commandName + "\" is not a valid console command."); + continue; + } + + PermittedCommands.Add(command); + } + } + } + + public static void LoadAll(string file) + { + if (!File.Exists(file)) return; + + XDocument doc = XMLExtensions.TryLoadXml(file); + if (doc == null || doc.Root == null) return; + + foreach (XElement element in doc.Root.Elements()) + { + List.Add(new PermissionPreset(element)); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs index d84994295..7bdbb30c8 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs @@ -125,8 +125,9 @@ namespace Barotrauma.Networking banList = new BanList(); LoadSettings(); + PermissionPreset.LoadAll(PermissionPresetFile); LoadClientPermissions(); - + CoroutineManager.StartCoroutine(StartServer(isPublic)); } diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs index dc1d78385..bfa98b46d 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs @@ -40,6 +40,7 @@ namespace Barotrauma.Networking } public const string SettingsFile = "serversettings.xml"; + public static readonly string PermissionPresetFile = "Data" + Path.DirectorySeparatorChar + "permissionpresets.xml"; public static readonly string ClientPermissionsFile = "Data" + Path.DirectorySeparatorChar + "clientpermissions.xml"; public Dictionary SerializableProperties From 3e4d2c5a8a3a1faf5f5c8f573160e005e8635037 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 20 Dec 2017 18:57:42 +0200 Subject: [PATCH 7/9] Fixed incorrect positioning of debug console question prompts. The ShowQuestionPrompt method used to take the last textblock in the console and consider that as the question prompt text, even though the text had only been queued and the actual GUITexblock hadn't been instantiated yet. --- .../BarotraumaClient/Source/DebugConsole.cs | 16 ++++++++------ .../BarotraumaServer/Source/DebugConsole.cs | 1 - .../BarotraumaShared/Source/DebugConsole.cs | 22 +++++++++---------- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs index 00b0b4bed..dde53818a 100644 --- a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs @@ -15,6 +15,8 @@ namespace Barotrauma private static Queue queuedMessages = new Queue(); + private static GUITextBlock activeQuestionText; + public static bool IsOpen { get @@ -69,6 +71,13 @@ namespace Barotrauma } } + if (activeQuestionText != null && + (listBox.children.Count == 0 || listBox.children[listBox.children.Count - 1] != activeQuestionText)) + { + listBox.children.Remove(activeQuestionText); + listBox.children.Add(activeQuestionText); + } + if (PlayerInput.KeyHit(Keys.F3)) { isOpen = !isOpen; @@ -177,13 +186,6 @@ namespace Barotrauma } selectedIndex = Messages.Count; - - if (activeQuestionText != null) - { - //make sure the active question stays at the bottom of the list - listBox.children.Remove(activeQuestionText); - listBox.children.Add(activeQuestionText); - } } private static void InitProjectSpecific() diff --git a/Barotrauma/BarotraumaServer/Source/DebugConsole.cs b/Barotrauma/BarotraumaServer/Source/DebugConsole.cs index bdbf0b014..ae33baf0d 100644 --- a/Barotrauma/BarotraumaServer/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaServer/Source/DebugConsole.cs @@ -2,7 +2,6 @@ using Microsoft.Xna.Framework; using System; using System.Collections.Generic; -using System.Linq; namespace Barotrauma { diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs index e5c70d132..05f58ee4d 100644 --- a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs @@ -106,9 +106,6 @@ namespace Barotrauma public delegate void QuestionCallback(string answer); private static QuestionCallback activeQuestionCallback; -#if CLIENT - private static GUIComponent activeQuestionText; -#endif private static List commands = new List(); public static List Commands @@ -1127,8 +1124,8 @@ namespace Barotrauma { NewMessage(command, Color.White); } - -#if !DEBUG && CLIENT + +#if CLIENT if (GameMain.Client != null) { if (GameMain.Client.HasConsoleCommandPermission(splitCommand[0].ToLowerInvariant())) @@ -1148,11 +1145,13 @@ namespace Barotrauma NewMessage("Server command: " + command, Color.White); return; } +#if !DEBUG if (!IsCommandPermitted(splitCommand[0].ToLowerInvariant(), GameMain.Client)) { ThrowError("You're not permitted to use the command \"" + splitCommand[0].ToLowerInvariant() + "\"!"); return; } +#endif } #endif @@ -1427,14 +1426,15 @@ namespace Barotrauma public static void ShowQuestionPrompt(string question, QuestionCallback onAnswered) { - NewMessage(" >>" + question, Color.Cyan); - activeQuestionCallback += onAnswered; + #if CLIENT - if (listBox != null && listBox.children.Count > 0) - { - activeQuestionText = listBox.children[listBox.children.Count - 1]; - } + activeQuestionText = new GUITextBlock(new Rectangle(0, 0, listBox.Rect.Width, 30), " >>" + question, "", Alignment.TopLeft, Alignment.Left, null, true, GUI.SmallFont); + activeQuestionText.CanBeFocused = false; + activeQuestionText.TextColor = Color.Cyan; +#else + NewMessage(" >>" + question, Color.Cyan); #endif + activeQuestionCallback += onAnswered; } private static bool TryParseTimeSpan(string s, out TimeSpan timeSpan) From 91699b26a675db95444a6aa294a0105368808b44 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 20 Dec 2017 18:58:27 +0200 Subject: [PATCH 8/9] Giveperm and revokeperm commands work correctly now when used by clients --- .../BarotraumaShared/Source/DebugConsole.cs | 123 +++++++++++++++++- 1 file changed, 117 insertions(+), 6 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs index 05f58ee4d..c4e894243 100644 --- a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs @@ -342,22 +342,76 @@ namespace Barotrauma if (perm.ToLower() == "all") { permission = ClientPermissions.EndRound | ClientPermissions.Kick | ClientPermissions.Ban | - ClientPermissions.SelectSub | ClientPermissions.SelectMode | ClientPermissions.ManageCampaign | ClientPermissions.ConsoleCommands; + ClientPermissions.SelectSub | ClientPermissions.SelectMode | ClientPermissions.ManageCampaign | ClientPermissions.ConsoleCommands; } else { - Enum.TryParse(perm, out permission); + if (!Enum.TryParse(perm, true, out permission)) + { + NewMessage(perm + " is not a valid permission!", Color.Red); + return; + } } client.GivePermission(permission); GameMain.Server.UpdateClientPermissions(client); NewMessage("Granted " + perm + " permissions to " + client.Name + ".", Color.White); }); + }, + (string[] args) => + { +#if CLIENT + if (args.Length < 1) return; + + int id; + if (!int.TryParse(args[0], out id)) + { + ThrowError("\"" + id + "\" is not a valid client ID."); + return; + } + + ShowQuestionPrompt("Permission to grant to client #" + id + "?", (perm) => + { + GameMain.Client.SendConsoleCommand("giveperm " +id + " " + perm); + }); +#endif + }, + (Client senderClient, Vector2 cursorWorldPos, string[] args) => + { + if (args.Length < 2) return; + + int id; + int.TryParse(args[0], out id); + var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); + if (client == null) + { + GameMain.Server.SendChatMessage("Client id \"" + id + "\" not found.", senderClient); + return; + } + + string perm = string.Join("", args.Skip(1)); + + ClientPermissions permission = ClientPermissions.None; + if (perm.ToLower() == "all") + { + permission = ClientPermissions.EndRound | ClientPermissions.Kick | ClientPermissions.Ban | + ClientPermissions.SelectSub | ClientPermissions.SelectMode | ClientPermissions.ManageCampaign | ClientPermissions.ConsoleCommands; + } + else + { + if (!Enum.TryParse(perm, true, out permission)) + { + GameMain.Server.SendChatMessage(perm + " is not a valid permission!", senderClient); + return; + } + } + client.GivePermission(permission); + GameMain.Server.UpdateClientPermissions(client); + GameMain.Server.SendChatMessage("Granted " + perm + " permissions to " + client.Name + ".", senderClient); + NewMessage(senderClient.Name + " granted " + perm + " permissions to " + client.Name + ".", Color.White); })); commands.Add(new Command("revokeperm", "revokeperm [id]: Revokes administrative permissions to the player with the specified client ID.", (string[] args) => { - //todo: allow client usage - if (GameMain.Server == null) return; if (args.Length < 1) return; @@ -375,16 +429,73 @@ namespace Barotrauma ClientPermissions permission = ClientPermissions.None; if (perm.ToLower() == "all") { - permission = ClientPermissions.EndRound | ClientPermissions.Kick | ClientPermissions.Ban | ClientPermissions.SelectSub | ClientPermissions.SelectMode | ClientPermissions.ManageCampaign; + permission = ClientPermissions.EndRound | ClientPermissions.Kick | ClientPermissions.Ban | + ClientPermissions.SelectSub | ClientPermissions.SelectMode | ClientPermissions.ManageCampaign | ClientPermissions.ConsoleCommands; } else { - Enum.TryParse(perm, out permission); + if (!Enum.TryParse(perm, true, out permission)) + { + NewMessage(perm + " is not a valid permission!", Color.Red); + return; + } } client.RemovePermission(permission); GameMain.Server.UpdateClientPermissions(client); NewMessage("Revoked " + perm + " permissions from " + client.Name + ".", Color.White); }); + }, + (string[] args) => + { +#if CLIENT + if (args.Length < 1) return; + + int id; + if (!int.TryParse(args[0], out id)) + { + ThrowError("\"" + id + "\" is not a valid client ID."); + return; + } + + ShowQuestionPrompt("Permission to revoke from client #" + id + "?", (perm) => + { + GameMain.Client.SendConsoleCommand("revokeperm " + id + " " + perm); + }); +#endif + }, + (Client senderClient, Vector2 cursorWorldPos, string[] args) => + { + if (args.Length < 2) return; + + int id; + int.TryParse(args[0], out id); + var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); + if (client == null) + { + GameMain.Server.SendChatMessage("Client id \"" + id + "\" not found.", senderClient); + return; + } + + string perm = string.Join("", args.Skip(1)); + + ClientPermissions permission = ClientPermissions.None; + if (perm.ToLower() == "all") + { + permission = ClientPermissions.EndRound | ClientPermissions.Kick | ClientPermissions.Ban | + ClientPermissions.SelectSub | ClientPermissions.SelectMode | ClientPermissions.ManageCampaign | ClientPermissions.ConsoleCommands; + } + else + { + if (!Enum.TryParse(perm, true, out permission)) + { + GameMain.Server.SendChatMessage(perm + " is not a valid permission!", senderClient); + return; + } + } + client.RemovePermission(permission); + GameMain.Server.UpdateClientPermissions(client); + GameMain.Server.SendChatMessage("Revoked " + perm + " permissions from " + client.Name + ".", senderClient); + NewMessage(senderClient.Name + " revoked " + perm + " permissions from " + client.Name + ".", Color.White); })); commands.Add(new Command("kick", "kick [name]: Kick a player out of the server.", (string[] args) => From b3c3970209e154abb0fc70ebfbed302b5a00a13a Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 20 Dec 2017 19:18:32 +0200 Subject: [PATCH 9/9] Server responses to clients using console commands ("granted permissions to client", error messages, etc) are displayed in the client's debug console instead of the chat box. Client command usage is included in server logs. --- .../Source/Networking/ChatMessage.cs | 4 ++ .../BarotraumaShared/Source/DebugConsole.cs | 45 ++++++++++--------- .../Source/Networking/ChatMessage.cs | 5 ++- .../Source/Networking/GameServer.cs | 6 +++ .../Source/Networking/ServerLog.cs | 17 ++++--- 5 files changed, 47 insertions(+), 30 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Networking/ChatMessage.cs b/Barotrauma/BarotraumaClient/Source/Networking/ChatMessage.cs index 4ca7e1297..2bbb6344c 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/ChatMessage.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/ChatMessage.cs @@ -44,6 +44,10 @@ namespace Barotrauma.Networking { new GUIMessageBox("", txt); } + else if (type == ChatMessageType.Console) + { + DebugConsole.NewMessage(txt, MessageColor[(int)ChatMessageType.Console]); + } else { GameMain.Client.AddChatMessage(txt, type, senderName, senderCharacter); diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs index c4e894243..3fcc7cbe9 100644 --- a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs @@ -157,12 +157,12 @@ namespace Barotrauma }, null, (Client client, Vector2 cursorWorldPos, string[] args) => { - GameMain.Server.SendChatMessage("***************", client); + GameMain.Server.SendConsoleMessage("***************", client); foreach (Client c in GameMain.Server.ConnectedClients) { - GameMain.Server.SendChatMessage("- " + c.ID.ToString() + ": " + c.Name + ", " + c.Connection.RemoteEndPoint.Address.ToString(), client); + GameMain.Server.SendConsoleMessage("- " + c.ID.ToString() + ": " + c.Name + ", " + c.Connection.RemoteEndPoint.Address.ToString(), client); } - GameMain.Server.SendChatMessage("***************", client); + GameMain.Server.SendConsoleMessage("***************", client); })); @@ -222,7 +222,7 @@ namespace Barotrauma { HumanAIController.DisableCrewAI = true; NewMessage("Crew AI disabled by \"" + client.Name + "\"", Color.White); - GameMain.Server.SendChatMessage("Crew AI disabled", client); + GameMain.Server.SendConsoleMessage("Crew AI disabled", client); })); commands.Add(new Command("enablecrewai", "enablecrewai: Enable the AI of the NPCs in the crew.", (string[] args) => @@ -235,7 +235,7 @@ namespace Barotrauma { HumanAIController.DisableCrewAI = false; NewMessage("Crew AI enabled by \"" + client.Name + "\"", Color.White); - GameMain.Server.SendChatMessage("Crew AI enabled", client); + GameMain.Server.SendConsoleMessage("Crew AI enabled", client); })); commands.Add(new Command("autorestart", "autorestart [true/false]: Enable or disable round auto-restart.", (string[] args) => @@ -384,7 +384,7 @@ namespace Barotrauma var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); if (client == null) { - GameMain.Server.SendChatMessage("Client id \"" + id + "\" not found.", senderClient); + GameMain.Server.SendConsoleMessage("Client id \"" + id + "\" not found.", senderClient); return; } @@ -400,13 +400,13 @@ namespace Barotrauma { if (!Enum.TryParse(perm, true, out permission)) { - GameMain.Server.SendChatMessage(perm + " is not a valid permission!", senderClient); + GameMain.Server.SendConsoleMessage(perm + " is not a valid permission!", senderClient); return; } } client.GivePermission(permission); GameMain.Server.UpdateClientPermissions(client); - GameMain.Server.SendChatMessage("Granted " + perm + " permissions to " + client.Name + ".", senderClient); + GameMain.Server.SendConsoleMessage("Granted " + perm + " permissions to " + client.Name + ".", senderClient); NewMessage(senderClient.Name + " granted " + perm + " permissions to " + client.Name + ".", Color.White); })); @@ -472,7 +472,7 @@ namespace Barotrauma var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); if (client == null) { - GameMain.Server.SendChatMessage("Client id \"" + id + "\" not found.", senderClient); + GameMain.Server.SendConsoleMessage("Client id \"" + id + "\" not found.", senderClient); return; } @@ -488,13 +488,13 @@ namespace Barotrauma { if (!Enum.TryParse(perm, true, out permission)) { - GameMain.Server.SendChatMessage(perm + " is not a valid permission!", senderClient); + GameMain.Server.SendConsoleMessage(perm + " is not a valid permission!", senderClient); return; } } client.RemovePermission(permission); GameMain.Server.UpdateClientPermissions(client); - GameMain.Server.SendChatMessage("Revoked " + perm + " permissions from " + client.Name + ".", senderClient); + GameMain.Server.SendConsoleMessage("Revoked " + perm + " permissions from " + client.Name + ".", senderClient); NewMessage(senderClient.Name + " revoked " + perm + " permissions from " + client.Name + ".", Color.White); })); @@ -682,7 +682,7 @@ namespace Barotrauma Submarine.MainSub.GodMode = !Submarine.MainSub.GodMode; NewMessage((Submarine.MainSub.GodMode ? "Godmode turned on by \"" : "Godmode off by \"") + client.Name+"\"", Color.White); - GameMain.Server.SendChatMessage(Submarine.MainSub.GodMode ? "Godmode on" : "Godmode off", client); + GameMain.Server.SendConsoleMessage(Submarine.MainSub.GodMode ? "Godmode on" : "Godmode off", client); })); commands.Add(new Command("lockx", "lockx: Lock horizontal movement of the main submarine.", (string[] args) => @@ -953,7 +953,7 @@ namespace Barotrauma int separatorIndex = Array.IndexOf(args, ";"); if (separatorIndex == -1 || args.Length < 3) { - GameMain.Server.SendChatMessage("Invalid parameters. The command should be formatted as \"setclientcharacter [client] ; [character]\"", senderClient); + GameMain.Server.SendConsoleMessage("Invalid parameters. The command should be formatted as \"setclientcharacter [client] ; [character]\"", senderClient); return; } @@ -964,7 +964,7 @@ namespace Barotrauma var client = GameMain.Server.ConnectedClients.Find(c => c.Name == clientName); if (client == null) { - GameMain.Server.SendChatMessage("Client \"" + clientName + "\" not found.", senderClient); + GameMain.Server.SendConsoleMessage("Client \"" + clientName + "\" not found.", senderClient); } var character = FindMatchingCharacter(argsRight, false); @@ -1076,7 +1076,7 @@ namespace Barotrauma var campaign = GameMain.GameSession?.GameMode as CampaignMode; if (campaign == null) { - GameMain.Server.SendChatMessage("No campaign active!", senderClient); + GameMain.Server.SendConsoleMessage("No campaign active!", senderClient); return; } @@ -1084,12 +1084,12 @@ namespace Barotrauma if (args.Length < 1 || !int.TryParse(args[0], out destinationIndex)) return; if (destinationIndex < 0 || destinationIndex >= campaign.Map.CurrentLocation.Connections.Count) { - GameMain.Server.SendChatMessage("Index out of bounds!", senderClient); + GameMain.Server.SendConsoleMessage("Index out of bounds!", senderClient); return; } Location location = campaign.Map.CurrentLocation.Connections[destinationIndex].OtherLocation(campaign.Map.CurrentLocation); campaign.Map.SelectLocation(location); - GameMain.Server.SendChatMessage(location.Name + " selected.", senderClient); + GameMain.Server.SendConsoleMessage(location.Name + " selected.", senderClient); })); #if DEBUG @@ -1289,7 +1289,8 @@ namespace Barotrauma if (string.IsNullOrWhiteSpace(command)) return; if (!client.HasPermission(ClientPermissions.ConsoleCommands)) { - GameMain.Server.SendChatMessage("You are not permitted to use console commands!", client); + GameMain.Server.SendConsoleMessage("You are not permitted to use console commands!", client); + GameServer.Log(client.Name + " attempted to execute the console command \"" + command + "\" without a permission to use console commands.", ServerLog.MessageType.ConsoleUsage); return; } @@ -1297,22 +1298,24 @@ namespace Barotrauma Command matchingCommand = commands.Find(c => c.names.Contains(splitCommand[0].ToLowerInvariant())); if (matchingCommand != null && !client.PermittedConsoleCommands.Contains(matchingCommand)) { - GameMain.Server.SendChatMessage("You are not permitted to use the command\"" + matchingCommand.names[0] + "\"!", client); + GameMain.Server.SendConsoleMessage("You are not permitted to use the command\"" + matchingCommand.names[0] + "\"!", client); + GameServer.Log(client.Name + " attempted to execute the console command \"" + command + "\" without a permission to use the command.", ServerLog.MessageType.ConsoleUsage); return; } else if (matchingCommand == null) { - GameMain.Server.SendChatMessage("Command \"" + splitCommand[0] + "\" not found.", client); + GameMain.Server.SendConsoleMessage("Command \"" + splitCommand[0] + "\" not found.", client); return; } try { matchingCommand.ServerExecuteOnClientRequest(client, cursorWorldPos, splitCommand.Skip(1).ToArray()); + GameServer.Log("Console command \"" + command + "\" executed by " + client.Name + ".", ServerLog.MessageType.ConsoleUsage); } catch (Exception e) { - ThrowError("Executing the command \"" + matchingCommand.names[0]+"\" by request from \""+client.Name+"\" failed.", e); + ThrowError("Executing the command \"" + matchingCommand.names[0] + "\" by request from \"" + client.Name + "\" failed.", e); } } diff --git a/Barotrauma/BarotraumaShared/Source/Networking/ChatMessage.cs b/Barotrauma/BarotraumaShared/Source/Networking/ChatMessage.cs index aea118e94..7139b94b2 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/ChatMessage.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/ChatMessage.cs @@ -7,7 +7,7 @@ namespace Barotrauma.Networking { enum ChatMessageType { - Default, Error, Dead, Server, Radio, Private, MessageBox + Default, Error, Dead, Server, Radio, Private, Console, MessageBox } partial class ChatMessage @@ -25,7 +25,8 @@ namespace Barotrauma.Networking new Color(63, 72, 204), //dead new Color(157, 225, 160), //server new Color(238, 208, 0), //radio - new Color(228, 199, 27) //private + new Color(228, 199, 27), //private + new Color(255, 255, 255) //console }; public readonly string Text; diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs index 7bdbb30c8..aff9917ae 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs @@ -1634,6 +1634,12 @@ namespace Barotrauma.Networking SendChatMessage(msg, recipient); } + public void SendConsoleMessage(string txt, Client recipient) + { + ChatMessage msg = ChatMessage.Create("", txt, ChatMessageType.Console, null); + SendChatMessage(msg, recipient); + } + public void SendChatMessage(ChatMessage msg, Client recipient) { msg.NetStateID = recipient.ChatMsgQueue.Count > 0 ? diff --git a/Barotrauma/BarotraumaShared/Source/Networking/ServerLog.cs b/Barotrauma/BarotraumaShared/Source/Networking/ServerLog.cs index cea50e2e8..74954b977 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/ServerLog.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/ServerLog.cs @@ -28,18 +28,20 @@ namespace Barotrauma.Networking Attack, Spawning, ServerMessage, + ConsoleUsage, Error } private readonly Color[] messageColor = { - Color.LightBlue, - new Color(255, 142, 0), - new Color(238, 208, 0), - new Color(204, 74, 78), - new Color(163, 73, 164), - new Color(157, 225, 160), - Color.Red + Color.LightBlue, //Chat + new Color(255, 142, 0), //ItemInteraction + new Color(238, 208, 0), //Inventory + new Color(204, 74, 78), //Attack + new Color(163, 73, 164), //Spawning + new Color(157, 225, 160), //ServerMessage + new Color(0, 162, 232), //ConsoleUsage + Color.Red //Error }; private readonly string[] messageTypeName = @@ -50,6 +52,7 @@ namespace Barotrauma.Networking "Attack & death", "Spawning", "Server message", + "Console usage", "Error" };