From 0840a29ff3cd9b2ab94d37456b64b4a55bfaf276 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Sun, 3 Dec 2017 22:54:28 +0200 Subject: [PATCH 01/27] 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 02/27] 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 03/27] 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 04/27] 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 48fb3d58b9631425c044400cfab1e4ea9139aaba Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Fri, 8 Dec 2017 13:47:27 +0300 Subject: [PATCH 05/27] Let players choose to grab onto the body's torso! Better CPR animations todo: network the limb targeting --- .../Source/Characters/CharacterHUD.cs | 28 +++++ .../Characters/Animation/AnimController.cs | 2 + .../Animation/HumanoidAnimController.cs | 104 +++++++++++++----- 3 files changed, 104 insertions(+), 30 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs b/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs index f5d37e0b2..c7d01809a 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs @@ -13,6 +13,8 @@ namespace Barotrauma private static Sprite noiseOverlay, damageOverlay; private static GUIButton cprButton; + + private static GUIButton grabHoldButton; private static GUIButton suicideButton; @@ -41,6 +43,8 @@ namespace Barotrauma if (cprButton != null && cprButton.Visible) cprButton.AddToGUIUpdateList(); + if (grabHoldButton != null && cprButton.Visible) grabHoldButton.AddToGUIUpdateList(); + if (suicideButton != null && suicideButton.Visible) suicideButton.AddToGUIUpdateList(); if (!character.IsUnconscious && character.Stun <= 0.0f) @@ -89,6 +93,8 @@ namespace Barotrauma if (cprButton != null && cprButton.Visible) cprButton.Update(deltaTime); + if (grabHoldButton != null && grabHoldButton.Visible) grabHoldButton.Update(deltaTime); + if (suicideButton != null && suicideButton.Visible) suicideButton.Update(deltaTime); if (damageOverlayTimer > 0.0f) damageOverlayTimer -= deltaTime; @@ -195,9 +201,31 @@ namespace Barotrauma }; } + if (grabHoldButton == null) + { + grabHoldButton = new GUIButton( + new Rectangle(character.SelectedCharacter.Inventory.SlotPositions[0].ToPoint() + new Point(320, -60), new Point(130, 20)), + "Grabbing: " + (character.AnimController.GrabLimb == LimbType.Torso ? "Torso" : "Hands"), ""); + + grabHoldButton.OnClicked = (button, userData) => + { + if (Character.Controlled == null || Character.Controlled.SelectedCharacter == null) return false; + + character.AnimController.GrabLimb = character.AnimController.GrabLimb == LimbType.None ? LimbType.Torso : LimbType.None; + + if (GameMain.Client != null) + { + GameMain.Client.CreateEntityEvent(Character.Controlled, new object[] { NetEntityEvent.Type.Repair }); + } + grabHoldButton.Text = "Grabbing: " + (character.AnimController.GrabLimb == LimbType.Torso ? "Torso" : "Hands"); + return true; + }; + } + //cprButton.Visible = character.GetSkillLevel("Medical") > 20.0f; if (cprButton.Visible) cprButton.Draw(spriteBatch); + if (grabHoldButton.Visible) grabHoldButton.Draw(spriteBatch); } if (character.FocusedCharacter != null && character.FocusedCharacter.CanBeSelected) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/AnimController.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/AnimController.cs index 28cfc35c4..7fcc25d17 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/AnimController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/AnimController.cs @@ -9,6 +9,8 @@ namespace Barotrauma public enum Animation { None, Climbing, UsingConstruction, Struggle, CPR }; public Animation Anim; + public LimbType GrabLimb; + protected Character character; protected float walkSpeed, swimSpeed; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs index 9c9a70b4a..19178201f 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs @@ -20,6 +20,7 @@ namespace Barotrauma private float thighTorque; private float cprAnimState; + private float cprPump; private float inWaterTimer; private bool swimming; @@ -882,8 +883,11 @@ namespace Barotrauma Crouching = true; Vector2 diff = character.SelectedCharacter.SimPosition - character.SimPosition; - var targetHead = character.SelectedCharacter.AnimController.GetLimb(LimbType.Head); - + Limb targetHead = character.SelectedCharacter.AnimController.GetLimb(LimbType.Head); + Limb targetTorso = character.SelectedCharacter.AnimController.GetLimb(LimbType.Torso); + Limb head = GetLimb(LimbType.Head); + Limb torso = GetLimb(LimbType.Torso); + Vector2 headDiff = targetHead == null ? diff : targetHead.SimPosition - character.SimPosition; targetMovement = new Vector2(diff.X, 0.0f); @@ -891,21 +895,41 @@ namespace Barotrauma UpdateStanding(); - Vector2 handPos = character.SelectedCharacter.AnimController.GetLimb(LimbType.Torso).SimPosition + Vector2.UnitY * 0.2f; + Vector2 handPos = targetTorso.SimPosition + Vector2.UnitY * 0.2f; Grab(handPos, handPos); - float yPos = (float)Math.Sin(cprAnimState) * 0.1f; - cprAnimState += deltaTime * 8.0f; + Vector2 colliderPos = GetColliderBottom(); + if (cprAnimState % 17 > 15.0f) + { + float yPos = (float)Math.Sin(cprAnimState) * 0.2f; + head.pullJoint.WorldAnchorB = new Vector2(targetHead.SimPosition.X, targetHead.SimPosition.Y + 0.3f + yPos); + head.pullJoint.Enabled = true; + torso.pullJoint.WorldAnchorB = new Vector2(torso.SimPosition.X, colliderPos.Y + (TorsoPosition - 0.2f)); + torso.pullJoint.Enabled = true; + } + else + { + head.pullJoint.WorldAnchorB = new Vector2(targetHead.SimPosition.X, targetHead.SimPosition.Y + 0.8f); + head.pullJoint.Enabled = true; + torso.pullJoint.WorldAnchorB = new Vector2(torso.SimPosition.X, colliderPos.Y + (TorsoPosition - 0.1f)); + torso.pullJoint.Enabled = true; + if (cprPump >= 1) + { + torso.body.ApplyForce(new Vector2(0, -1000f)); + targetTorso.body.ApplyForce(new Vector2(0, -1000f)); + cprPump = 0; + } + cprPump += deltaTime; + } - var head = GetLimb(LimbType.Head); - head.pullJoint.WorldAnchorB = new Vector2(targetHead.SimPosition.X, targetHead.SimPosition.Y + 0.6f + yPos); - head.pullJoint.Enabled = true; + cprAnimState += deltaTime; } public override void DragCharacter(Character target) { if (target == null) return; + Limb torso = GetLimb(LimbType.Torso); Limb leftHand = GetLimb(LimbType.LeftHand); Limb rightHand = GetLimb(LimbType.RightHand); @@ -918,31 +942,34 @@ namespace Barotrauma for (int i = 0; i < 2; i++) { - Limb targetLimb = target.AnimController.GetLimb(LimbType.Torso); + Limb targetLimb = target.AnimController.GetLimb(GrabLimb); - if (i == 0) + if (targetLimb == null || targetLimb.IsSevered) { - if (!targetLeftHand.IsSevered) + targetLimb = target.AnimController.GetLimb(LimbType.Torso); + if (i == 0) { - targetLimb = targetLeftHand; + if (!targetLeftHand.IsSevered) + { + targetLimb = targetLeftHand; + } + else if (!targetRightHand.IsSevered) + { + targetLimb = targetRightHand; + } } - else if (!targetRightHand.IsSevered) + else { - targetLimb = targetRightHand; + if (!targetRightHand.IsSevered) + { + targetLimb = targetRightHand; + } + else if (!targetLeftHand.IsSevered) + { + targetLimb = targetLeftHand; + } } } - else - { - if (!targetRightHand.IsSevered) - { - targetLimb = targetRightHand; - } - else if (!targetLeftHand.IsSevered) - { - targetLimb = targetLeftHand; - } - } - Limb pullLimb = i == 0 ? leftHand : rightHand; if (i == 1 && inWater) @@ -954,12 +981,29 @@ namespace Barotrauma Vector2 diff = ConvertUnits.ToSimUnits(targetLimb.WorldPosition - pullLimb.WorldPosition); pullLimb.pullJoint.Enabled = true; - pullLimb.pullJoint.WorldAnchorB = pullLimb.SimPosition + diff; - pullLimb.pullJoint.MaxForce = 10000.0f; + if (targetLimb.type == LimbType.Torso) + { + pullLimb.pullJoint.WorldAnchorB = targetLimb.SimPosition; + pullLimb.pullJoint.MaxForce = 5000.0f; + targetMovement *= 0.7f; //Carrying people like that takes a lot of effort. + } + else + { + pullLimb.pullJoint.WorldAnchorB = pullLimb.SimPosition + diff; + pullLimb.pullJoint.MaxForce = 5000.0f; + } targetLimb.pullJoint.Enabled = true; - targetLimb.pullJoint.WorldAnchorB = targetLimb.SimPosition - diff; - targetLimb.pullJoint.MaxForce = 10000.0f; + if (targetLimb.type == LimbType.Torso) + { + targetLimb.pullJoint.WorldAnchorB = torso.SimPosition + (Vector2.UnitX * Dir) * 0.6f; + targetLimb.pullJoint.MaxForce = 300.0f; + } + else + { + targetLimb.pullJoint.WorldAnchorB = targetLimb.SimPosition - diff; + targetLimb.pullJoint.MaxForce = 5000.0f; + } target.AnimController.movement = -diff; } From fff8d714fe421fcd08425065a71c1247db94916d Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Fri, 8 Dec 2017 14:09:09 +0300 Subject: [PATCH 06/27] I THINK this is all that needs to be done to network it...right? --- .../BarotraumaClient/Source/Characters/CharacterNetworking.cs | 1 + .../BarotraumaShared/Source/Characters/CharacterNetworking.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs index ec44d08af..12367f179 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs @@ -91,6 +91,7 @@ namespace Barotrauma bool crouching = msg.ReadBoolean(); keys[(int)InputType.Crouch].Held = crouching; keys[(int)InputType.Crouch].SetState(false, crouching); + AnimController.GrabLimb = (LimbType)msg.ReadInt16(); } bool hasAttackLimb = msg.ReadBoolean(); diff --git a/Barotrauma/BarotraumaShared/Source/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaShared/Source/Characters/CharacterNetworking.cs index 5abc8331e..a0eabba53 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/CharacterNetworking.cs @@ -429,6 +429,7 @@ namespace Barotrauma if (AnimController is HumanoidAnimController) { tempBuffer.Write(((HumanoidAnimController)AnimController).Crouching); + tempBuffer.Write((int)((HumanoidAnimController)AnimController).GrabLimb); } bool hasAttackLimb = AnimController.Limbs.Any(l => l != null && l.attack != null); From 286f290e573df49e091760e691306ec10591afe6 Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Fri, 8 Dec 2017 15:32:37 +0300 Subject: [PATCH 07/27] Fixed networking --- .../Source/Characters/CharacterHUD.cs | 8 +++++++- .../Source/Characters/CharacterNetworking.cs | 12 ++++++++---- .../Source/Characters/CharacterNetworking.cs | 7 +++++-- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs b/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs index c58217cfd..46586ed9d 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs @@ -213,10 +213,16 @@ namespace Barotrauma character.AnimController.GrabLimb = character.AnimController.GrabLimb == LimbType.None ? LimbType.Torso : LimbType.None; + foreach (Limb limb in Character.Controlled.SelectedCharacter.AnimController.Limbs) + { + limb.pullJoint.Enabled = false; + } + if (GameMain.Client != null) { - GameMain.Client.CreateEntityEvent(Character.Controlled, new object[] { NetEntityEvent.Type.Repair }); + GameMain.Client.CreateEntityEvent(Character.Controlled, new object[] { NetEntityEvent.Type.Control }); } + grabHoldButton.Text = "Grabbing: " + (character.AnimController.GrabLimb == LimbType.Torso ? "Torso" : "Hands"); return true; }; diff --git a/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs index 12367f179..f9983ec4e 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs @@ -17,15 +17,19 @@ namespace Barotrauma switch ((NetEntityEvent.Type)extraData[0]) { case NetEntityEvent.Type.InventoryState: - msg.WriteRangedInteger(0, 2, 0); + msg.WriteRangedInteger(0, 3, 0); inventory.ClientWrite(msg, extraData); break; case NetEntityEvent.Type.Repair: - msg.WriteRangedInteger(0, 2, 1); + msg.WriteRangedInteger(0, 3, 1); msg.Write(AnimController.Anim == AnimController.Animation.CPR); break; case NetEntityEvent.Type.Status: - msg.WriteRangedInteger(0, 2, 2); + msg.WriteRangedInteger(0, 3, 2); + break; + case NetEntityEvent.Type.Control: + msg.WriteRangedInteger(0, 3, 3); + msg.Write((int)AnimController.GrabLimb); break; } } @@ -91,7 +95,7 @@ namespace Barotrauma bool crouching = msg.ReadBoolean(); keys[(int)InputType.Crouch].Held = crouching; keys[(int)InputType.Crouch].SetState(false, crouching); - AnimController.GrabLimb = (LimbType)msg.ReadInt16(); + AnimController.GrabLimb = (LimbType)msg.ReadInt32(); } bool hasAttackLimb = msg.ReadBoolean(); diff --git a/Barotrauma/BarotraumaShared/Source/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaShared/Source/Characters/CharacterNetworking.cs index 197eac64d..cf1598c3c 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/CharacterNetworking.cs @@ -324,7 +324,7 @@ namespace Barotrauma break; case ClientNetObject.ENTITY_STATE: - int eventType = msg.ReadRangedInteger(0,2); + int eventType = msg.ReadRangedInteger(0,3); switch (eventType) { case 0: @@ -356,6 +356,9 @@ namespace Barotrauma Kill(lastAttackCauseOfDeath); } break; + case 3: + AnimController.GrabLimb = (LimbType)msg.ReadInt32(); + break; } break; } @@ -435,7 +438,7 @@ namespace Barotrauma if (AnimController is HumanoidAnimController) { tempBuffer.Write(((HumanoidAnimController)AnimController).Crouching); - tempBuffer.Write((int)((HumanoidAnimController)AnimController).GrabLimb); + tempBuffer.Write((int)AnimController.GrabLimb); } bool hasAttackLimb = AnimController.Limbs.Any(l => l != null && l.attack != null); From de7489db8b687eccd2c76321da6a3c5df81f4c36 Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Fri, 8 Dec 2017 20:55:46 +0300 Subject: [PATCH 08/27] Change int to UInt32 for grabLimb networking Fixed IsRagdolled state networking --- .../Source/Characters/CharacterNetworking.cs | 7 +++++-- Barotrauma/BarotraumaShared/Source/Characters/Character.cs | 6 ++++-- .../Source/Characters/CharacterNetworking.cs | 6 ++++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs index f9983ec4e..2b9015be1 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs @@ -29,7 +29,7 @@ namespace Barotrauma break; case NetEntityEvent.Type.Control: msg.WriteRangedInteger(0, 3, 3); - msg.Write((int)AnimController.GrabLimb); + msg.Write((UInt16)AnimController.GrabLimb); break; } } @@ -95,7 +95,7 @@ namespace Barotrauma bool crouching = msg.ReadBoolean(); keys[(int)InputType.Crouch].Held = crouching; keys[(int)InputType.Crouch].SetState(false, crouching); - AnimController.GrabLimb = (LimbType)msg.ReadInt32(); + AnimController.GrabLimb = (LimbType)msg.ReadUInt16(); } bool hasAttackLimb = msg.ReadBoolean(); @@ -371,6 +371,9 @@ namespace Barotrauma SetStun(0.0f, true, true); } + bool ragdolled = msg.ReadBoolean(); + IsRagdolled = ragdolled; + bool huskInfected = msg.ReadBoolean(); if (huskInfected) { diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs index a506e62d1..5b3dacbdf 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs @@ -1468,15 +1468,17 @@ namespace Barotrauma return; } + //Do ragdoll shenanigans before Stun because it's still technically a stun, innit? Less network updates for us! if (IsForceRagdolled) IsRagdolled = IsForceRagdolled; - else if (!IsRagdolled || AnimController.Collider.LinearVelocity.Length() < 1f) //Keep us ragdolled if we were forced or we're too speedy to unragdoll + else if (!IsRagdolled || (GameMain.Server != null && AnimController.Collider.LinearVelocity.Length() < 1f)) //Keep us ragdolled if we were forced or we're too speedy to unragdoll IsRagdolled = IsKeyDown(InputType.Ragdoll); //Handle this here instead of Control because we can stop being ragdolled ourselves if (IsRagdolled) { if (AnimController is HumanoidAnimController) ((HumanoidAnimController)AnimController).Crouching = false; - + if(GameMain.Server != null) + GameMain.Server.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.Status }); AnimController.ResetPullJoints(); selectedConstruction = null; return; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaShared/Source/Characters/CharacterNetworking.cs index cf1598c3c..28ab23947 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/CharacterNetworking.cs @@ -357,7 +357,7 @@ namespace Barotrauma } break; case 3: - AnimController.GrabLimb = (LimbType)msg.ReadInt32(); + AnimController.GrabLimb = (LimbType)msg.ReadUInt16(); break; } break; @@ -438,7 +438,7 @@ namespace Barotrauma if (AnimController is HumanoidAnimController) { tempBuffer.Write(((HumanoidAnimController)AnimController).Crouching); - tempBuffer.Write((int)AnimController.GrabLimb); + tempBuffer.Write((UInt16)AnimController.GrabLimb); } bool hasAttackLimb = AnimController.Limbs.Any(l => l != null && l.attack != null); @@ -536,6 +536,8 @@ namespace Barotrauma msg.WriteRangedSingle(MathHelper.Clamp(Stun, 0.0f, MaxStun), 0.0f, MaxStun, 8); } + msg.Write(IsRagdolled); + msg.Write(HuskInfectionState > 0.0f); } } From e0504042784c27df6b16695cf9f476f9866a8c23 Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Fri, 8 Dec 2017 22:30:47 +0300 Subject: [PATCH 09/27] Carrying stunned/unconscious/dead people up ladders woot!!! Properly orient grabbed player on climbing and shoulder grab Allow interaction with buttons, ladders, etc. when grabbing someone --- .../Animation/HumanoidAnimController.cs | 38 +++++++++++++++++-- .../Source/Characters/Character.cs | 2 +- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs index 19178201f..cd7567166 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs @@ -869,7 +869,34 @@ namespace Barotrauma character.SelectedConstruction = null; IgnorePlatforms = false; } + else if (character.SelectedCharacter != null && !character.SelectedCharacter.AllowInput) + { + Limb targetLeftHand = character.SelectedCharacter.AnimController.GetLimb(LimbType.LeftHand); + Limb targetRightHand = character.SelectedCharacter.AnimController.GetLimb(LimbType.RightHand); + Limb targetTorso = character.SelectedCharacter.AnimController.GetLimb(LimbType.Torso); + if (character.SelectedCharacter.AnimController.Dir != Dir) + character.SelectedCharacter.AnimController.Flip(); + + targetTorso.pullJoint.Enabled = true; + targetTorso.pullJoint.WorldAnchorB = torso.SimPosition + (Vector2.UnitX * -Dir) * 0.2f; + targetTorso.pullJoint.MaxForce = 5000.0f; + + if (!targetLeftHand.IsSevered) + { + targetLeftHand.pullJoint.Enabled = true; + targetLeftHand.pullJoint.WorldAnchorB = torso.SimPosition + (new Vector2(1 * Dir, 1)) * 0.2f; + targetLeftHand.pullJoint.MaxForce = 5000.0f; + } + if (!targetRightHand.IsSevered) + { + targetRightHand.pullJoint.Enabled = true; + targetRightHand.pullJoint.WorldAnchorB = torso.SimPosition + (new Vector2(1 * Dir, 1)) * 0.2f; + targetRightHand.pullJoint.MaxForce = 5000.0f; + } + + character.SelectedCharacter.AnimController.IgnorePlatforms = true; + } } private void UpdateCPR(float deltaTime) @@ -880,11 +907,13 @@ namespace Barotrauma return; } + Character target = character.SelectedCharacter; + Crouching = true; - Vector2 diff = character.SelectedCharacter.SimPosition - character.SimPosition; - Limb targetHead = character.SelectedCharacter.AnimController.GetLimb(LimbType.Head); - Limb targetTorso = character.SelectedCharacter.AnimController.GetLimb(LimbType.Torso); + Vector2 diff = target.SimPosition - character.SimPosition; + Limb targetHead = target.AnimController.GetLimb(LimbType.Head); + Limb targetTorso = target.AnimController.GetLimb(LimbType.Torso); Limb head = GetLimb(LimbType.Head); Limb torso = GetLimb(LimbType.Torso); @@ -986,6 +1015,9 @@ namespace Barotrauma pullLimb.pullJoint.WorldAnchorB = targetLimb.SimPosition; pullLimb.pullJoint.MaxForce = 5000.0f; targetMovement *= 0.7f; //Carrying people like that takes a lot of effort. + + if (target.AnimController.Dir != Dir) + target.AnimController.Flip(); } else { diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs index 5b3dacbdf..88d5d70fe 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs @@ -1308,7 +1308,7 @@ namespace Barotrauma findFocusedTimer -= deltaTime; } - if (SelectedCharacter != null && IsKeyHit(InputType.Select)) + if (SelectedCharacter != null && focusedItem == null && IsKeyHit(InputType.Select)) //Let people use ladders and buttons and stuff when dragging chars { DeselectCharacter(); } From 07aeac4fdc94804e147f7c8502bb7768a288934a Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Sat, 9 Dec 2017 19:09:10 +0300 Subject: [PATCH 10/27] Traitor Count/Coefficient and Ragdoll and Karma server settings buttons!!! oh my GOD!!! --- .../Source/Networking/GameServerSettings.cs | 83 ++++++++++++++++++- .../Source/Characters/Character.cs | 2 +- .../Source/Networking/GameServerSettings.cs | 21 +++++ 3 files changed, 102 insertions(+), 4 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameServerSettings.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameServerSettings.cs index 7c694d39a..229fe6432 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameServerSettings.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameServerSettings.cs @@ -320,7 +320,7 @@ namespace Barotrauma.Networking return true; }; - y += 40; + y += 20; var voteKickBox = new GUITickBox(new Rectangle(0, y, 20, 20), "Allow vote kicking", Alignment.Left, settingsTabs[1]); voteKickBox.Selected = Voting.AllowVoteKick; @@ -357,7 +357,7 @@ namespace Barotrauma.Networking return true; }; - y += 40; + y += 20; var randomizeLevelBox = new GUITickBox(new Rectangle(0, y, 20, 20), "Randomize level seed between rounds", Alignment.Left, settingsTabs[1]); randomizeLevelBox.Selected = RandomizeSeed; @@ -367,7 +367,7 @@ namespace Barotrauma.Networking return true; }; - y += 40; + y += 20; var saveLogsBox = new GUITickBox(new Rectangle(0, y, 20, 20), "Save server logs", Alignment.Left, settingsTabs[1]); saveLogsBox.Selected = SaveServerLogs; @@ -378,6 +378,83 @@ namespace Barotrauma.Networking return true; }; + y += 20; + + var ragdollButtonBox = new GUITickBox(new Rectangle(0, y, 20, 20), "Allow ragdoll button", Alignment.Left, settingsTabs[1]); + ragdollButtonBox.Selected = AllowRagdollButton; + ragdollButtonBox.OnSelected = (GUITickBox) => + { + AllowRagdollButton = GUITickBox.Selected; + return true; + }; + + y += 20; + + var traitorRatioBox = new GUITickBox(new Rectangle(0, y, 20, 20), "Use % of players for max traitors", Alignment.Left, settingsTabs[1]); + var traitorRatioText = new GUITextBlock(new Rectangle(20, y + 20, 20, 20), "Traitor ratio: 20 %", "", settingsTabs[1], GUI.SmallFont); + var traitorRatioSlider = new GUIScrollBar(new Rectangle(150, y + 22, 100, 15), "", 0.1f, settingsTabs[1]); + //Prepare the slider before the tick box + if (traitorUseRatio) + { + traitorRatioSlider.UserData = traitorRatioText; + traitorRatioSlider.Step = 0.01f; //Lots of fine-tuning + traitorRatioSlider.BarScroll = (traitorRatio - 0.1f) / 0.9f; + } + else + { + traitorRatioSlider.UserData = traitorRatioText; + traitorRatioSlider.Step = 1f / (maxPlayers-1); + traitorRatioSlider.BarScroll = MathUtils.Round(traitorRatio, 1f); + } + //Slider END + + traitorRatioBox.Selected = traitorUseRatio; + traitorRatioBox.OnSelected = (GUITickBox) => + { + traitorUseRatio = GUITickBox.Selected; + //Affect the slider graphics + if (traitorUseRatio) + { + traitorRatioSlider.UserData = traitorRatioText; + traitorRatioSlider.Step = 0.01f; //Lots of fine-tuning + traitorRatioSlider.BarScroll = 0.2f; //default values + traitorRatioSlider.OnMoved(traitorRatioSlider, traitorRatioSlider.BarScroll); //Update the scroll bar + } + else + { + traitorRatioSlider.UserData = traitorRatioText; + traitorRatioSlider.Step = 1f / (maxPlayers-1); + traitorRatioSlider.BarScroll = 1; //default values + traitorRatioSlider.OnMoved(traitorRatioSlider, traitorRatioSlider.BarScroll); //Update the scroll bar + } + return true; + }; + traitorRatioSlider.OnMoved = (GUIScrollBar scrollBar, float barScroll) => + { + GUITextBlock traitorText = scrollBar.UserData as GUITextBlock; + if (traitorUseRatio) + { + traitorRatio = barScroll * 0.9f + 0.1f; + traitorText.Text = "Traitor ratio: " + (int)MathUtils.Round(traitorRatio * 100.0f, 1.0f) + " %"; + } + else + { + traitorRatio = MathUtils.Round(barScroll * (maxPlayers-1), 1f) + 1; + traitorText.Text = "Traitor count: " + traitorRatio; + } + return true; + }; + traitorRatioSlider.OnMoved(traitorRatioSlider, traitorRatioSlider.BarScroll); + + y += 45; + + var karmaButtonBox = new GUITickBox(new Rectangle(0, y, 20, 20), "Use Karma", Alignment.Left, settingsTabs[1]); + karmaButtonBox.Selected = KarmaEnabled; + karmaButtonBox.OnSelected = (GUITickBox) => + { + KarmaEnabled = GUITickBox.Selected; + return true; + }; //-------------------------------------------------------------------------------- // banlist diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs index 88d5d70fe..198eb25d4 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs @@ -1471,7 +1471,7 @@ namespace Barotrauma //Do ragdoll shenanigans before Stun because it's still technically a stun, innit? Less network updates for us! if (IsForceRagdolled) IsRagdolled = IsForceRagdolled; - else if (!IsRagdolled || (GameMain.Server != null && AnimController.Collider.LinearVelocity.Length() < 1f)) //Keep us ragdolled if we were forced or we're too speedy to unragdoll + else if (GameMain.Server != null && GameMain.Server.AllowRagdollButton && (!IsRagdolled || AnimController.Collider.LinearVelocity.Length() < 1f)) //Keep us ragdolled if we were forced or we're too speedy to unragdoll IsRagdolled = IsKeyDown(InputType.Ragdoll); //Handle this here instead of Control because we can stop being ragdolled ourselves if (IsRagdolled) diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs index ade900b05..b597f341c 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs @@ -130,6 +130,13 @@ namespace Barotrauma.Networking private set; } + [Serialize(true, true)] + public bool AllowRagdollButton + { + get; + private set; + } + [Serialize(true, true)] public bool AllowFileTransfers { @@ -210,6 +217,20 @@ namespace Barotrauma.Networking private set; } + [Serialize(true, true)] + public bool traitorUseRatio + { + get; + private set; + } + + [Serialize(0.2f, true)] + public float traitorRatio + { + get; + private set; + } + [Serialize(false,true)] public bool KarmaEnabled { From ffba72c750b0acbcc289aa16e6cc36641fc33f75 Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Sat, 9 Dec 2017 19:13:21 +0300 Subject: [PATCH 11/27] Forgot to put new settings into the .xml --- Barotrauma/BarotraumaShared/serversettings.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Barotrauma/BarotraumaShared/serversettings.xml b/Barotrauma/BarotraumaShared/serversettings.xml index 1eb8f2d21..c2da717b4 100644 --- a/Barotrauma/BarotraumaShared/serversettings.xml +++ b/Barotrauma/BarotraumaShared/serversettings.xml @@ -15,11 +15,15 @@ allowspectating="True" endroundatlevelend="True" saveserverlogs="True" + allowragdollbutton="True" allowfiletransfers="True" allowrespawn="True" allowvotekick="True" endvoterequiredratio="0.6" kickvoterequiredratio="0.6" + traitoruseratio="True" + traitorratio="0.2" + karmaenabled="True" SubSelection="Manual" ModeSelection="Manual" TraitorsEnabled="No" From 04707232ded447a94f6c92ee8543ef6483ea9f2b Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Sat, 9 Dec 2017 19:15:51 +0300 Subject: [PATCH 12/27] forgot to remove the console command for karma --- Barotrauma/BarotraumaShared/Source/DebugConsole.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs index 15b2e7ffa..237718d75 100644 --- a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs @@ -419,12 +419,6 @@ namespace Barotrauma }); })); - commands.Add(new Command("togglekarma", "togglekarma: Toggles the karma system.", (string[] args) => - { - if (GameMain.Server == null) return; - GameMain.Server.KarmaEnabled = !GameMain.Server.KarmaEnabled; - })); - commands.Add(new Command("kick", "kick [name]: Kick a player out of the server.", (string[] args) => { if (GameMain.NetworkMember == null || args.Length == 0) return; From 383d9f3aab0442bd3d068b37272e086d6ab878de Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Wed, 13 Dec 2017 21:29:54 +0300 Subject: [PATCH 13/27] Karma too unfinished to be enabled by default --- Barotrauma/BarotraumaShared/serversettings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/serversettings.xml b/Barotrauma/BarotraumaShared/serversettings.xml index c2da717b4..18abd505a 100644 --- a/Barotrauma/BarotraumaShared/serversettings.xml +++ b/Barotrauma/BarotraumaShared/serversettings.xml @@ -23,7 +23,7 @@ kickvoterequiredratio="0.6" traitoruseratio="True" traitorratio="0.2" - karmaenabled="True" + karmaenabled="False" SubSelection="Manual" ModeSelection="Manual" TraitorsEnabled="No" From ff55140ce3b6b67e827a879023a2dbc011780fbe Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Thu, 14 Dec 2017 19:07:53 +0300 Subject: [PATCH 14/27] Revert "forgot to remove the console command for karma" This reverts commit 04707232ded447a94f6c92ee8543ef6483ea9f2b. --- Barotrauma/BarotraumaShared/Source/DebugConsole.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs index 237718d75..15b2e7ffa 100644 --- a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs @@ -419,6 +419,12 @@ namespace Barotrauma }); })); + commands.Add(new Command("togglekarma", "togglekarma: Toggles the karma system.", (string[] args) => + { + if (GameMain.Server == null) return; + GameMain.Server.KarmaEnabled = !GameMain.Server.KarmaEnabled; + })); + commands.Add(new Command("kick", "kick [name]: Kick a player out of the server.", (string[] args) => { if (GameMain.NetworkMember == null || args.Length == 0) return; From 141214eadc6f4aa1e8d46d2f770b66cdf585bb7b Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Thu, 14 Dec 2017 19:08:17 +0300 Subject: [PATCH 15/27] Fix infinitely repairing karma by welding fixed hulls --- Barotrauma/BarotraumaShared/Source/Map/Structure.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Map/Structure.cs b/Barotrauma/BarotraumaShared/Source/Map/Structure.cs index a9f7f8330..06d60a7e0 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Structure.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Structure.cs @@ -678,14 +678,13 @@ namespace Barotrauma if (!MathUtils.IsValid(damage)) return; - float damageDiff = damage - sections[sectionIndex].damage; + if (GameMain.Server != null && damage != sections[sectionIndex].damage) { GameMain.Server.CreateEntityEvent(this); } - AdjustKarma(attacker, damageDiff); if (damage < prefab.Health*0.5f) { if (sections[sectionIndex].gap != null) @@ -717,10 +716,14 @@ namespace Barotrauma sections[sectionIndex].gap.Open = (damage / prefab.Health - 0.5f) * 2.0f; } - + + float damageDiff = damage - sections[sectionIndex].damage; bool hadHole = SectionBodyDisabled(sectionIndex); sections[sectionIndex].damage = MathHelper.Clamp(damage, 0.0f, prefab.Health); + if (sections[sectionIndex].damage < prefab.Health) //otherwise it's possible to infinitely gain karma by welding fixed things + AdjustKarma(attacker, damageDiff); + bool hasHole = SectionBodyDisabled(sectionIndex); if (hadHole == hasHole) return; From e5c49d929d306a9a390fa754a9c7881f1f64a985 Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Sun, 17 Dec 2017 23:13:50 +0300 Subject: [PATCH 16/27] Fixed ragdolling and grab-type switching in singleplayer --- Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs | 4 ++-- Barotrauma/BarotraumaShared/Source/Characters/Character.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs b/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs index bed626321..a2c3e861b 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs @@ -211,7 +211,7 @@ namespace Barotrauma { if (Character.Controlled == null || Character.Controlled.SelectedCharacter == null) return false; - character.AnimController.GrabLimb = character.AnimController.GrabLimb == LimbType.None ? LimbType.Torso : LimbType.None; + Character.Controlled.AnimController.GrabLimb = Character.Controlled.AnimController.GrabLimb == LimbType.None ? LimbType.Torso : LimbType.None; foreach (Limb limb in Character.Controlled.SelectedCharacter.AnimController.Limbs) { @@ -223,7 +223,7 @@ namespace Barotrauma GameMain.Client.CreateEntityEvent(Character.Controlled, new object[] { NetEntityEvent.Type.Control }); } - grabHoldButton.Text = "Grabbing: " + (character.AnimController.GrabLimb == LimbType.Torso ? "Torso" : "Hands"); + grabHoldButton.Text = "Grabbing: " + (Character.Controlled.AnimController.GrabLimb == LimbType.Torso ? "Torso" : "Hands"); return true; }; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs index a6035ccf5..c6ae2591e 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs @@ -1499,7 +1499,7 @@ namespace Barotrauma //Do ragdoll shenanigans before Stun because it's still technically a stun, innit? Less network updates for us! if (IsForceRagdolled) IsRagdolled = IsForceRagdolled; - else if (GameMain.Server != null && GameMain.Server.AllowRagdollButton && (!IsRagdolled || AnimController.Collider.LinearVelocity.Length() < 1f)) //Keep us ragdolled if we were forced or we're too speedy to unragdoll + else if ((GameMain.Server == null || GameMain.Server.AllowRagdollButton) && (!IsRagdolled || AnimController.Collider.LinearVelocity.Length() < 1f)) //Keep us ragdolled if we were forced or we're too speedy to unragdoll IsRagdolled = IsKeyDown(InputType.Ragdoll); //Handle this here instead of Control because we can stop being ragdolled ourselves if (IsRagdolled) From 3343185e9f68c245e199e8673cd24039c99d62de Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Sun, 17 Dec 2017 23:25:17 +0300 Subject: [PATCH 17/27] Make radios drain battery when idle so they're no longer infinite when not equipped in face slot Reduce drainage rate due to original drain rate being too quick though However: radios will still drain battery even when dropped or put in a locker. This could be solved by either implementing a conditional into .xml which would check if a character exists, orrrr by adding a toggle in-inventory button, orrrr by allowing a third "toggle" param for booleans e.g. "OnUse" IsActive="toggle" --- .../BarotraumaShared/Content/Items/Jobgear/misc.xml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Content/Items/Jobgear/misc.xml b/Barotrauma/BarotraumaShared/Content/Items/Jobgear/misc.xml index cdb8cb115..c94e03422 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Jobgear/misc.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Jobgear/misc.xml @@ -14,13 +14,16 @@ - - - - + + + + + + + + - From 3b65802a9584a57d2eddc333497e6e73ecf440af Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Tue, 19 Dec 2017 15:25:01 +0300 Subject: [PATCH 18/27] Fix issue #97 by allowing stunned people to sustain bleed + oxygen damage as well as handling crit health effects differently --- .../Source/Characters/Character.cs | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs index c6ae2591e..737abc64d 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs @@ -1490,6 +1490,7 @@ namespace Barotrauma } } + //Skip health effects as critical health handles it differently if (IsUnconscious) { UpdateUnconscious(deltaTime); @@ -1502,6 +1503,17 @@ namespace Barotrauma else if ((GameMain.Server == null || GameMain.Server.AllowRagdollButton) && (!IsRagdolled || AnimController.Collider.LinearVelocity.Length() < 1f)) //Keep us ragdolled if we were forced or we're too speedy to unragdoll IsRagdolled = IsKeyDown(InputType.Ragdoll); //Handle this here instead of Control because we can stop being ragdolled ourselves + //Health effects + if (needsAir) UpdateOxygen(deltaTime); + + Health -= bleeding * deltaTime; + Bleeding -= BleedingDecreaseSpeed * deltaTime; + + if (health <= minHealth) Kill(CauseOfDeath.Bloodloss); + + if (!IsDead) LockHands = false; + + //ragdoll button if (IsRagdolled) { if (AnimController is HumanoidAnimController) ((HumanoidAnimController)AnimController).Crouching = false; @@ -1512,6 +1524,8 @@ namespace Barotrauma return; } + //AI and control stuff + Control(deltaTime, cam); if (controlled != this && (!(this is AICharacter) || IsRemotePlayer)) { @@ -1533,15 +1547,6 @@ namespace Barotrauma if (aiTarget != null) aiTarget.SoundRange = 0.0f; lowPassMultiplier = MathHelper.Lerp(lowPassMultiplier, 1.0f, 0.1f); - - if (needsAir) UpdateOxygen(deltaTime); - - Health -= bleeding * deltaTime; - Bleeding -= BleedingDecreaseSpeed * deltaTime; - - if (health <= minHealth) Kill(CauseOfDeath.Bloodloss); - - if (!IsDead) LockHands = false; } partial void UpdateControlled(float deltaTime, Camera cam); @@ -1581,12 +1586,18 @@ namespace Barotrauma AnimController.ResetPullJoints(); selectedConstruction = null; - if (oxygen <= 0.0f) Oxygen -= deltaTime * 0.5f; + if (oxygen <= 0.0f) Oxygen -= deltaTime * 0.5f; //Slow down oxygen consumption to increase time in crit + else if (needsAir) UpdateOxygen(deltaTime); //So you can't simply cheat out of requiring oxygen when crit - if (health <= 0.0f) + if (health <= 0.0f) //Critical health - use current state for crit time { AddDamage(bleeding > 0.5f ? CauseOfDeath.Bloodloss : CauseOfDeath.Damage, Math.Max(bleeding, 1.0f) * deltaTime, null); } + else //Keep on bleedin' + { + Health -= bleeding * deltaTime; + Bleeding -= BleedingDecreaseSpeed * deltaTime; + } } private void UpdateSightRange() From e56f5c49467aeb06c15863fd70aa92078b748671 Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Tue, 19 Dec 2017 22:11:45 +0300 Subject: [PATCH 19/27] what started as a fix of https://github.com/Regalis11/Barotrauma/issues/103 ended up being a huge CPR overhaul. oops. CPR: There's now a difference between CPR on bleeding, CPR on hurting and CPR on oxygen deprivation. If you try to CPR bleeding people, it will make bloody sounds and particles while hurting them. So don't. If you CPR people with less than 0 health, it will do RNG based on your skill level to bring them back to life with 2 HP. Otherwise if you CPR their oxygen back, chest pumps will simply prevent oxygen deprivation and mouth-to-mouth will bring back their oxygen while taking yours based on the skill level. Crit: Changed it so you always lose oxygen when critical. Your heart stopped either way! --- .../Animation/HumanoidAnimController.cs | 63 +++++++++++++++++++ .../Source/Characters/Character.cs | 10 +-- 2 files changed, 66 insertions(+), 7 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs index 18b1316a0..d77bc2bac 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs @@ -933,6 +933,17 @@ namespace Barotrauma Grab(handPos, handPos); Vector2 colliderPos = GetColliderBottom(); + + if (GameMain.Client == null) //Serverside code + { + if (target.Bleeding <= 0.5f && target.Oxygen <= 0.0f) //If they're bleeding too hard CPR will hurt them + { + target.Oxygen += deltaTime * 0.5f; //Stabilize them + } + } + + + int skill = character.GetSkillLevel("Medical"); if (cprAnimState % 17 > 15.0f) { float yPos = (float)Math.Sin(cprAnimState) * 0.2f; @@ -940,6 +951,16 @@ namespace Barotrauma head.pullJoint.Enabled = true; torso.pullJoint.WorldAnchorB = new Vector2(torso.SimPosition.X, colliderPos.Y + (TorsoPosition - 0.2f)); torso.pullJoint.Enabled = true; + + if (GameMain.Client == null) //Serverside code + { + float cpr = skill / 2.0f; //Max possible oxygen addition is 20 per second + character.Oxygen -= (30.0f - cpr) * deltaTime; //Worse skill = more oxygen required + if (character.Oxygen > 0.0f) //we didn't suffocate yet did we + target.Oxygen += cpr * deltaTime; + + //DebugConsole.NewMessage("CPR Us: " + character.Oxygen + " Them: " + target.Oxygen + " How good we are: restore " + cpr + " use " + (30.0f - cpr), Color.Aqua); + } } else { @@ -952,6 +973,48 @@ namespace Barotrauma torso.body.ApplyForce(new Vector2(0, -1000f)); targetTorso.body.ApplyForce(new Vector2(0, -1000f)); cprPump = 0; + + if (target.Bleeding <= 0.5f && target.Health <= 0.0f && !target.IsDead) //Have a chance to revive them to 2 HP if they were damaged. + { + if (GameMain.Client == null) //Serverside code + { + float reviveChance = (cprAnimState % 17) * (skill / 50.0f); //~5% max chance for 10 skill, ~50% max chance for 100 skill + float rng = Rand.Int(100, Rand.RandSync.Server); + + //DebugConsole.NewMessage("CPR Pump cprAnimState: " + (cprAnimState % 17) + " revive chance: " + reviveChance + " rng: " + rng, Color.Aqua); + if (rng <= reviveChance) //HOLY CRAP YOU SAVED HIM!!! + { + target.Oxygen = Math.Max(target.Oxygen, 10.0f); + target.Health = 2.0f; + Anim = Animation.None; + return; + } + } + } + else if (target.Bleeding > 0.5f || skill < 50) //We will hurt them if they're bleeding or we suck + { + //If not bleeding: 10% skill causes 0.8 damage per pump, 40% skill causes only 0.2 + if (target.Bleeding <= 0.5f) + target.AddDamage(CauseOfDeath.Damage, (50 - skill) * 0.02f, character); + else //If bleeding: 2 HP damage per pump. Basically speeds up their death. Don't pump bleeding people! + { + target.AddDamage(CauseOfDeath.Bloodloss, 1.0f, character); +#if CLIENT + SoundPlayer.PlayDamageSound(DamageSoundType.LimbBlunt, 25.0f, targetTorso.body); + float bloodParticleAmount = 4; + float bloodParticleSize = 1.0f; + + for (int i = 0; i < bloodParticleAmount; i++) + { + var blood = GameMain.ParticleManager.CreateParticle(inWater ? "waterblood" : "blood", targetTorso.WorldPosition, Rand.Vector(10.0f), 0.0f, target.AnimController.CurrentHull); + if (blood != null) + { + blood.Size *= bloodParticleSize; + } + } +#endif + } + } } cprPump += deltaTime; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs index 737abc64d..a1de13a8f 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs @@ -1538,15 +1538,12 @@ namespace Barotrauma selectedConstruction = null; } - if (SelectedCharacter != null && AnimController.Anim == AnimController.Animation.CPR) - { - if (GameMain.Client == null) SelectedCharacter.Oxygen += (GetSkillLevel("Medical") / 10.0f) * deltaTime; - } - UpdateSightRange(); if (aiTarget != null) aiTarget.SoundRange = 0.0f; lowPassMultiplier = MathHelper.Lerp(lowPassMultiplier, 1.0f, 0.1f); + + //CPR stuff is handled in the UpdateCPR function in HumanoidAnimController } partial void UpdateControlled(float deltaTime, Camera cam); @@ -1586,8 +1583,7 @@ namespace Barotrauma AnimController.ResetPullJoints(); selectedConstruction = null; - if (oxygen <= 0.0f) Oxygen -= deltaTime * 0.5f; //Slow down oxygen consumption to increase time in crit - else if (needsAir) UpdateOxygen(deltaTime); //So you can't simply cheat out of requiring oxygen when crit + Oxygen -= deltaTime * 0.5f; //We're critical - our heart stopped! if (health <= 0.0f) //Critical health - use current state for crit time { From 0a8c79c1cb0fffe85f2338bc67544585c2f953af Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 19 Dec 2017 22:22:42 +0200 Subject: [PATCH 20/27] 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 21/27] 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 22/27] 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 23/27] 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 24/27] 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" }; From 9ed2871ede11d0fd0053fb5f2cc07c13cc428d22 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 20 Dec 2017 20:26:22 +0200 Subject: [PATCH 25/27] Renamed a couple of properties for consistency & removed unnecessary CPR blood particle scaling --- .../Source/Networking/GameServerSettings.cs | 22 +++++++++---------- .../Animation/HumanoidAnimController.cs | 8 +------ .../Source/Networking/GameServerSettings.cs | 4 ++-- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameServerSettings.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameServerSettings.cs index 229fe6432..62410b7bf 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameServerSettings.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameServerSettings.cs @@ -394,26 +394,26 @@ namespace Barotrauma.Networking var traitorRatioText = new GUITextBlock(new Rectangle(20, y + 20, 20, 20), "Traitor ratio: 20 %", "", settingsTabs[1], GUI.SmallFont); var traitorRatioSlider = new GUIScrollBar(new Rectangle(150, y + 22, 100, 15), "", 0.1f, settingsTabs[1]); //Prepare the slider before the tick box - if (traitorUseRatio) + if (TraitorUseRatio) { traitorRatioSlider.UserData = traitorRatioText; traitorRatioSlider.Step = 0.01f; //Lots of fine-tuning - traitorRatioSlider.BarScroll = (traitorRatio - 0.1f) / 0.9f; + traitorRatioSlider.BarScroll = (TraitorRatio - 0.1f) / 0.9f; } else { traitorRatioSlider.UserData = traitorRatioText; traitorRatioSlider.Step = 1f / (maxPlayers-1); - traitorRatioSlider.BarScroll = MathUtils.Round(traitorRatio, 1f); + traitorRatioSlider.BarScroll = MathUtils.Round(TraitorRatio, 1f); } //Slider END - traitorRatioBox.Selected = traitorUseRatio; + traitorRatioBox.Selected = TraitorUseRatio; traitorRatioBox.OnSelected = (GUITickBox) => { - traitorUseRatio = GUITickBox.Selected; + TraitorUseRatio = GUITickBox.Selected; //Affect the slider graphics - if (traitorUseRatio) + if (TraitorUseRatio) { traitorRatioSlider.UserData = traitorRatioText; traitorRatioSlider.Step = 0.01f; //Lots of fine-tuning @@ -432,15 +432,15 @@ namespace Barotrauma.Networking traitorRatioSlider.OnMoved = (GUIScrollBar scrollBar, float barScroll) => { GUITextBlock traitorText = scrollBar.UserData as GUITextBlock; - if (traitorUseRatio) + if (TraitorUseRatio) { - traitorRatio = barScroll * 0.9f + 0.1f; - traitorText.Text = "Traitor ratio: " + (int)MathUtils.Round(traitorRatio * 100.0f, 1.0f) + " %"; + TraitorRatio = barScroll * 0.9f + 0.1f; + traitorText.Text = "Traitor ratio: " + (int)MathUtils.Round(TraitorRatio * 100.0f, 1.0f) + " %"; } else { - traitorRatio = MathUtils.Round(barScroll * (maxPlayers-1), 1f) + 1; - traitorText.Text = "Traitor count: " + traitorRatio; + TraitorRatio = MathUtils.Round(barScroll * (maxPlayers-1), 1f) + 1; + traitorText.Text = "Traitor count: " + TraitorRatio; } return true; }; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs index d77bc2bac..dd7d675c5 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs @@ -1001,16 +1001,10 @@ namespace Barotrauma target.AddDamage(CauseOfDeath.Bloodloss, 1.0f, character); #if CLIENT SoundPlayer.PlayDamageSound(DamageSoundType.LimbBlunt, 25.0f, targetTorso.body); - float bloodParticleAmount = 4; - float bloodParticleSize = 1.0f; - for (int i = 0; i < bloodParticleAmount; i++) + for (int i = 0; i < 4; i++) { var blood = GameMain.ParticleManager.CreateParticle(inWater ? "waterblood" : "blood", targetTorso.WorldPosition, Rand.Vector(10.0f), 0.0f, target.AnimController.CurrentHull); - if (blood != null) - { - blood.Size *= bloodParticleSize; - } } #endif } diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs index 43e9ad779..cc07cf19e 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs @@ -221,14 +221,14 @@ namespace Barotrauma.Networking } [Serialize(true, true)] - public bool traitorUseRatio + public bool TraitorUseRatio { get; private set; } [Serialize(0.2f, true)] - public float traitorRatio + public float TraitorRatio { get; private set; From 604fc65154901dc9b957a0c165df4aab5d826ad9 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 21 Dec 2017 19:49:26 +0200 Subject: [PATCH 26/27] Human AI improvements & fixes: - Replaced item name comparisons with Prefab.NameMatches (-> item names can be changed without breaking the AIs). - Having an AIObjective set as the current order of the character doesn't automatically cause it to have a high priority. For example, the order to fix leaks has a low priority if there are no leaks to fix. - AIObjectiveFixLeaks makes sure the character is wearing a diving suit before going to fix a leak. The characters used to run in and out of flooded rooms because the AIObjectiveFindSafety objective would become active as soon as the character entered the room, causing the character to run out, and then immediately run back because they are no longer in immediate danger of drowning, making the FixLeaks objective the most high-priority one. - Characters attempt to find a room with no water in AIObjectiveIdle even if the character is wearing a diving suit. - AIObjectiveFindSafety considers flooded rooms dangerous even if the character is wearing a diving suit (-> the character attempts to go into a more dry room instead of happily idling in the flooded one). - Distance to a hull doesn't decrease its desirability nearly as much in AIObjectiveFindSafety (-> fixes characters not bothering to move into a non-flooded room if it's far away). - AIObjectiveOperateItem makes sure the item is equipped before using it (-> characters can't weld leaks with the welder in their inventory). --- .../Characters/AI/IndoorsSteeringManager.cs | 18 ++-- .../Characters/AI/Objectives/AIObjective.cs | 44 ++++------ .../AI/Objectives/AIObjectiveCombat.cs | 23 +++-- .../AI/Objectives/AIObjectiveContainItem.cs | 19 +++-- .../Objectives/AIObjectiveFindDivingGear.cs | 13 +-- .../AI/Objectives/AIObjectiveFindSafety.cs | 84 +++++++++---------- .../AI/Objectives/AIObjectiveFixLeak.cs | 37 +++++--- .../AI/Objectives/AIObjectiveFixLeaks.cs | 61 ++++++++++++-- .../AI/Objectives/AIObjectiveGetItem.cs | 11 +++ .../AI/Objectives/AIObjectiveGoTo.cs | 16 +++- .../AI/Objectives/AIObjectiveIdle.cs | 35 ++++---- .../AI/Objectives/AIObjectiveManager.cs | 42 ++++++---- .../AI/Objectives/AIObjectiveOperateItem.cs | 56 ++++++++++++- .../AI/Objectives/AIObjectiveRescue.cs | 4 +- .../AI/Objectives/AIObjectiveRescueAll.cs | 4 +- .../Items/Components/Holdable/RepairTool.cs | 8 +- .../Source/Items/Components/Turret.cs | 4 +- 17 files changed, 310 insertions(+), 169 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs index 1ba1069c6..3e0f20f84 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs @@ -64,7 +64,7 @@ namespace Barotrauma protected override Vector2 DoSteeringSeek(Vector2 target, float speed = 1) { //find a new path if one hasn't been found yet or the target is different from the current target - if (currentPath == null || Vector2.Distance(target, currentTarget)>1.0f || findPathTimer < -5.0f) + if (currentPath == null || Vector2.Distance(target, currentTarget) > 1.0f || findPathTimer < -1.0f) { if (findPathTimer > 0.0f) return Vector2.Zero; @@ -73,21 +73,21 @@ namespace Barotrauma if (character != null && character.Submarine == null) { var targetHull = Hull.FindHull(FarseerPhysics.ConvertUnits.ToDisplayUnits(target), null, false); - if (targetHull!=null && targetHull.Submarine != null) + if (targetHull != null && targetHull.Submarine != null) { pos -= targetHull.SimPosition; } - } + } currentPath = pathFinder.FindPath(pos, target); - findPathTimer = Rand.Range(1.0f,1.2f); + findPathTimer = Rand.Range(1.0f, 1.2f); return DiffToCurrentNode(); } - + Vector2 diff = DiffToCurrentNode(); - + var collider = character.AnimController.Collider; //if not in water and the waypoint is between the top and bottom of the collider, no need to move vertically if (!character.AnimController.InWater && @@ -104,7 +104,7 @@ namespace Barotrauma private Vector2 DiffToCurrentNode() { - if (currentPath == null || currentPath.Finished || currentPath.Unreachable) return Vector2.Zero; + if (currentPath == null || currentPath.Unreachable) return Vector2.Zero; if (currentPath.Finished) { @@ -113,8 +113,8 @@ namespace Barotrauma { //todo: take multiple subs into account pos2 -= CurrentPath.Nodes.Last().Submarine.SimPosition; - } - return currentTarget-pos2; + } + return currentTarget - pos2; } if (canOpenDoors && !character.LockHands) CheckDoorsInPath(); diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs index ee6381c50..0e527227e 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs @@ -4,21 +4,13 @@ using System.Linq; namespace Barotrauma { - class AIObjective + abstract class AIObjective { - protected List subObjectives; - + protected readonly List subObjectives; protected float priority; - - protected Character character; - + protected readonly Character character; protected string option; - public virtual bool IsCompleted() - { - return false; - } - public virtual bool CanBeCompleted { get { return true; } @@ -27,15 +19,12 @@ namespace Barotrauma public string Option { get { return option; } - } - + } public AIObjective(Character character, string option) { subObjectives = new List(); - this.character = character; - this.option = option; #if DEBUG @@ -60,8 +49,6 @@ namespace Barotrauma Act(deltaTime); } - protected virtual void Act(float deltaTime) { } - public void AddSubObjective(AIObjective objective) { if (subObjectives.Any(o => o.IsDuplicate(objective))) return; @@ -69,19 +56,20 @@ namespace Barotrauma subObjectives.Add(objective); } - public virtual float GetPriority(Character character) + public AIObjective GetCurrentSubObjective() { - return 0.0f; + AIObjective currentSubObjective = this; + while (currentSubObjective.subObjectives.Count > 0) + { + currentSubObjective = subObjectives[0]; + } + return currentSubObjective; } - public virtual bool IsDuplicate(AIObjective otherObjective) - { -#if DEBUG - throw new NotImplementedException(); -#else - return (this.GetType() == otherObjective.GetType()); -#endif - - } + protected abstract void Act(float deltaTime); + + public abstract bool IsCompleted(); + public abstract float GetPriority(AIObjectiveManager objectiveManager); + public abstract bool IsDuplicate(AIObjective otherObjective); } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs index cbe66e791..cf84ae4f3 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs @@ -33,7 +33,6 @@ namespace Barotrauma } coolDownTimer = CoolDown; - } protected override void Act(float deltaTime) @@ -41,17 +40,24 @@ namespace Barotrauma coolDownTimer -= deltaTime; var weapon = character.Inventory.FindItem("weapon"); - - if (weapon==null) + + if (weapon == null) { Escape(deltaTime); } else { + //TODO: make sure the weapon is ready to use (projectiles/batteries loaded) if (!character.SelectedItems.Contains(weapon)) { - character.Inventory.TryPutItem(weapon, 3, false, character); - weapon.Equip(character); + if (character.Inventory.TryPutItem(weapon, 3, false, character)) + { + weapon.Equip(character); + } + else + { + return; + } } character.CursorPosition = enemy.Position; character.SetInput(InputType.Aim, false, true); @@ -99,8 +105,13 @@ namespace Barotrauma return enemy.IsDead || coolDownTimer <= 0.0f; } - public override float GetPriority(Character character) + public override float GetPriority(AIObjectiveManager objectiveManager) { + if (objectiveManager.CurrentOrder == this) + { + return AIObjectiveManager.OrderPriority; + } + //clamp the strength to the health of this character //(it doesn't make a difference whether the enemy does 200 or 600 damage, it's one hit kill anyway) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveContainItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveContainItem.cs index bc92c927e..9dc4e26bc 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveContainItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveContainItem.cs @@ -19,15 +19,6 @@ namespace Barotrauma { this.itemName = itemName; this.container = container; - - //check if the container has room for more items - //canBeCompleted = false; - //foreach (Item contained in container.inventory.Items) - //{ - // if (contained != null) continue; - // canBeCompleted = true; - // break; - //} } public override bool IsCompleted() @@ -35,6 +26,16 @@ namespace Barotrauma return isCompleted || container.Inventory.FindItem(itemName)!=null; } + public override float GetPriority(AIObjectiveManager objectiveManager) + { + if (objectiveManager.CurrentOrder == this) + { + return AIObjectiveManager.OrderPriority; + } + + return 1.0f; + } + protected override void Act(float deltaTime) { if (isCompleted) return; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs index 0b9928d66..132408dd3 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs @@ -15,7 +15,7 @@ namespace Barotrauma if (item == null) return false; var containedItems = item.ContainedItems; - var oxygenTank = Array.Find(containedItems, i => i.Name == "Oxygen Tank" && i.Condition > 0.0f); + var oxygenTank = Array.Find(containedItems, i => i.Prefab.NameMatches("Oxygen Tank") && i.Condition > 0.0f); return oxygenTank != null; } @@ -42,7 +42,7 @@ namespace Barotrauma if (containedItems == null) return; //check if there's an oxygen tank in the mask - var oxygenTank = Array.Find(containedItems, i => i.Name == "Oxygen Tank"); + var oxygenTank = Array.Find(containedItems, i => i.Prefab.NameMatches("Oxygen Tank")); if (oxygenTank != null) { @@ -66,15 +66,18 @@ namespace Barotrauma if (subObjective != null) { subObjective.TryComplete(deltaTime); - - //isCompleted = subObjective.IsCompleted(); } } - public override float GetPriority(Character character) + public override float GetPriority(AIObjectiveManager objectiveManager) { if (character.AnimController.CurrentHull == null) return 100.0f; + if (objectiveManager.CurrentOrder == this) + { + return AIObjectiveManager.OrderPriority; + } + return 100.0f - character.Oxygen; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs index 56de5b8e5..391900d20 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.Xna.Framework; +using System; using System.Collections.Generic; using System.Linq; @@ -27,39 +28,23 @@ namespace Barotrauma unreachable = new List(); } + public override bool IsCompleted() + { + return false; + } + protected override void Act(float deltaTime) { - var currentHull = character.AnimController.CurrentHull; currenthullSafety = OverrideCurrentHullSafety == null ? GetHullSafety(currentHull, character) : (float)OverrideCurrentHullSafety; - - if (currentHull != null) + + if (NeedsDivingGear()) { - if (NeedsDivingGear()) - { - if (!FindDivingGear(deltaTime)) return; - } - - if (currenthullSafety > MinSafety) - { - if (Math.Abs(currentHull.WorldPosition.X - character.WorldPosition.X) > 100.0f) - { - character.AIController.SteeringManager.SteeringSeek(currentHull.SimPosition, 0.5f); - } - else - { - - character.AIController.SteeringManager.Reset(); - } - - character.AIController.SelectTarget(null); - - goToObjective = null; - return; - } + if (!FindDivingGear(deltaTime)) return; } + if (searchHullTimer > 0.0f) { @@ -79,7 +64,7 @@ namespace Barotrauma if (goToObjective != null) { var pathSteering = character.AIController.SteeringManager as IndoorsSteeringManager; - if (pathSteering!=null && pathSteering.CurrentPath!= null && + if (pathSteering != null && pathSteering.CurrentPath != null && pathSteering.CurrentPath.Unreachable && !unreachable.Contains(goToObjective.Target)) { unreachable.Add(goToObjective.Target as Hull); @@ -92,7 +77,7 @@ namespace Barotrauma private bool FindDivingGear(float deltaTime) { - if (divingGearObjective==null) + if (divingGearObjective == null) { divingGearObjective = new AIObjectiveFindDivingGear(character, false); } @@ -113,8 +98,9 @@ namespace Barotrauma if (hull == character.AnimController.CurrentHull || unreachable.Contains(hull)) continue; float hullValue = GetHullSafety(hull, character); - hullValue -= (float)Math.Sqrt(Math.Abs(character.Position.X - hull.Position.X)); - hullValue -= (float)Math.Sqrt(Math.Abs(character.Position.Y - hull.Position.Y) * 2.0f); + //slight preference over hulls that are closer + hullValue -= (float)Math.Sqrt(Math.Abs(character.Position.X - hull.Position.X)) * 0.1f; + hullValue -= (float)Math.Sqrt(Math.Abs(character.Position.Y - hull.Position.Y)) * 0.2f; if (bestHull == null || hullValue > bestValue) { @@ -144,13 +130,13 @@ namespace Barotrauma return false; } - public override float GetPriority(Character character) + public override float GetPriority(AIObjectiveManager objectiveManager) { if (character.Oxygen < 80.0f) { return 150.0f - character.Oxygen; } - + if (character.AnimController.CurrentHull == null) return 5.0f; currenthullSafety = GetHullSafety(character.AnimController.CurrentHull, character); priority = 100.0f - currenthullSafety; @@ -171,13 +157,12 @@ namespace Barotrauma } } } - - + if (NeedsDivingGear()) { if (divingGearObjective != null && !divingGearObjective.IsCompleted()) priority += 20.0f; } - + return priority; } @@ -185,11 +170,28 @@ namespace Barotrauma { if (hull == null) return 0.0f; + float safety = 100.0f; + float waterPercentage = (hull.WaterVolume / hull.Volume) * 100.0f; + if (hull.LethalPressure > 0.0f && character.PressureProtection <= 0.0f) + { + safety -= 100.0f; + } + else if (character.OxygenAvailable <= 0.0f) + { + safety -= waterPercentage; + } + else + { + safety -= waterPercentage * 0.1f; + } + + if (hull.OxygenPercentage < 30.0f) safety -= (30.0f - hull.OxygenPercentage) * 5.0f; + + if (safety <= 0.0f) return 0.0f; + float fireAmount = 0.0f; - var nearbyHulls = hull.GetConnectedHulls(3); - foreach (Hull hull2 in nearbyHulls) { foreach (FireSource fireSource in hull2.FireSources) @@ -204,13 +206,9 @@ namespace Barotrauma } } } + safety -= fireAmount; - float safety = 100.0f - fireAmount; - - if (waterPercentage > 30.0f && character.OxygenAvailable <= 0.0f) safety -= waterPercentage; - if (hull.OxygenPercentage < 30.0f) safety -= (30.0f - hull.OxygenPercentage) * 5.0f; - - return safety; + return MathHelper.Clamp(safety, 0.0f, 100.0f); } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs index 356ed3590..f1e9bfd97 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs @@ -7,7 +7,9 @@ namespace Barotrauma { class AIObjectiveFixLeak : AIObjective { - private Gap leak; + private readonly Gap leak; + + private AIObjectiveGoTo gotoObjective; public Gap Leak { @@ -20,15 +22,20 @@ namespace Barotrauma this.leak = leak; } - public override float GetPriority(Character character) + public override bool IsCompleted() + { + return leak.Open <= 0.0f || leak.Removed; + } + + public override float GetPriority(AIObjectiveManager objectiveManager) { if (leak.Open == 0.0f) return 0.0f; float leakSize = (leak.IsHorizontal ? leak.Rect.Height : leak.Rect.Width) * Math.Max(leak.Open, 0.1f); float dist = Vector2.DistanceSquared(character.SimPosition, leak.SimPosition); - dist = Math.Max(dist/100.0f, 1.0f); - return Math.Min(leakSize/dist, 40.0f); + dist = Math.Max(dist / 100.0f, 1.0f); + return Math.Min(leakSize / dist, 40.0f); } public override bool IsDuplicate(AIObjective otherObjective) @@ -44,7 +51,7 @@ namespace Barotrauma if (weldingTool == null) { - subObjectives.Add(new AIObjectiveGetItem(character, "Welding Tool", true)); + AddSubObjective(new AIObjectiveGetItem(character, "Welding Tool", true)); return; } else @@ -52,25 +59,31 @@ namespace Barotrauma var containedItems = weldingTool.ContainedItems; if (containedItems == null) return; - var fuelTank = Array.Find(containedItems, i => i.Name == "Welding Fuel Tank" && i.Condition > 0.0f); + var fuelTank = Array.Find(containedItems, i => i.Prefab.NameMatches("Welding Fuel Tank") && i.Condition > 0.0f); if (fuelTank == null) { AddSubObjective(new AIObjectiveContainItem(character, "Welding Fuel Tank", weldingTool.GetComponent())); + return; } } var repairTool = weldingTool.GetComponent(); if (repairTool == null) return; - if (Vector2.Distance(character.WorldPosition, leak.WorldPosition) > 300.0f) + Vector2 standPosition = GetStandPosition(); + + if (Vector2.DistanceSquared(character.WorldPosition, leak.WorldPosition) > 100.0f * 100.0f) { - AddSubObjective(new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character)); + var gotoObjective = new AIObjectiveGoTo(ConvertUnits.ToSimUnits(standPosition), character); + if (!gotoObjective.IsCompleted()) + { + AddSubObjective(gotoObjective); + return; + } } - else - { - AddSubObjective(new AIObjectiveOperateItem(repairTool, character, "", leak)); - } + + AddSubObjective(new AIObjectiveOperateItem(repairTool, character, "", true, leak)); } private Vector2 GetStandPosition() diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs index f29971f03..2414a726c 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs @@ -7,38 +7,80 @@ namespace Barotrauma { class AIObjectiveFixLeaks : AIObjective { - const float UpdateGapListInterval = 10.0f; + const float UpdateGapListInterval = 5.0f; - private float updateGapListTimer; + private double lastGapUpdate; private AIObjectiveIdle idleObjective; + private AIObjectiveFindDivingGear findDivingGear; + private List objectiveList; public AIObjectiveFixLeaks(Character character) : base (character, "") { - objectiveList = new List(); } public override bool IsCompleted() { - return false; + if (Timing.TotalTime > lastGapUpdate + UpdateGapListInterval || objectiveList == null) + { + UpdateGapList(); + lastGapUpdate = Timing.TotalTime; + } + + return objectiveList.Count == 0; + } + + public override float GetPriority(AIObjectiveManager objectiveManager) + { + if (Timing.TotalTime > lastGapUpdate + UpdateGapListInterval || objectiveList == null) + { + UpdateGapList(); + lastGapUpdate = Timing.TotalTime; + } + + float priority = 0.0f; + foreach (AIObjectiveFixLeak fixObjective in objectiveList) + { + //gaps from outside to inside significantly increase the priority + if (!fixObjective.Leak.IsRoomToRoom) + { + priority = Math.Max(priority + fixObjective.Leak.Open * 100.0f, 50.0f); + } + else + { + priority += fixObjective.Leak.Open * 10.0f; + } + + if (priority >= 100.0f) break; + } + + return Math.Min(priority, 100.0f); } protected override void Act(float deltaTime) { - updateGapListTimer -= deltaTime; - - if (updateGapListTimer<=0.0f) + if (Timing.TotalTime > lastGapUpdate + UpdateGapListInterval || objectiveList == null) { UpdateGapList(); - - updateGapListTimer = UpdateGapListInterval; + lastGapUpdate = Timing.TotalTime; } if (objectiveList.Any()) { + if (!objectiveList[objectiveList.Count - 1].Leak.IsRoomToRoom) + { + if (findDivingGear == null) findDivingGear = new AIObjectiveFindDivingGear(character, true); + + if (!findDivingGear.IsCompleted() && findDivingGear.CanBeCompleted) + { + findDivingGear.TryComplete(deltaTime); + return; + } + } + objectiveList[objectiveList.Count - 1].TryComplete(deltaTime); if (!objectiveList[objectiveList.Count - 1].CanBeCompleted || @@ -56,6 +98,7 @@ namespace Barotrauma private void UpdateGapList() { + if (objectiveList == null) objectiveList = new List(); objectiveList.Clear(); foreach (Gap gap in Gap.GapList) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs index 07aec2f29..05c80fa69 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs @@ -2,6 +2,7 @@ using Microsoft.Xna.Framework; using System.Collections.Generic; using System.Linq; +using System; namespace Barotrauma { @@ -26,6 +27,16 @@ namespace Barotrauma get { return canBeCompleted; } } + public override float GetPriority(AIObjectiveManager objectiveManager) + { + if (objectiveManager.CurrentOrder == this) + { + return AIObjectiveManager.OrderPriority; + } + + return 1.0f; + } + public AIObjectiveGetItem(Character character, Item targetItem, bool equip = false) : base(character, "") { diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs index 3a9b2e037..cf566bd8a 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs @@ -18,6 +18,16 @@ namespace Barotrauma private bool getDivingGearIfNeeded; + public override float GetPriority(AIObjectiveManager objectiveManager) + { + if (objectiveManager.CurrentOrder == this) + { + return AIObjectiveManager.OrderPriority; + } + + return 1.0f; + } + public override bool CanBeCompleted { get @@ -93,7 +103,7 @@ namespace Barotrauma } } - if (Vector2.Distance(currTargetPos, character.SimPosition) < 1.0f) + if (Vector2.DistanceSquared(currTargetPos, character.SimPosition) < 0.5f * 0.5f) { character.AIController.SteeringManager.Reset(); character.AnimController.TargetDir = currTargetPos.X > character.SimPosition.X ? Direction.Right : Direction.Left; @@ -104,7 +114,7 @@ namespace Barotrauma var indoorsSteering = character.AIController.SteeringManager as IndoorsSteeringManager; - if (indoorsSteering.CurrentPath==null || indoorsSteering.CurrentPath.Unreachable) + if (indoorsSteering.CurrentPath == null || indoorsSteering.CurrentPath.Unreachable) { indoorsSteering.SteeringWander(); } @@ -130,7 +140,7 @@ namespace Barotrauma if (item.IsInsideTrigger(character.WorldPosition)) completed = true; } - completed = completed || Vector2.Distance(target != null ? target.SimPosition : targetPos, character.SimPosition) < allowedDistance; + completed = completed || Vector2.DistanceSquared(target != null ? target.SimPosition : targetPos, character.SimPosition) < allowedDistance * allowedDistance; if (completed) character.AIController.SteeringManager.Reset(); diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs index 59b5b6f6a..6d0c6df9f 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs @@ -9,15 +9,21 @@ namespace Barotrauma { const float WallAvoidDistance = 150.0f; - AITarget currentTarget; + private AITarget currentTarget; private float newTargetTimer; + private AIObjectiveFindSafety findSafety; + public AIObjectiveIdle(Character character) : base(character, "") { - } - public override float GetPriority(Character character) + public override bool IsCompleted() + { + return false; + } + + public override float GetPriority(AIObjectiveManager objectiveManager) { return 1.0f; } @@ -31,6 +37,14 @@ namespace Barotrauma { return; } + + if (character.AnimController.InWater) + { + //attempt to find a safer place if in water + if (findSafety == null) findSafety = new AIObjectiveFindSafety(character); + findSafety.TryComplete(deltaTime); + return; + } if (newTargetTimer <= 0.0f) { @@ -92,17 +106,10 @@ namespace Barotrauma return; } } - - if (character.AnimController.InWater) - { - character.AIController.SteeringManager.SteeringManual(deltaTime, new Vector2(0.0f, 0.5f)); - } - else - { - character.AIController.SteeringManager.SteeringWander(); - //reset vertical steering to prevent dropping down from platforms etc - character.AIController.SteeringManager.ResetY(); - } + + character.AIController.SteeringManager.SteeringWander(); + //reset vertical steering to prevent dropping down from platforms etc + character.AIController.SteeringManager.ResetY(); return; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveManager.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveManager.cs index 8482e3867..468434e8c 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveManager.cs @@ -11,15 +11,17 @@ namespace Barotrauma private Character character; - private AIObjective currentObjective; + private AIObjective currentOrder; + public AIObjective CurrentOrder + { + get { return currentOrder; } + } + public AIObjective CurrentObjective { - get - { - if (currentObjective != null) return currentObjective; - return objectives.Any() ? objectives[0] : null; - } + get; + private set; } public AIObjectiveManager(Character character) @@ -47,8 +49,13 @@ namespace Barotrauma public float GetCurrentPriority(Character character) { - if (currentObjective != null) return OrderPriority; - return (CurrentObjective == null) ? 0.0f : CurrentObjective.GetPriority(character); + if (CurrentOrder != null && + (objectives.Count == 0 || currentOrder.GetPriority(this) > objectives[0].GetPriority(this))) + { + return CurrentOrder.GetPriority(this); + } + + return objectives.Count == 0 ? 0.0f : objectives[0].GetPriority(this); } public void UpdateObjectives() @@ -59,43 +66,46 @@ namespace Barotrauma objectives = objectives.FindAll(o => !o.IsCompleted() && o.CanBeCompleted); //sort objectives according to priority - objectives.Sort((x, y) => y.GetPriority(character).CompareTo(x.GetPriority(character))); + objectives.Sort((x, y) => y.GetPriority(this).CompareTo(x.GetPriority(this))); } public void DoCurrentObjective(float deltaTime) { - if (currentObjective != null && (!objectives.Any() || objectives[0].GetPriority(character) < OrderPriority)) + if (currentOrder != null && (!objectives.Any() || objectives[0].GetPriority(this) < currentOrder.GetPriority(this))) { - currentObjective.TryComplete(deltaTime); + CurrentObjective = currentOrder; + currentOrder.TryComplete(deltaTime); return; } if (!objectives.Any()) return; objectives[0].TryComplete(deltaTime); + + CurrentObjective = objectives[0]; } public void SetOrder(Order order, string option) { if (order == null) return; - currentObjective = null; + currentOrder = null; switch (order.Name.ToLowerInvariant()) { case "follow": - currentObjective = new AIObjectiveGoTo(Character.Controlled, character, true); + currentOrder = new AIObjectiveGoTo(Character.Controlled, character, true); break; case "wait": - currentObjective = new AIObjectiveGoTo(character, character, true); + currentOrder = new AIObjectiveGoTo(character, character, true); break; case "fixleaks": case "fix leaks": - currentObjective = new AIObjectiveFixLeaks(character); + currentOrder = new AIObjectiveFixLeaks(character); break; default: if (order.TargetItem == null) return; - currentObjective = new AIObjectiveOperateItem(order.TargetItem, character, option, null, order.UseController); + currentOrder = new AIObjectiveOperateItem(order.TargetItem, character, option, false, null, order.UseController); break; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs index 9788393a3..061442e73 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs @@ -1,5 +1,6 @@ using Barotrauma.Items.Components; using Microsoft.Xna.Framework; +using System.Collections.Generic; using System.Linq; namespace Barotrauma @@ -14,6 +15,8 @@ namespace Barotrauma private bool canBeCompleted; + private bool requireEquip; + public override bool CanBeCompleted { get @@ -27,11 +30,21 @@ namespace Barotrauma get { return operateTarget; } } - public AIObjectiveOperateItem(ItemComponent item, Character character, string option, Entity operateTarget = null, bool useController = false) - :base (character, option) + public override float GetPriority(AIObjectiveManager objectiveManager) + { + if (objectiveManager.CurrentOrder == this) + { + return AIObjectiveManager.OrderPriority; + } + + return 1.0f; + } + + public AIObjectiveOperateItem(ItemComponent item, Character character, string option, bool requireEquip, Entity operateTarget = null, bool useController = false) + : base (character, option) { this.component = item; - + this.requireEquip = requireEquip; this.operateTarget = operateTarget; if (useController) @@ -72,6 +85,43 @@ namespace Barotrauma } else { + if (requireEquip && !character.HasEquippedItem(component.Item)) + { + //the item has to be equipped before using it if it's holdable + var holdable = component.Item.GetComponent(); + if (holdable == null) + { + DebugConsole.ThrowError("AIObjectiveOperateItem failed - equipping item " + component.Item + " is required but the item has no Holdable component"); + return; + } + + for (int i = 0; i < CharacterInventory.limbSlots.Length; i++) + { + if (CharacterInventory.limbSlots[i] == InvSlotType.Any || + !holdable.AllowedSlots.Any(s => s.HasFlag(CharacterInventory.limbSlots[i]))) + { + continue; + } + + //equip slot already taken + if (character.Inventory.Items[i] != null) + { + //try to put the item in an Any slot, and drop it if that fails + if (!character.Inventory.Items[i].AllowedSlots.Contains(InvSlotType.Any) || + !character.Inventory.TryPutItem(character.Inventory.Items[i], character, new List() { InvSlotType.Any })) + { + character.Inventory.Items[i].Drop(); + } + } + if (character.Inventory.TryPutItem(component.Item, i, true, character)) + { + component.Item.Equip(character); + break; + } + } + return; + } + if (component.AIOperate(deltaTime, character, this)) isCompleted = true; } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs index 84f291d48..60391493f 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs @@ -3,7 +3,7 @@ using System.Diagnostics; namespace Barotrauma { - class AIObjectiveRescue : AIObjective + /*class AIObjectiveRescue : AIObjective { private readonly Character targetCharacter; @@ -30,5 +30,5 @@ namespace Barotrauma return targetCharacter.IsDead ? 1000.0f / distance : 10000.0f / distance; } - } + }*/ } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescueAll.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescueAll.cs index adeb631b9..784b76859 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescueAll.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescueAll.cs @@ -3,7 +3,7 @@ using System.Linq; namespace Barotrauma { - class AIObjectiveRescueAll : AIObjective + /*class AIObjectiveRescueAll : AIObjective { private List rescueTargets; @@ -44,5 +44,5 @@ namespace Barotrauma AddSubObjective(new AIObjectiveRescue(character, target)); } } - } + }*/ } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs index b6bc208fc..74ae7f130 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs @@ -103,11 +103,7 @@ namespace Barotrauma.Items.Components { if (character == null) return false; if (!character.IsKeyDown(InputType.Aim)) return false; - - //if (DoesUseFail(Character)) return false; - - //targetPosition = targetPosition.X, -targetPosition.Y); - + float degreeOfSuccess = DegreeOfSuccess(character)/100.0f; if (Rand.Range(0.0f, 0.5f) > degreeOfSuccess) @@ -241,7 +237,7 @@ namespace Barotrauma.Items.Components { Gap leak = objective.OperateTarget as Gap; if (leak == null) return true; - + float dist = Vector2.Distance(leak.WorldPosition, item.WorldPosition); //too far away -> consider this done and hope the AI is smart enough to move closer diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs index 29d5c60e4..5af91073e 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs @@ -258,9 +258,9 @@ namespace Barotrauma.Items.Components if (batteryToLoad == null) return true; - if (batteryToLoad.RechargeSpeed < batteryToLoad.MaxRechargeSpeed*0.4f) + if (batteryToLoad.RechargeSpeed < batteryToLoad.MaxRechargeSpeed * 0.4f) { - objective.AddSubObjective(new AIObjectiveOperateItem(batteryToLoad, character, "")); + objective.AddSubObjective(new AIObjectiveOperateItem(batteryToLoad, character, "", false)); return false; } } From 4db708335a77d7cdb4d66ba94e7896021453e201 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 21 Dec 2017 21:57:06 +0200 Subject: [PATCH 27/27] Readded homoglyph comparisons to client name checking during login (i.e. similar name checks can't be bypassed by using similar-looking characters). Looks like this got removed due to a messed up merge 5bdb57b Closes #182 --- .../BarotraumaShared/Source/Networking/GameServerLogin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServerLogin.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServerLogin.cs index f0b4d65e4..c40d8f9d3 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServerLogin.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServerLogin.cs @@ -179,7 +179,7 @@ namespace Barotrauma.Networking DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (name taken by the server)", Color.Red); return; } - Client nameTaken = ConnectedClients.Find(c => c.Name.ToLower() == clName.ToLower()); + Client nameTaken = ConnectedClients.Find(c => Homoglyphs.Compare(c.Name.ToLower(), clName.ToLower())); if (nameTaken != null) { if (nameTaken.Connection.RemoteEndPoint.Address.ToString() == inc.SenderEndPoint.Address.ToString())