From 0840a29ff3cd9b2ab94d37456b64b4a55bfaf276 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Sun, 3 Dec 2017 22:54:28 +0200 Subject: [PATCH 01/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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 760452170e611c0c7890dfe904314fbd6e2f849a Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Thu, 14 Dec 2017 22:57:42 +0300 Subject: [PATCH 16/53] Implement ID card slot and ID card description that states the owner and his job in preparation for identity system --- .../Source/Items/CharacterInventory.cs | 21 +++++++++++++++--- .../Content/Items/Reactor/reactor.xml | 4 ++-- .../Content/Items/Tools/tools.xml | 2 +- .../BarotraumaShared/Content/Items/idcard.xml | 7 ++---- Barotrauma/BarotraumaShared/Content/Jobs.xml | 12 +++++----- .../Content/UI/inventoryIcons.png | Bin 14066 -> 16233 bytes .../Source/Characters/Character.cs | 2 +- .../Source/Characters/Jobs/Job.cs | 1 + .../Source/Items/CharacterInventory.cs | 4 ++-- .../BarotraumaShared/Source/Items/Item.cs | 16 +++++++------ 10 files changed, 42 insertions(+), 27 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Items/CharacterInventory.cs b/Barotrauma/BarotraumaClient/Source/Items/CharacterInventory.cs index 6ccfbc19c..6c609618e 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/CharacterInventory.cs @@ -39,7 +39,7 @@ namespace Barotrauma case 3: case 4: SlotPositions[i] = new Vector2( - spacing * 2 + rectWidth + (spacing + rectWidth) * (i - 2), + spacing * 2 + rectWidth + (spacing + rectWidth) * (i - 1), GameMain.GraphicsHeight - (spacing + rectHeight) * 3); useOnSelfButton[i - 3] = new GUIButton( @@ -52,16 +52,24 @@ namespace Barotrauma break; + //face case 5: SlotPositions[i] = new Vector2( spacing * 2 + rectWidth + (spacing + rectWidth) * (i - 5), GameMain.GraphicsHeight - (spacing + rectHeight) * 3); + break; + //id card + case 6: + SlotPositions[i] = new Vector2( + spacing * 2 + rectWidth + (spacing + rectWidth) * (i - 5), + GameMain.GraphicsHeight - (spacing + rectHeight) * 3); + break; default: SlotPositions[i] = new Vector2( - spacing * 2 + rectWidth + (spacing + rectWidth) * ((i - 6) % 5), - GameMain.GraphicsHeight - (spacing + rectHeight) * ((i > 10) ? 2 : 1)); + spacing * 2 + rectWidth + (spacing + rectWidth) * ((i - 7) % 5), + GameMain.GraphicsHeight - (spacing + rectHeight) * ((i > 11) ? 2 : 1)); break; } } @@ -247,6 +255,13 @@ namespace Barotrauma new Vector2(15.0f, 16.0f), Vector2.One, SpriteEffects.None, 0.1f); } + else if (i == 6) + { + spriteBatch.Draw(icons, new Vector2(slotRect.Center.X, slotRect.Center.Y), + new Rectangle(62, 36, 22, 18), Color.White * 0.7f, 0.0f, + new Vector2(11.0f, 9.0f), Vector2.One, + SpriteEffects.None, 0.1f); + } } base.Draw(spriteBatch); diff --git a/Barotrauma/BarotraumaShared/Content/Items/Reactor/reactor.xml b/Barotrauma/BarotraumaShared/Content/Items/Reactor/reactor.xml index 5ac35b6f1..255bf051f 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Reactor/reactor.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Reactor/reactor.xml @@ -68,7 +68,7 @@ - + - + diff --git a/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml b/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml index 0d6c81251..2f194ca87 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml @@ -116,7 +116,7 @@ - + diff --git a/Barotrauma/BarotraumaShared/Content/Items/idcard.xml b/Barotrauma/BarotraumaShared/Content/Items/idcard.xml index 1c2d8c62e..d6659ef30 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/idcard.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/idcard.xml @@ -10,8 +10,5 @@ - - - - - + + \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/Content/Jobs.xml b/Barotrauma/BarotraumaShared/Content/Jobs.xml index 17fa0ac06..c46354b4e 100644 --- a/Barotrauma/BarotraumaShared/Content/Jobs.xml +++ b/Barotrauma/BarotraumaShared/Content/Jobs.xml @@ -8,7 +8,7 @@ - + @@ -30,7 +30,7 @@ - + @@ -48,7 +48,7 @@ - + @@ -66,7 +66,7 @@ - + @@ -86,7 +86,7 @@ - + @@ -105,7 +105,7 @@ - + diff --git a/Barotrauma/BarotraumaShared/Content/UI/inventoryIcons.png b/Barotrauma/BarotraumaShared/Content/UI/inventoryIcons.png index ab2341432011a5710647dff39d55485cacc6f1f6..885ca8b46b947c2754e0e566f22973aa166e39c2 100644 GIT binary patch literal 16233 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 literal 14066 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 Date: Thu, 14 Dec 2017 23:24:25 +0300 Subject: [PATCH 17/53] disguise bool for Wearable class --- .../BarotraumaShared/Source/Items/Components/Wearable.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Wearable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Wearable.cs index 168387bc8..248d4257d 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Wearable.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Wearable.cs @@ -31,14 +31,14 @@ namespace Barotrauma.Items.Components private WearableSprite[] wearableSprites; private LimbType[] limbType; private Limb[] limb; - private List damageModifiers; public List DamageModifiers { get { return damageModifiers; } } - + + public bool Disguise; public Wearable (Item item, XElement element) : base(item, element) { From 18d75c620fcac820747e5c442eb3d88c0b0571e3 Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Fri, 15 Dec 2017 16:47:10 +0300 Subject: [PATCH 18/53] Adds crowbar function to force-open doors... ***except the Door component ignores the damn requireditem and allows bare fukken hands to open it goddamnit*** --- .../Source/Items/Components/Door.cs | 2 +- .../Content/Items/Door/doors.xml | 9 ++++++--- .../Content/Items/Tools/tools.png | Bin 7169 -> 9821 bytes .../Content/Items/Tools/tools.xml | 19 +++++++++++++++++- .../Source/Items/Components/Door.cs | 10 ++++----- .../Items/Components/Holdable/Holdable.cs | 2 +- .../Items/Components/Holdable/Pickable.cs | 2 +- 7 files changed, 31 insertions(+), 13 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Door.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Door.cs index 38817a23f..ccb31b328 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Door.cs @@ -7,7 +7,7 @@ using System; namespace Barotrauma.Items.Components { - partial class Door : ItemComponent, IDrawableComponent, IServerSerializable + partial class Door : Pickable, IDrawableComponent, IServerSerializable { private ConvexHull convexHull; private ConvexHull convexHull2; diff --git a/Barotrauma/BarotraumaShared/Content/Items/Door/doors.xml b/Barotrauma/BarotraumaShared/Content/Items/Door/doors.xml index 120365d6c..043507a2d 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Door/doors.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Door/doors.xml @@ -6,7 +6,8 @@ - + + @@ -35,7 +36,8 @@ - + + @@ -64,7 +66,8 @@ - + + diff --git a/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.png b/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.png index 55f741a6bd25183d7d8d49df3eb4f25641aff28b..79f6c2c5f459bd15e9635156564015053ddd2e72 100644 GIT binary patch literal 9821 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 delta 7166 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 A`v3p{ diff --git a/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml b/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml index 2f194ca87..882767d30 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml @@ -175,7 +175,7 @@ price="10"> - + @@ -185,6 +185,23 @@ + + + + + + + + + + + Date: Sat, 16 Dec 2017 17:26:54 +0300 Subject: [PATCH 19/53] Port over changes from https://github.com/Regalis11/Barotrauma/pull/163/commits/9ad9a65f1e051f2e60f88b3de5b12d2f2dc84674 --- .../BarotraumaShared/Source/Items/Components/ItemComponent.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs index f6d7cb494..498279d26 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs @@ -506,13 +506,15 @@ namespace Barotrauma.Items.Components } List prevRequiredItems = new List(requiredItems); - requiredItems.Clear(); + bool overrideRequiredItems = false; foreach (XElement subElement in componentElement.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) { case "requireditem": + if (!overrideRequiredItems) requiredItems.Clear(); + overrideRequiredItems = true; RelatedItem newRequiredItem = RelatedItem.Load(subElement); if (newRequiredItem == null) continue; From bd646fb23ed4c8f20d1d3455a189bac67c20e835 Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Sat, 16 Dec 2017 19:52:25 +0300 Subject: [PATCH 20/53] Implements identity system! If your mask hides your face (e.g. oxygen mask, clown mask and diving suit), the game will either use the name of the ID card owner in your ID card slot or will give you a "?" name. This only affects "DisplayName". The reason why I can't completely override the .Name for the client from server is due to how crew manifest and CharacterInfo classes are handled - it would require a major rework of many Character-related systems and interactions to truly make this "hack-proof". Server hosts will have to stay on their toes I guess. --- .../Source/Characters/Character.cs | 7 +++-- .../Source/Characters/CharacterHUD.cs | 2 +- .../Source/Items/Inventory.cs | 21 ++++++++++++-- .../Content/Items/Diving/divinggear.xml | 4 +-- .../Content/Items/Jobgear/misc.xml | 1 + .../Content/Items/Tools/tools.xml | 4 +-- .../Source/Characters/Character.cs | 15 ++++++++++ .../Source/Characters/CharacterInfo.cs | 29 ++++++++++++++++++- .../Source/Characters/Jobs/Job.cs | 3 +- 9 files changed, 74 insertions(+), 12 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Character.cs b/Barotrauma/BarotraumaClient/Source/Characters/Character.cs index 8d54f0a0d..2d73f742c 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Character.cs @@ -298,15 +298,16 @@ namespace Barotrauma if (info != null) { - Vector2 namePos = new Vector2(pos.X, pos.Y - 110.0f - (5.0f / cam.Zoom)) - GUI.Font.MeasureString(Info.Name) * 0.5f / cam.Zoom; + string name = Info.DisplayName; + Vector2 namePos = new Vector2(pos.X, pos.Y - 110.0f - (5.0f / cam.Zoom)) - GUI.Font.MeasureString(name) * 0.5f / cam.Zoom; Color nameColor = Color.White; if (Character.Controlled != null && TeamID != Character.Controlled.TeamID) { nameColor = Color.Red; } - GUI.Font.DrawString(spriteBatch, Info.Name, namePos + new Vector2(1.0f / cam.Zoom, 1.0f / cam.Zoom), Color.Black, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.001f); - GUI.Font.DrawString(spriteBatch, Info.Name, namePos, nameColor, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.0f); + GUI.Font.DrawString(spriteBatch, name, namePos + new Vector2(1.0f / cam.Zoom, 1.0f / cam.Zoom), Color.Black, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.001f); + GUI.Font.DrawString(spriteBatch, name, namePos, nameColor, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.0f); if (GameMain.DebugDraw) { diff --git a/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs b/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs index 9a3ac7b23..8a9f1ebc4 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs @@ -208,7 +208,7 @@ namespace Barotrauma string focusName = character.FocusedCharacter.SpeciesName; if (character.FocusedCharacter.Info != null) { - focusName = character.FocusedCharacter.Info.Name; + focusName = character.FocusedCharacter.Info.DisplayName; } Vector2 textPos = startPos; textPos -= new Vector2(GUI.Font.MeasureString(focusName).X / 2, 20); diff --git a/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs b/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs index 8130dea44..b5204a9f0 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs @@ -296,9 +296,26 @@ namespace Barotrauma } else { - toolTip = string.IsNullOrEmpty(Items[i].Description) ? + string description = Items[i].Description; + if (Items[i].Name == "ID Card") + { + string[] readTags = Items[i].Tags.Split(','); + string idName = null; + string idJob = null; + foreach (string tag in readTags) + { + string[] s = tag.Split(':'); + if (s[0] == "name") + idName = s[1]; + if (s[0] == "job") + idJob = s[1]; + } + if (idName != null) + description = "This belongs to " + idName + (idJob != null ? ", the " + idJob + ".\n" : ".\n") + description; + } + toolTip = string.IsNullOrEmpty(description) ? Items[i].Name : - Items[i].Name + '\n' + Items[i].Description; + Items[i].Name + '\n' + description; } DrawToolTip(spriteBatch, toolTip, slots[i].Rect); diff --git a/Barotrauma/BarotraumaShared/Content/Items/Diving/divinggear.xml b/Barotrauma/BarotraumaShared/Content/Items/Diving/divinggear.xml index 238988d26..9cbd1a043 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Diving/divinggear.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Diving/divinggear.xml @@ -41,7 +41,7 @@ - + @@ -92,7 +92,7 @@ - + diff --git a/Barotrauma/BarotraumaShared/Content/Items/Jobgear/misc.xml b/Barotrauma/BarotraumaShared/Content/Items/Jobgear/misc.xml index 5a8c593f1..cdb8cb115 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Jobgear/misc.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Jobgear/misc.xml @@ -38,6 +38,7 @@ + diff --git a/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml b/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml index 882767d30..79c955152 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml @@ -197,8 +197,8 @@ - + aimpos="50,0" handle1="-5,0" holdangle="30" reload="1.7"> + diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs index 85e74eb6b..702b737f7 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs @@ -139,6 +139,19 @@ namespace Barotrauma } } + private float hideFaceTimer; + public bool HideFace + { + get + { + return hideFaceTimer > 0.0f; + } + set + { + hideFaceTimer = MathHelper.Clamp(hideFaceTimer + (value ? 1.0f : -0.5f), 0.0f, 10.0f); + } + } + public string ConfigPath { get; @@ -1406,6 +1419,8 @@ namespace Barotrauma item.Submarine = Submarine; } } + + HideFace = false; if (isDead) return; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/Source/Characters/CharacterInfo.cs index dd6d8133f..12e5d93c4 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/CharacterInfo.cs @@ -12,7 +12,34 @@ namespace Barotrauma partial class CharacterInfo { public string Name; - + public string DisplayName + { + get { + string disguiseName = "?"; + if (Character != null && Character.HideFace) + { + if (Character.Inventory != null) + { + var idCard = Character.Inventory.FindItem("ID Card"); + if (idCard != null && Character.Inventory.IsInLimbSlot(idCard, InvSlotType.Card)) //Disguise as the ID card name if it's equipped + { + string[] readTags = idCard.Tags.Split(','); + foreach (string tag in readTags) + { + string[] s = tag.Split(':'); + if (s[0] == "name") + { + disguiseName = s[1]; + break; + } + } + } + } + return disguiseName; + } + return Name; + } + } public Character Character; public readonly string File; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Jobs/Job.cs b/Barotrauma/BarotraumaShared/Source/Characters/Jobs/Job.cs index d2b3e28e8..865e6bcbc 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Jobs/Job.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Jobs/Job.cs @@ -130,7 +130,8 @@ namespace Barotrauma { item.AddTag(s); } - item.Description = "This belongs to " + character.Name + ", the " + Name + "."; + item.AddTag("name:" + character.Name); + item.AddTag("job:" + Name); } if (parentItem != null) parentItem.Combine(item); From bdeb3a19fda9824c89d500c38fe104c3ca73c95b Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Sat, 16 Dec 2017 20:24:24 +0300 Subject: [PATCH 21/53] Updated server logs so admins can see disguised persons clientist should also display stuff TODO: Log inventory interactions like putting item in slots (e.g. oxy mask equipped in Head slot or equipped in Any slot, etc.) so you can tell the exact moment someone decided to disguise themselves. --- .../BarotraumaClient/Source/Characters/Character.cs | 2 ++ .../Source/Items/Components/Machines/Pump.cs | 6 +++--- .../Source/Items/Components/Power/PowerContainer.cs | 4 ++-- .../Source/Items/Components/Signal/Connection.cs | 4 ++-- .../BarotraumaShared/Source/Characters/Character.cs | 12 ++++++++++-- Barotrauma/BarotraumaShared/Source/DebugConsole.cs | 2 +- .../Source/Items/Components/DockingPort.cs | 4 ++-- .../BarotraumaShared/Source/Items/Components/Door.cs | 2 +- .../Source/Items/Components/Holdable/Holdable.cs | 8 ++++---- .../Source/Items/Components/Holdable/MeleeWeapon.cs | 4 ++-- .../Source/Items/Components/Holdable/Throwable.cs | 2 +- .../Items/Components/Machines/Deconstructor.cs | 2 +- .../Source/Items/Components/Machines/Fabricator.cs | 4 ++-- .../Source/Items/Components/Machines/Pump.cs | 4 ++-- .../Source/Items/Components/Machines/Reactor.cs | 2 +- .../Source/Items/Components/Power/PowerContainer.cs | 2 +- .../Items/Components/Signal/ConnectionPanel.cs | 12 ++++++------ .../Source/Items/Components/Signal/Wire.cs | 6 +++--- .../Source/Items/Components/Turret.cs | 2 +- .../BarotraumaShared/Source/Items/Inventory.cs | 8 ++++---- Barotrauma/BarotraumaShared/Source/Items/Item.cs | 4 ++-- 21 files changed, 53 insertions(+), 43 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Character.cs b/Barotrauma/BarotraumaClient/Source/Characters/Character.cs index 2d73f742c..ddcc3112f 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Character.cs @@ -299,6 +299,8 @@ namespace Barotrauma if (info != null) { string name = Info.DisplayName; + if (controlled == null && name != Info.Name) + name += " (Disguised)"; Vector2 namePos = new Vector2(pos.X, pos.Y - 110.0f - (5.0f / cam.Zoom)) - GUI.Font.MeasureString(name) * 0.5f / cam.Zoom; Color nameColor = Color.White; diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Pump.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Pump.cs index 63777556f..2e9691150 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Pump.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Pump.cs @@ -20,7 +20,7 @@ namespace Barotrauma.Items.Components if (GameMain.Server != null) { item.CreateServerEvent(this); - GameServer.Log(Character.Controlled + (IsActive ? " turned on " : " turned off ") + item.Name, ServerLog.MessageType.ItemInteraction); + GameServer.Log(Character.Controlled.LogName + (IsActive ? " turned on " : " turned off ") + item.Name, ServerLog.MessageType.ItemInteraction); } else if (GameMain.Client != null) { @@ -39,7 +39,7 @@ namespace Barotrauma.Items.Components if (GameMain.Server != null) { item.CreateServerEvent(this); - GameServer.Log(Character.Controlled + " set the pumping speed of " + item.Name + " to " + (int)(flowPercentage) + " %", ServerLog.MessageType.ItemInteraction); + GameServer.Log(Character.Controlled.LogName + " set the pumping speed of " + item.Name + " to " + (int)(flowPercentage) + " %", ServerLog.MessageType.ItemInteraction); } else if (GameMain.Client != null) { @@ -58,7 +58,7 @@ namespace Barotrauma.Items.Components if (GameMain.Server != null) { item.CreateServerEvent(this); - GameServer.Log(Character.Controlled + " set the pumping speed of " + item.Name + " to " + (int)(flowPercentage) + " %", ServerLog.MessageType.ItemInteraction); + GameServer.Log(Character.Controlled.LogName + " set the pumping speed of " + item.Name + " to " + (int)(flowPercentage) + " %", ServerLog.MessageType.ItemInteraction); } else if (GameMain.Client != null) { diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Power/PowerContainer.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Power/PowerContainer.cs index e113317cc..0de3d0b2b 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Power/PowerContainer.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Power/PowerContainer.cs @@ -19,7 +19,7 @@ namespace Barotrauma.Items.Components if (GameMain.Server != null) { item.CreateServerEvent(this); - GameServer.Log(Character.Controlled + " set the recharge speed of " + item.Name + " to " + (int)((rechargeSpeed / maxRechargeSpeed) * 100.0f) + " %", ServerLog.MessageType.ItemInteraction); + GameServer.Log(Character.Controlled.LogName + " set the recharge speed of " + item.Name + " to " + (int)((rechargeSpeed / maxRechargeSpeed) * 100.0f) + " %", ServerLog.MessageType.ItemInteraction); } else if (GameMain.Client != null) { @@ -38,7 +38,7 @@ namespace Barotrauma.Items.Components if (GameMain.Server != null) { item.CreateServerEvent(this); - GameServer.Log(Character.Controlled + " set the recharge speed of " + item.Name + " to " + (int)((rechargeSpeed / maxRechargeSpeed) * 100.0f) + " %", ServerLog.MessageType.ItemInteraction); + GameServer.Log(Character.Controlled.LogName + " set the recharge speed of " + item.Name + " to " + (int)((rechargeSpeed / maxRechargeSpeed) * 100.0f) + " %", ServerLog.MessageType.ItemInteraction); } else if (GameMain.Client != null) { diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs index 1bf1927b0..93b37d1f6 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs @@ -174,12 +174,12 @@ namespace Barotrauma.Items.Components var otherConnection = draggingConnected.OtherConnection(this); if (otherConnection == null) { - GameServer.Log(Character.Controlled + " connected a wire to " + + GameServer.Log(Character.Controlled.LogName + " connected a wire to " + Item.Name + " (" + Name + ")", ServerLog.MessageType.ItemInteraction); } else { - GameServer.Log(Character.Controlled + " connected a wire from " + + GameServer.Log(Character.Controlled.LogName + " connected a wire from " + Item.Name + " (" + Name + ") to " + otherConnection.item.Name + " (" + otherConnection.Name + ")", ServerLog.MessageType.ItemInteraction); } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs index 702b737f7..07ce2b3ad 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs @@ -138,6 +138,14 @@ namespace Barotrauma return info != null && !string.IsNullOrWhiteSpace(info.Name) ? info.Name : SpeciesName; } } + //Only used by server logs to determine "true identity" of the player for cases when they're disguised + public string LogName + { + get + { + return info != null && !string.IsNullOrWhiteSpace(info.Name) ? info.Name + (info.DisplayName != info.Name ? " (as " + info.DisplayName + ")" : "") : SpeciesName; + } + } private float hideFaceTimer; public bool HideFace @@ -1646,7 +1654,7 @@ namespace Barotrauma var attackingCharacter = attacker as Character; if (attackingCharacter != null && attackingCharacter.AIController == null) { - GameServer.Log(Name + " attacked by " + attackingCharacter.Name+". Damage: "+attackResult.Damage+" Bleeding damage: "+attackResult.Bleeding, ServerLog.MessageType.Attack); + GameServer.Log(LogName + " attacked by " + attackingCharacter.LogName +". Damage: "+attackResult.Damage+" Bleeding damage: "+attackResult.Bleeding, ServerLog.MessageType.Attack); } if (GameMain.Client == null && @@ -1811,7 +1819,7 @@ namespace Barotrauma AnimController.Frozen = false; - GameServer.Log(Name+" has died (Cause of death: "+causeOfDeath+")", ServerLog.MessageType.Attack); + GameServer.Log(LogName+" has died (Cause of death: "+causeOfDeath+")", ServerLog.MessageType.Attack); if (OnDeath != null) OnDeath(this, causeOfDeath); diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs index 12e257202..839001bca 100644 --- a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs @@ -97,7 +97,7 @@ namespace Barotrauma NewMessage("***************", Color.Cyan); foreach (Client c in GameMain.Server.ConnectedClients) { - NewMessage("- " + c.ID.ToString() + ": " + c.Name + ", " + c.Connection.RemoteEndPoint.Address.ToString(), Color.Cyan); + NewMessage("- " + c.ID.ToString() + ": " + c.Name + (c.Character != null ? " playing " + c.Character.LogName : "") + ", " + c.Connection.RemoteEndPoint.Address.ToString(), Color.Cyan); } NewMessage("***************", Color.Cyan); })); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs index 1b33e369a..236a2c85d 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs @@ -699,12 +699,12 @@ namespace Barotrauma.Items.Components if (docked) { if (item.Submarine != null && dockingTarget?.item?.Submarine != null) - GameServer.Log(sender.Name + " docked " + item.Submarine.Name + " to " + dockingTarget.item.Submarine.Name, ServerLog.MessageType.ItemInteraction); + GameServer.Log(sender.LogName + " docked " + item.Submarine.Name + " to " + dockingTarget.item.Submarine.Name, ServerLog.MessageType.ItemInteraction); } else { if (item.Submarine != null && prevDockingTarget?.item?.Submarine != null) - GameServer.Log(sender.Name + " undocked " + item.Submarine.Name + " from " + prevDockingTarget.item.Submarine.Name, ServerLog.MessageType.ItemInteraction); + GameServer.Log(sender.LogName + " undocked " + item.Submarine.Name + " from " + prevDockingTarget.item.Submarine.Name, ServerLog.MessageType.ItemInteraction); } } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs index 0ee8f360a..0f52ffcfe 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs @@ -417,7 +417,7 @@ namespace Barotrauma.Items.Components bool newState = predictedState == null ? isOpen : predictedState.Value; if (sender != null && wasOpen != newState) { - GameServer.Log(sender.Name + (newState ? " opened " : " closed ") + item.Name, ServerLog.MessageType.ItemInteraction); + GameServer.Log(sender.LogName + (newState ? " opened " : " closed ") + item.Name, ServerLog.MessageType.ItemInteraction); } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs index cd1683d18..b1b4a9a43 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs @@ -190,7 +190,7 @@ namespace Barotrauma.Items.Components item.body.Enabled = true; IsActive = true; - if (!alreadySelected) GameServer.Log(character.Name + " equipped " + item.Name, ServerLog.MessageType.ItemInteraction); + if (!alreadySelected) GameServer.Log(character.LogName + " equipped " + item.Name, ServerLog.MessageType.ItemInteraction); } } @@ -200,7 +200,7 @@ namespace Barotrauma.Items.Components picker.DeselectItem(item); - GameServer.Log(character.Name + " unequipped " + item.Name, ServerLog.MessageType.ItemInteraction); + GameServer.Log(character.LogName + " unequipped " + item.Name, ServerLog.MessageType.ItemInteraction); item.body.Enabled = false; IsActive = false; @@ -235,7 +235,7 @@ namespace Barotrauma.Items.Components item.CreateServerEvent(this); if (picker != null) { - Networking.GameServer.Log(picker.Name + " detached " + item.Name + " from a wall", ServerLog.MessageType.ItemInteraction); + Networking.GameServer.Log(picker.LogName + " detached " + item.Name + " from a wall", ServerLog.MessageType.ItemInteraction); } } return true; @@ -290,7 +290,7 @@ namespace Barotrauma.Items.Components if (GameMain.Server != null) { item.CreateServerEvent(this); - GameServer.Log(character.Name + " attached " + item.Name+" to a wall", ServerLog.MessageType.ItemInteraction); + GameServer.Log(character.LogName + " attached " + item.Name+" to a wall", ServerLog.MessageType.ItemInteraction); } item.Drop(); } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/MeleeWeapon.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/MeleeWeapon.cs index b98cb3361..07aa6982a 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/MeleeWeapon.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/MeleeWeapon.cs @@ -255,12 +255,12 @@ namespace Barotrauma.Items.Components { GameMain.Server.CreateEntityEvent(item, new object[] { Networking.NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnUse, targetCharacter.ID }); - string logStr = picker?.Name + " used " + item.Name; + string logStr = picker?.LogName + " used " + item.Name; if (item.ContainedItems != null && item.ContainedItems.Length > 0) { logStr += "(" + string.Join(", ", item.ContainedItems.Select(i => i?.Name)) + ")"; } - logStr += " on " + targetCharacter + "."; + logStr += " on " + targetCharacter.LogName + "."; Networking.GameServer.Log(logStr, Networking.ServerLog.MessageType.Attack); } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Throwable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Throwable.cs index 968ece938..7d31ae1d8 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Throwable.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Throwable.cs @@ -97,7 +97,7 @@ namespace Barotrauma.Items.Components Vector2 throwVector = picker.CursorWorldPosition - picker.WorldPosition; throwVector = Vector2.Normalize(throwVector); - GameServer.Log(picker.Name + " threw " + item.Name, ServerLog.MessageType.ItemInteraction); + GameServer.Log(picker.LogName + " threw " + item.Name, ServerLog.MessageType.ItemInteraction); item.Drop(); item.body.ApplyLinearImpulse(throwVector * throwForce * item.body.Mass * 3.0f); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs index d9f127d14..96b285ea4 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs @@ -105,7 +105,7 @@ namespace Barotrauma.Items.Components if (user != null) { - GameServer.Log(user.Name + (IsActive ? " activated " : " deactivated ") + item.Name, ServerLog.MessageType.ItemInteraction); + GameServer.Log(user.LogName + (IsActive ? " activated " : " deactivated ") + item.Name, ServerLog.MessageType.ItemInteraction); } #if CLIENT diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs index 299bdf083..9857fb052 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs @@ -159,7 +159,7 @@ namespace Barotrauma.Items.Components if (user != null) { - GameServer.Log(user.Name + " started fabricating " + selectedItem.TargetItem.Name + " in " + item.Name, ServerLog.MessageType.ItemInteraction); + GameServer.Log(user.LogName + " started fabricating " + selectedItem.TargetItem.Name + " in " + item.Name, ServerLog.MessageType.ItemInteraction); } #if CLIENT @@ -184,7 +184,7 @@ namespace Barotrauma.Items.Components { if (fabricatedItem != null && user != null) { - GameServer.Log(user.Name + " cancelled the fabrication of " + fabricatedItem.TargetItem.Name + " in " + item.Name, ServerLog.MessageType.ItemInteraction); + GameServer.Log(user.LogName + " cancelled the fabrication of " + fabricatedItem.TargetItem.Name + " in " + item.Name, ServerLog.MessageType.ItemInteraction); } IsActive = false; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Pump.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Pump.cs index 88e7b74eb..b99f8ef31 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Pump.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Pump.cs @@ -157,11 +157,11 @@ namespace Barotrauma.Items.Components { if (newFlowPercentage != FlowPercentage) { - GameServer.Log(c.Character + " set the pumping speed of " + item.Name + " to " + (int)(newFlowPercentage) + " %", ServerLog.MessageType.ItemInteraction); + GameServer.Log(c.Character.LogName + " set the pumping speed of " + item.Name + " to " + (int)(newFlowPercentage) + " %", ServerLog.MessageType.ItemInteraction); } if (newIsActive != IsActive) { - GameServer.Log(c.Character + (newIsActive ? " turned on " : " turned off ") + item.Name, ServerLog.MessageType.ItemInteraction); + GameServer.Log(c.Character.LogName + (newIsActive ? " turned on " : " turned off ") + item.Name, ServerLog.MessageType.ItemInteraction); } FlowPercentage = newFlowPercentage; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs index 9d25c3058..9e3b031f5 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs @@ -173,7 +173,7 @@ namespace Barotrauma.Items.Components { if (Timing.TotalTime >= (float)nextServerLogWriteTime) { - GameServer.Log(lastUser + " adjusted reactor settings: " + + GameServer.Log(lastUser.LogName + " adjusted reactor settings: " + "Temperature: " + (int)temperature + ", Fission rate: " + (int)fissionRate + ", Cooling rate: " + (int)coolingRate + diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs index be46a3539..47626ff3e 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs @@ -201,7 +201,7 @@ namespace Barotrauma.Items.Components if (item.CanClientAccess(c)) { RechargeSpeed = newRechargeSpeed; - GameServer.Log(c.Character + " set the recharge speed of "+item.Name+" to "+ (int)((rechargeSpeed / maxRechargeSpeed) * 100.0f) + " %", ServerLog.MessageType.ItemInteraction); + GameServer.Log(c.Character.LogName + " set the recharge speed of "+item.Name+" to "+ (int)((rechargeSpeed / maxRechargeSpeed) * 100.0f) + " %", ServerLog.MessageType.ItemInteraction); } item.CreateServerEvent(this); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs index cb4f18016..f82c732d3 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs @@ -215,7 +215,7 @@ namespace Barotrauma.Items.Components if (existingWire.Locked) { //this should not be possible unless the client is running a modified version of the game - GameServer.Log(c.Character.Name + " attempted to disconnect a locked wire from " + + GameServer.Log(c.Character.LogName + " attempted to disconnect a locked wire from " + Connections[i].Item.Name + " (" + Connections[i].Name + ")", ServerLog.MessageType.Error); continue; } @@ -224,12 +224,12 @@ namespace Barotrauma.Items.Components if (existingWire.Connections[0] == null && existingWire.Connections[1] == null) { - GameServer.Log(c.Character.Name + " disconnected a wire from " + + GameServer.Log(c.Character.LogName + " disconnected a wire from " + Connections[i].Item.Name + " (" + Connections[i].Name + ")", ServerLog.MessageType.ItemInteraction); } else if (existingWire.Connections[0] != null) { - GameServer.Log(c.Character.Name + " disconnected a wire from " + + GameServer.Log(c.Character.LogName + " disconnected a wire from " + Connections[i].Item.Name + " (" + Connections[i].Name + ") to " + existingWire.Connections[0].Item.Name + " (" + existingWire.Connections[0].Name + ")", ServerLog.MessageType.ItemInteraction); //wires that are not in anyone's inventory (i.e. not currently being rewired) @@ -244,7 +244,7 @@ namespace Barotrauma.Items.Components } else if (existingWire.Connections[1] != null) { - GameServer.Log(c.Character.Name + " disconnected a wire from " + + GameServer.Log(c.Character.LogName + " disconnected a wire from " + Connections[i].Item.Name + " (" + Connections[i].Name + ") to " + existingWire.Connections[1].Item.Name + " (" + existingWire.Connections[1].Name + ")", ServerLog.MessageType.ItemInteraction); if (existingWire.Item.ParentInventory == null) @@ -276,13 +276,13 @@ namespace Barotrauma.Items.Components if (otherConnection == null) { - GameServer.Log(c.Character.Name + " connected a wire to " + + GameServer.Log(c.Character.LogName + " connected a wire to " + Connections[i].Item.Name + " (" + Connections[i].Name + ")", ServerLog.MessageType.ItemInteraction); } else { - GameServer.Log(c.Character.Name + " connected a wire from " + + GameServer.Log(c.Character.LogName + " connected a wire from " + Connections[i].Item.Name + " (" + Connections[i].Name + ") to " + (otherConnection == null ? "none" : otherConnection.Item.Name + " (" + (otherConnection.Name) + ")"), ServerLog.MessageType.ItemInteraction); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs index 99cd54835..f32fa02d9 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs @@ -308,18 +308,18 @@ namespace Barotrauma.Items.Components { if (connections[0] != null && connections[1] != null) { - GameServer.Log(user.Name + " disconnected a wire from " + + GameServer.Log(user.LogName + " disconnected a wire from " + connections[0].Item.Name + " (" + connections[0].Name + ") to "+ connections[1].Item.Name + " (" + connections[1].Name + ")", ServerLog.MessageType.ItemInteraction); } else if (connections[0] != null) { - GameServer.Log(user.Name + " disconnected a wire from " + + GameServer.Log(user.LogName + " disconnected a wire from " + connections[0].Item.Name + " (" + connections[0].Name + ")", ServerLog.MessageType.ItemInteraction); } else if (connections[1] != null) { - GameServer.Log(user.Name + " disconnected a wire from " + + GameServer.Log(user.LogName + " disconnected a wire from " + connections[1].Item.Name + " (" + connections[1].Name + ")", ServerLog.MessageType.ItemInteraction); } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs index 5d3563d18..29d5c60e4 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs @@ -175,7 +175,7 @@ namespace Barotrauma.Items.Components if (character != null) { - string msg = character.Name + " launched " + item.Name + " (projectile: " + projectiles[0].Item.Name; + string msg = character.LogName + " launched " + item.Name + " (projectile: " + projectiles[0].Item.Name; if (projectiles[0].Item.ContainedItems == null || projectiles[0].Item.ContainedItems.All(i => i == null)) { msg += ")"; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs b/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs index ed782dd53..669b68f34 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs @@ -212,11 +212,11 @@ namespace Barotrauma { if (Owner == c.Character) { - GameServer.Log(c.Character + " picked up " + item.Name, ServerLog.MessageType.Inventory); + GameServer.Log(c.Character.LogName+ " picked up " + item.Name, ServerLog.MessageType.Inventory); } else { - GameServer.Log(c.Character + " placed " + item.Name + " in " + Owner, ServerLog.MessageType.Inventory); + GameServer.Log(c.Character.LogName + " placed " + item.Name + " in " + Owner, ServerLog.MessageType.Inventory); } } } @@ -227,11 +227,11 @@ namespace Barotrauma { if (Owner == c.Character) { - GameServer.Log(c.Character + " dropped " + item.Name, ServerLog.MessageType.Inventory); + GameServer.Log(c.Character.LogName + " dropped " + item.Name, ServerLog.MessageType.Inventory); } else { - GameServer.Log(c.Character + " removed " + item.Name + " from " + Owner, ServerLog.MessageType.Inventory); + GameServer.Log(c.Character.LogName + " removed " + item.Name + " from " + Owner, ServerLog.MessageType.Inventory); } } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index 1667a41bd..45161fefa 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -1361,12 +1361,12 @@ namespace Barotrauma if (ContainedItems == null || ContainedItems.All(i => i == null)) { - GameServer.Log(c.Character.Name + " used item " + Name, ServerLog.MessageType.ItemInteraction); + GameServer.Log(c.Character.LogName + " used item " + Name, ServerLog.MessageType.ItemInteraction); } else { GameServer.Log( - c.Character.Name + " used item " + Name + " (contained items: " + string.Join(", ", Array.FindAll(ContainedItems, i => i != null).Select(i => i.Name)) + ")", + c.Character.LogName + " used item " + Name + " (contained items: " + string.Join(", ", Array.FindAll(ContainedItems, i => i != null).Select(i => i.Name)) + ")", ServerLog.MessageType.ItemInteraction); } From af53449d2dbcef3a5351ca00a5ffe99567456050 Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Sat, 16 Dec 2017 20:38:27 +0300 Subject: [PATCH 22/53] Update the size of tools.png to 64x128 as according to juanjp600, powers of two for sizes are more efficient due to memory alignment or something. --- .../Content/Items/Tools/tools.png | Bin 9821 -> 9950 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.png b/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.png index 79f6c2c5f459bd15e9635156564015053ddd2e72..8cfb428349b362f84bea07bf5b2d08e82ea38160 100644 GIT binary patch delta 9400 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 delta 9247 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 Date: Sat, 16 Dec 2017 21:09:02 +0300 Subject: [PATCH 23/53] Expose Description to Sub Editor so you can do things like create spare ID cards or w/e --- Barotrauma/BarotraumaShared/Source/Items/Item.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index 45161fefa..8af99c2af 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -120,6 +120,7 @@ namespace Barotrauma } private string description; + [Editable, Serialize("", true)] public string Description { get { return description == null ? prefab.Description : description; } From 8ab97af4564b2a64c912f07a43b556032b1425ba Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Sat, 16 Dec 2017 21:30:18 +0300 Subject: [PATCH 24/53] Allow sub makers to alter the ID Card's description property on spawn points --- .../Source/Items/Inventory.cs | 2 +- .../BarotraumaClient/Source/Map/WayPoint.cs | 20 ++++++++++++++++++- .../Source/Characters/Jobs/Job.cs | 2 ++ .../BarotraumaShared/Source/Map/WayPoint.cs | 13 ++++++++++++ .../Source/Networking/RespawnManager.cs | 4 +++- 5 files changed, 38 insertions(+), 3 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs b/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs index b5204a9f0..166974b34 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs @@ -297,7 +297,7 @@ namespace Barotrauma else { string description = Items[i].Description; - if (Items[i].Name == "ID Card") + if (Items[i].Prefab.Name == "ID Card") { string[] readTags = Items[i].Tags.Split(','); string idName = null; diff --git a/Barotrauma/BarotraumaClient/Source/Map/WayPoint.cs b/Barotrauma/BarotraumaClient/Source/Map/WayPoint.cs index 983249d48..48213cbde 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/WayPoint.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/WayPoint.cs @@ -106,6 +106,16 @@ namespace Barotrauma return true; } + private bool EnterIDCardDesc(GUITextBox textBox, string text) + { + IdCardDesc = text; + textBox.Text = text; + textBox.Color = Color.Green; + + textBox.Deselect(); + + return true; + } private bool EnterIDCardTags(GUITextBox textBox, string text) { IdCardTags = text.Split(','); @@ -172,8 +182,16 @@ namespace Barotrauma y = 40 + 20; - new GUITextBlock(new Rectangle(0, y, 100, 20), "ID Card tags:", Color.Transparent, Color.White, Alignment.TopLeft, null, editingHUD); + new GUITextBlock(new Rectangle(0, y, 100, 20), "ID Card description:", Color.Transparent, Color.White, Alignment.TopLeft, null, editingHUD); GUITextBox propertyBox = new GUITextBox(new Rectangle(100, y, 200, 20), "", editingHUD); + propertyBox.Text = idCardDesc; + propertyBox.OnEnterPressed = EnterIDCardDesc; + propertyBox.OnTextChanged = TextBoxChanged; + propertyBox.ToolTip = "Characters spawning at this spawnpoint will have the specified description added to their ID card. This can be used to describe additional access levels their card has on the sub."; + + y = y + 30; + + new GUITextBlock(new Rectangle(0, y, 100, 20), "ID Card tags:", Color.Transparent, Color.White, Alignment.TopLeft, null, editingHUD); propertyBox.Text = string.Join(", ", idCardTags); propertyBox.OnEnterPressed = EnterIDCardTags; propertyBox.OnTextChanged = TextBoxChanged; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Jobs/Job.cs b/Barotrauma/BarotraumaShared/Source/Characters/Jobs/Job.cs index 865e6bcbc..04271943d 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Jobs/Job.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Jobs/Job.cs @@ -132,6 +132,8 @@ namespace Barotrauma } item.AddTag("name:" + character.Name); item.AddTag("job:" + Name); + if (!string.IsNullOrWhiteSpace(spawnPoint.IdCardDesc)) + item.Description = spawnPoint.IdCardDesc; } if (parentItem != null) parentItem.Combine(item); diff --git a/Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs b/Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs index 809426c4c..712ce55dc 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs @@ -21,6 +21,7 @@ namespace Barotrauma protected SpawnType spawnType; //characters spawning at the waypoint will be given an ID card with these tags + private string idCardDesc; private string[] idCardTags; //only characters with this job will be spawned at the waypoint @@ -57,6 +58,11 @@ namespace Barotrauma } } + public string IdCardDesc + { + get { return idCardDesc; } + private set { idCardDesc = value; } + } public string[] IdCardTags { get { return idCardTags; } @@ -113,6 +119,7 @@ namespace Barotrauma public override MapEntity Clone() { var clone = new WayPoint(rect, Submarine); + clone.idCardDesc = idCardDesc; clone.idCardTags = idCardTags; clone.spawnType = spawnType; clone.assignedJob = assignedJob; @@ -570,6 +577,11 @@ namespace Barotrauma Enum.TryParse(element.GetAttributeString("spawn", "Path"), out w.spawnType); + string idCardDescString = element.GetAttributeString("idcarddesc", ""); + if (!string.IsNullOrWhiteSpace(idCardDescString)) + { + w.IdCardDesc = idCardDescString; + } string idCardTagString = element.GetAttributeString("idcardtags", ""); if (!string.IsNullOrWhiteSpace(idCardTagString)) { @@ -604,6 +616,7 @@ namespace Barotrauma new XAttribute("y", (int)(rect.Y - Submarine.HiddenSubPosition.Y)), new XAttribute("spawn", spawnType)); + if (idCardDesc != null) element.Add(new XAttribute("idcarddesc", idCardDesc)); if (idCardTags.Length > 0) { element.Add(new XAttribute("idcardtags", string.Join(",", idCardTags))); diff --git a/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs b/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs index 44e8d777c..90cafee63 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs @@ -533,7 +533,9 @@ namespace Barotrauma.Networking foreach (string s in shuttleSpawnPoints[i].IdCardTags) { item.AddTag(s); - } + } + if (!string.IsNullOrWhiteSpace(shuttleSpawnPoints[i].IdCardDesc)) + item.Description = shuttleSpawnPoints[i].IdCardDesc; } #if CLIENT GameMain.GameSession.CrewManager.AddCharacter(character); From 0ce9acb76a6c7684be2bee5868b85654b6a65723 Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Sat, 16 Dec 2017 21:45:30 +0300 Subject: [PATCH 25/53] fix things --- Barotrauma/BarotraumaClient/Source/Map/WayPoint.cs | 12 ++++++++---- Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Map/WayPoint.cs b/Barotrauma/BarotraumaClient/Source/Map/WayPoint.cs index 48213cbde..cf1f7a269 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/WayPoint.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/WayPoint.cs @@ -153,7 +153,7 @@ namespace Barotrauma private GUIComponent CreateEditingHUD(bool inGame = false) { int width = 500; - int height = spawnType == SpawnType.Path ? 100 : 140; + int height = spawnType == SpawnType.Path ? 100 : 200; int x = GameMain.GraphicsWidth / 2 - width / 2, y = 10; editingHUD = new GUIFrame(new Rectangle(x, y, width, height), Color.Black * 0.5f); @@ -182,8 +182,9 @@ namespace Barotrauma y = 40 + 20; - new GUITextBlock(new Rectangle(0, y, 100, 20), "ID Card description:", Color.Transparent, Color.White, Alignment.TopLeft, null, editingHUD); - GUITextBox propertyBox = new GUITextBox(new Rectangle(100, y, 200, 20), "", editingHUD); + new GUITextBlock(new Rectangle(0, y, 100, 20), "ID Card desc:", Color.Transparent, Color.White, Alignment.TopLeft, null, editingHUD); + GUITextBox propertyBox = new GUITextBox(new Rectangle(100, y, 350, 20), "", editingHUD); + propertyBox.MaxTextLength = 150; propertyBox.Text = idCardDesc; propertyBox.OnEnterPressed = EnterIDCardDesc; propertyBox.OnTextChanged = TextBoxChanged; @@ -192,6 +193,8 @@ namespace Barotrauma y = y + 30; new GUITextBlock(new Rectangle(0, y, 100, 20), "ID Card tags:", Color.Transparent, Color.White, Alignment.TopLeft, null, editingHUD); + propertyBox = new GUITextBox(new Rectangle(100, y, 350, 20), "", editingHUD); + propertyBox.MaxTextLength = 60; propertyBox.Text = string.Join(", ", idCardTags); propertyBox.OnEnterPressed = EnterIDCardTags; propertyBox.OnTextChanged = TextBoxChanged; @@ -200,7 +203,8 @@ namespace Barotrauma y = y + 30; new GUITextBlock(new Rectangle(0, y, 100, 20), "Assigned job:", Color.Transparent, Color.White, Alignment.TopLeft, null, editingHUD); - propertyBox = new GUITextBox(new Rectangle(100, y, 200, 20), "", editingHUD); + propertyBox = new GUITextBox(new Rectangle(100, y, 350, 20), "", editingHUD); + propertyBox.MaxTextLength = 60; propertyBox.Text = (assignedJob == null) ? "None" : assignedJob.Name; propertyBox.OnEnterPressed = EnterAssignedJob; propertyBox.OnTextChanged = TextBoxChanged; diff --git a/Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs b/Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs index 712ce55dc..65fb6bc8c 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs @@ -616,7 +616,7 @@ namespace Barotrauma new XAttribute("y", (int)(rect.Y - Submarine.HiddenSubPosition.Y)), new XAttribute("spawn", spawnType)); - if (idCardDesc != null) element.Add(new XAttribute("idcarddesc", idCardDesc)); + if (!string.IsNullOrWhiteSpace(idCardDesc)) element.Add(new XAttribute("idcarddesc", idCardDesc)); if (idCardTags.Length > 0) { element.Add(new XAttribute("idcardtags", string.Join(",", idCardTags))); From 828bde6f0478fc0f1de41fe6e2edda2456060b96 Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Sun, 17 Dec 2017 14:18:30 +0300 Subject: [PATCH 26/53] Make flashlight's idle holding position similar to that of oxygen/welding tanks so you can still use them unaimed --- Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml b/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml index 79c955152..5e0c69ecf 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml @@ -248,7 +248,7 @@ - + From d5e409bfff929556c48f5f2f15263ba361e40597 Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Sun, 17 Dec 2017 14:55:35 +0300 Subject: [PATCH 27/53] Increase muffling effects with obstruction for spoken speech (barely affects radio chat and wifi components, however it does add extra 100f range to the distance) Decreased required muffling to 0.3f Added an "obstructionmult" to intensify speaking obstruction effects --- .../BarotraumaShared/Source/Networking/ChatMessage.cs | 6 +++--- Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Networking/ChatMessage.cs b/Barotrauma/BarotraumaShared/Source/Networking/ChatMessage.cs index 6b7a2333f..e69dbf80f 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/ChatMessage.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/ChatMessage.cs @@ -105,18 +105,18 @@ namespace Barotrauma.Networking return ApplyDistanceEffect(listener, Sender, Text, SpeakRange); } - public static string ApplyDistanceEffect(Entity listener, Entity Sender, string text, float range) + public static string ApplyDistanceEffect(Entity listener, Entity Sender, string text, float range, float obstructionmult = 2.0f) { if (listener.WorldPosition == Sender.WorldPosition) return text; float dist = Vector2.Distance(listener.WorldPosition, Sender.WorldPosition); if (dist > range) return ""; - if (Submarine.CheckVisibility(listener.SimPosition, Sender.SimPosition) != null) dist *= 2.0f; + if (Submarine.CheckVisibility(listener.SimPosition, Sender.SimPosition) != null) dist = (dist + 100f) * obstructionmult; if (dist > range) return ""; float garbleAmount = dist / range; - if (garbleAmount < 0.5f) return text; + if (garbleAmount < 0.3f) return text; int startIndex = Math.Max(text.IndexOf(':') + 1, 1); diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs index cec645624..b40f789c1 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs @@ -1848,7 +1848,7 @@ namespace Barotrauma.Networking case ChatMessageType.Default: if (!receiver.IsDead) { - return ChatMessage.ApplyDistanceEffect(receiver, sender, message, ChatMessage.SpeakRange); + return ChatMessage.ApplyDistanceEffect(receiver, sender, message, ChatMessage.SpeakRange, 3.0f); } break; case ChatMessageType.Radio: From 4f7c805a9c515a5af9530afaf366948baa98d60c Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Sun, 17 Dec 2017 15:17:15 +0300 Subject: [PATCH 28/53] Hide character names/health/etc. if you can't see their head or it doesn't exist. TODO: use Line of Sight instead of CheckVisibility --- Barotrauma/BarotraumaClient/Source/Characters/Character.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Character.cs b/Barotrauma/BarotraumaClient/Source/Characters/Character.cs index ddcc3112f..aaf6a7bba 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Character.cs @@ -296,11 +296,18 @@ namespace Barotrauma if (this == controlled) return; + //Ideally it shouldn't send the character entirely if we can't see them but /shrug, this isn't the most hacker-proof game atm + Limb selfHead = controlled != null ? controlled.AnimController.GetLimb(LimbType.Head) : null; + Limb targHead = this.AnimController.GetLimb(LimbType.Head); + if (controlled != null && selfHead != null && targHead != null && Submarine.CheckVisibility(selfHead.SimPosition, targHead.SimPosition) != null) //TODO: use Line of Sight instead of CheckVisibility + return; + if (info != null) { string name = Info.DisplayName; if (controlled == null && name != Info.Name) name += " (Disguised)"; + Vector2 namePos = new Vector2(pos.X, pos.Y - 110.0f - (5.0f / cam.Zoom)) - GUI.Font.MeasureString(name) * 0.5f / cam.Zoom; Color nameColor = Color.White; From a620b0683177e6178f00397397c6ef6aa090d7df Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Sun, 17 Dec 2017 15:49:16 +0300 Subject: [PATCH 29/53] Play a loud sound when the door is crowbarred open so everyone is alerted! (Security should know when someonoe is breaking into places without ID card) TODO: use bump.ogg when wrenching/crowbarring is initiated --- .../BarotraumaShared/BarotraumaShared.projitems | 6 ++++++ .../Content/Items/Door/doors.xml | 9 ++++++--- .../Content/Items/Tools/bump.ogg | Bin 0 -> 5250 bytes .../Content/Items/Tools/crowbar.ogg | Bin 0 -> 24035 bytes .../Source/Items/Components/Door.cs | 3 +++ .../Items/Components/Holdable/Pickable.cs | 1 + 6 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 Barotrauma/BarotraumaShared/Content/Items/Tools/bump.ogg create mode 100644 Barotrauma/BarotraumaShared/Content/Items/Tools/crowbar.ogg diff --git a/Barotrauma/BarotraumaShared/BarotraumaShared.projitems b/Barotrauma/BarotraumaShared/BarotraumaShared.projitems index 5a76b4df1..91e79eb92 100644 --- a/Barotrauma/BarotraumaShared/BarotraumaShared.projitems +++ b/Barotrauma/BarotraumaShared/BarotraumaShared.projitems @@ -976,6 +976,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/Barotrauma/BarotraumaShared/Content/Items/Door/doors.xml b/Barotrauma/BarotraumaShared/Content/Items/Door/doors.xml index 043507a2d..476f8a787 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Door/doors.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Door/doors.xml @@ -6,12 +6,13 @@ - + + @@ -36,12 +37,13 @@ - + + @@ -66,12 +68,13 @@ - + + diff --git a/Barotrauma/BarotraumaShared/Content/Items/Tools/bump.ogg b/Barotrauma/BarotraumaShared/Content/Items/Tools/bump.ogg new file mode 100644 index 0000000000000000000000000000000000000000..d3cd6041a407e6f4d990e3a3249bbdb29d307d0f GIT binary patch literal 5250 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 VX$<~6xKj@iGhL3I0?Ynx{{Z;l99{qb literal 0 HcmV?d00001 diff --git a/Barotrauma/BarotraumaShared/Content/Items/Tools/crowbar.ogg b/Barotrauma/BarotraumaShared/Content/Items/Tools/crowbar.ogg new file mode 100644 index 0000000000000000000000000000000000000000..a0f6049535ad36e9fb2059963ba7283753e8b609 GIT binary patch literal 24035 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 OHMgn{z#{>8KmiU>zhZ^} literal 0 HcmV?d00001 diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs index 0f52ffcfe..fbb0e3fe4 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs @@ -206,6 +206,9 @@ namespace Barotrauma.Items.Components public override bool OnPicked(Character picker) { SetState(predictedState == null ? !isOpen : !predictedState.Value, false, true); //crowbar function +#if CLIENT + PlaySound(ActionType.OnPicked, item.WorldPosition); +#endif return false; } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Pickable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Pickable.cs index 7dba6ac00..69e06f783 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Pickable.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Pickable.cs @@ -89,6 +89,7 @@ namespace Barotrauma.Items.Components #if CLIENT if (!GameMain.Instance.LoadingScreenOpen && picker == Character.Controlled) GUI.PlayUISound(GUISoundType.PickItem); + PlaySound(ActionType.OnPicked, item.WorldPosition); #endif return true; From 6b8bd59656ee8e8adaeb40474b5be21245fdb0ff Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Sun, 17 Dec 2017 15:52:48 +0300 Subject: [PATCH 30/53] Let mechanics spawn with crowbars --- Barotrauma/BarotraumaShared/Content/Jobs.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/Barotrauma/BarotraumaShared/Content/Jobs.xml b/Barotrauma/BarotraumaShared/Content/Jobs.xml index c46354b4e..4ed0e602f 100644 --- a/Barotrauma/BarotraumaShared/Content/Jobs.xml +++ b/Barotrauma/BarotraumaShared/Content/Jobs.xml @@ -49,6 +49,7 @@ + From 0eacbce313b795708d6fb2c07af58d48f793bec6 Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Sun, 17 Dec 2017 16:32:25 +0300 Subject: [PATCH 31/53] Tweaked crowbar to be a powerful two-handed weapon instead --- Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml | 8 ++++---- .../Source/Items/Components/Holdable/MeleeWeapon.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml b/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml index 5e0c69ecf..fc2150ced 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml @@ -190,15 +190,15 @@ category="Equipment" Tags="smallitem" - price="10"> + price="15"> - - + + diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/MeleeWeapon.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/MeleeWeapon.cs index 07aa6982a..38f8ca61a 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/MeleeWeapon.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/MeleeWeapon.cs @@ -137,7 +137,7 @@ namespace Barotrauma.Items.Components } else { - ac.HoldItem(deltaTime, item, handlePos, new Vector2(hitPos, 0.0f), aimPos, false, holdAngle); + ac.HoldItem(deltaTime, item, handlePos, holdPos, aimPos, false, holdAngle); } } else From 714b52dce80b01bf6493c8cca6067aecab3b213f Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Sun, 17 Dec 2017 18:05:05 +0300 Subject: [PATCH 32/53] Fixed several sound-related issues that have been bothering me for ages, e.g. * Sounds not playing when the structure is fully destroyed * Glass windows using wrong sound effects * Only one glassBreak sound was used despite there being 3 --- .../BarotraumaClient/Source/Sounds/SoundPlayer.cs | 7 ++++--- .../BarotraumaShared/Content/Sounds/sounds.xml | 4 ++-- Barotrauma/BarotraumaShared/Source/Map/Structure.cs | 12 ++++++------ 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Sounds/SoundPlayer.cs b/Barotrauma/BarotraumaClient/Source/Sounds/SoundPlayer.cs index 8442023e4..9f7055eae 100644 --- a/Barotrauma/BarotraumaClient/Source/Sounds/SoundPlayer.cs +++ b/Barotrauma/BarotraumaClient/Source/Sounds/SoundPlayer.cs @@ -453,10 +453,11 @@ namespace Barotrauma { damage = MathHelper.Clamp(damage+Rand.Range(-10.0f, 10.0f), 0.0f, 100.0f); var sounds = damageSounds.FindAll(s => - damage >= s.damageRange.X && - damage <= s.damageRange.Y && + s.damageRange == null || + (damage >= s.damageRange.X && + damage <= s.damageRange.Y) && s.damageType == damageType && - (string.IsNullOrEmpty(s.requiredTag) || (tags != null && tags.Contains(s.requiredTag)))); + (tags == null ? string.IsNullOrEmpty(s.requiredTag) : tags.Contains(s.requiredTag))); if (!sounds.Any()) return; diff --git a/Barotrauma/BarotraumaShared/Content/Sounds/sounds.xml b/Barotrauma/BarotraumaShared/Content/Sounds/sounds.xml index 8aae1829b..d4156d5d9 100644 --- a/Barotrauma/BarotraumaShared/Content/Sounds/sounds.xml +++ b/Barotrauma/BarotraumaShared/Content/Sounds/sounds.xml @@ -16,8 +16,8 @@ - - + + diff --git a/Barotrauma/BarotraumaShared/Source/Map/Structure.cs b/Barotrauma/BarotraumaShared/Source/Map/Structure.cs index a9f7f8330..46a7fd781 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Structure.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Structure.cs @@ -506,15 +506,15 @@ namespace Barotrauma float impact = Vector2.Dot(f2.Body.LinearVelocity, -normal)*f2.Body.Mass*0.1f; - if (impact < 10.0f) return true; - #if CLIENT SoundPlayer.PlayDamageSound(DamageSoundType.StructureBlunt, impact, new Vector2( - sections[section].rect.X + sections[section].rect.Width / 2, - sections[section].rect.Y - sections[section].rect.Height / 2)); + sections[section].rect.X + sections[section].rect.Width / 2, + sections[section].rect.Y - sections[section].rect.Height / 2), tags: Tags); #endif + if (impact < 10.0f) return true; + AddDamage(section, impact); } } @@ -661,10 +661,10 @@ namespace Barotrauma #if CLIENT GameMain.ParticleManager.CreateParticle("dustcloud", SectionPosition(i), 0.0f, 0.0f); - if (playSound && !SectionBodyDisabled(i)) + if (playSound)// && !SectionBodyDisabled(i)) { DamageSoundType damageSoundType = (attack.DamageType == DamageType.Blunt) ? DamageSoundType.StructureBlunt : DamageSoundType.StructureSlash; - SoundPlayer.PlayDamageSound(damageSoundType, damageAmount, worldPosition); + SoundPlayer.PlayDamageSound(damageSoundType, damageAmount, worldPosition, tags: Tags); } #endif From e35fe18662813d2d3bff2b02e3117c2a95414192 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Sun, 17 Dec 2017 19:33:53 +0200 Subject: [PATCH 33/53] Carrier's "dummy limb" with a light source cannot be severed. Closes #154 --- .../BarotraumaShared/Content/Characters/Carrier/carrier.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/Content/Characters/Carrier/carrier.xml b/Barotrauma/BarotraumaShared/Content/Characters/Carrier/carrier.xml index 9377d3d06..81a8367e1 100644 --- a/Barotrauma/BarotraumaShared/Content/Characters/Carrier/carrier.xml +++ b/Barotrauma/BarotraumaShared/Content/Characters/Carrier/carrier.xml @@ -61,7 +61,7 @@ - + From 041d1112b89475c424ee0cf10baf1d66f32011a3 Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Sun, 17 Dec 2017 21:35:19 +0300 Subject: [PATCH 34/53] Added a timer to the raycasts so it's not screwing over performance too hard --- .../Source/Characters/Character.cs | 32 ++++++++++++++++--- .../Source/Items/Inventory.cs | 2 +- .../Source/Items/Components/Wearable.cs | 4 +-- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Character.cs b/Barotrauma/BarotraumaClient/Source/Characters/Character.cs index aaf6a7bba..da4bf9838 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Character.cs @@ -15,6 +15,8 @@ namespace Barotrauma { protected float soundTimer; protected float soundInterval; + protected float nameTimer; + protected bool nameVisible; private List sounds; @@ -295,11 +297,33 @@ namespace Barotrauma } if (this == controlled) return; + + nameTimer -= (float)Timing.Step; + if (nameTimer <= 0.0f) + { + //Ideally it shouldn't send the character entirely if we can't see them but /shrug, this isn't the most hacker-proof game atm + Limb selfHead = controlled != null ? controlled.AnimController.GetLimb(LimbType.Head) : null; + Limb targHead = this.AnimController.GetLimb(LimbType.Head); - //Ideally it shouldn't send the character entirely if we can't see them but /shrug, this isn't the most hacker-proof game atm - Limb selfHead = controlled != null ? controlled.AnimController.GetLimb(LimbType.Head) : null; - Limb targHead = this.AnimController.GetLimb(LimbType.Head); - if (controlled != null && selfHead != null && targHead != null && Submarine.CheckVisibility(selfHead.SimPosition, targHead.SimPosition) != null) //TODO: use Line of Sight instead of CheckVisibility + if (controlled != null && controlled.Submarine == Submarine) + { + if (selfHead != null && targHead != null) + { + Body closestBody = Submarine.CheckVisibility(selfHead.SimPosition, targHead.SimPosition); + Structure wall = null; + if (closestBody != null) + wall = closestBody.UserData as Structure; + nameVisible = closestBody == null || wall == null || !wall.CastShadow; + } + else + nameVisible = false; + } + else + nameVisible = true; //Ideally it should check for visibility from outside the sub/from sub-to-sub, but this will work for now. + nameTimer = Rand.Range(0.5f, 2f); + } + + if (!nameVisible) return; if (info != null) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs b/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs index 166974b34..377c51275 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs @@ -297,7 +297,7 @@ namespace Barotrauma else { string description = Items[i].Description; - if (Items[i].Prefab.Name == "ID Card") + if (Items[i].Prefab.NameMatches("ID Card")) { string[] readTags = Items[i].Tags.Split(','); string idName = null; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Wearable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Wearable.cs index 248d4257d..168387bc8 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Wearable.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Wearable.cs @@ -31,14 +31,14 @@ namespace Barotrauma.Items.Components private WearableSprite[] wearableSprites; private LimbType[] limbType; private Limb[] limb; + private List damageModifiers; public List DamageModifiers { get { return damageModifiers; } } - - public bool Disguise; + public Wearable (Item item, XElement element) : base(item, element) { From a5d6da31a40eb88a008f7e9150c84f90207c1309 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Sun, 17 Dec 2017 20:42:31 +0200 Subject: [PATCH 35/53] Holdable items can be held in any limb slot, not just hands. + Characters can hold a flashlight in their mouth. --- .../Content/Items/Tools/tools.xml | 6 +-- .../Animation/HumanoidAnimController.cs | 4 +- .../Items/Components/Holdable/Holdable.cs | 38 +++++++++++++++++-- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml b/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml index 0d6c81251..c59cedf22 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml @@ -218,8 +218,7 @@ @@ -231,12 +230,11 @@ - + - diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs index f6962526c..9dbaf2193 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs @@ -1011,8 +1011,6 @@ namespace Barotrauma public override void HoldItem(float deltaTime, Item item, Vector2[] handlePos, Vector2 holdPos, Vector2 aimPos, bool aim, float holdAngle) { - Holdable holdable = item.GetComponent(); - if (character.IsUnconscious || character.Stun > 0.0f) aim = false; //calculate the handle positions @@ -1030,7 +1028,6 @@ namespace Barotrauma bool usingController = character.SelectedConstruction != null && character.SelectedConstruction.GetComponent() != null; - float itemAngle; if (Anim != Animation.Climbing && !usingController && character.Stun <= 0.0f && aim && itemPos != Vector2.Zero) { @@ -1042,6 +1039,7 @@ namespace Barotrauma itemAngle = (torso.body.Rotation + holdAngle * Dir); + Holdable holdable = item.GetComponent(); if (holdable.ControlPose) { head.body.SmoothRotate(itemAngle); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs index 73d99dedc..4c67bfcdf 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs @@ -184,8 +184,8 @@ namespace Barotrauma.Items.Components item.SetTransform(rightHand.SimPosition, 0.0f); } - bool alreadySelected = character.HasSelectedItem(item); - if (picker.TrySelectItem(item)) + bool alreadySelected = character.HasEquippedItem(item); + if (picker.TrySelectItem(item) || picker.HasEquippedItem(item)) { item.body.Enabled = true; IsActive = true; @@ -308,7 +308,7 @@ namespace Barotrauma.Items.Components public override void Update(float deltaTime, Camera cam) { if (item.body == null || !item.body.Enabled) return; - if (picker == null || !picker.HasSelectedItem(item)) + if (picker == null || !picker.HasEquippedItem(item)) { IsActive = false; return; @@ -320,7 +320,37 @@ namespace Barotrauma.Items.Components item.Submarine = picker.Submarine; - picker.AnimController.HoldItem(deltaTime, item, handlePos, holdPos, aimPos, picker.IsKeyDown(InputType.Aim), holdAngle); + if (picker.HasSelectedItem(item)) + { + picker.AnimController.HoldItem(deltaTime, item, handlePos, holdPos, aimPos, picker.IsKeyDown(InputType.Aim), holdAngle); + } + else + { + Limb equipLimb = null; + if (picker.Inventory.IsInLimbSlot(item, InvSlotType.Face) || picker.Inventory.IsInLimbSlot(item, InvSlotType.Head)) + { + equipLimb = picker.AnimController.GetLimb(LimbType.Head); + } + else if (picker.Inventory.IsInLimbSlot(item, InvSlotType.Torso)) + { + equipLimb = picker.AnimController.GetLimb(LimbType.Torso); + } + else if (picker.Inventory.IsInLimbSlot(item, InvSlotType.Legs)) + { + equipLimb = picker.AnimController.GetLimb(LimbType.Waist); + } + + if (equipLimb != null) + { + float itemAngle = (equipLimb.Rotation + holdAngle * picker.AnimController.Dir); + + Matrix itemTransfrom = Matrix.CreateRotationZ(equipLimb.Rotation); + Vector2 transformedHandlePos = Vector2.Transform(handlePos[0], itemTransfrom); + + item.body.ResetDynamics(); + item.SetTransform(equipLimb.SimPosition - transformedHandlePos, itemAngle); + } + } } protected void Flip(Item item) From 6127bb473d9c13e47a05e0937b84eccb95fba7e8 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Sun, 17 Dec 2017 21:47:03 +0200 Subject: [PATCH 36/53] - Moved character nametag visibility culling to the update method (doesn't make sense for the update rate to be tied to rendering framerate). - Name tags on characters that are outside the camera view are automatically hidden to avoid the costly visibility checks. - If the controlled character or the target has no head, the visibility check is done based on the position of the torso. - Fixed sub->outside visibility checks. - Fixed invalid attributes in the flashlight config. --- .../Source/Characters/Character.cs | 113 +++++++----------- .../Content/Items/Tools/tools.xml | 2 +- .../Source/Characters/Character.cs | 4 + 3 files changed, 49 insertions(+), 70 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Character.cs b/Barotrauma/BarotraumaClient/Source/Characters/Character.cs index da4bf9838..bd5705ffb 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Character.cs @@ -204,6 +204,48 @@ namespace Barotrauma if (Lights.LightManager.ViewTarget == this) Lights.LightManager.ViewTarget = null; } + partial void UpdateProjSpecific(float deltaTime, Camera cam) + { + nameTimer -= deltaTime; + if (nameTimer <= 0.0f) + { + if (controlled == null) + { + nameVisible = true; + } + //if the character is not in the camera view, the name can't be visible and we can avoid the expensive visibility checks + else if (WorldPosition.X < cam.WorldView.X || WorldPosition.X > cam.WorldView.Right || + WorldPosition.Y > cam.WorldView.Y || WorldPosition.Y < cam.WorldView.Y - cam.WorldView.Height) + { + nameVisible = false; + } + else + { + //Ideally it shouldn't send the character entirely if we can't see them but /shrug, this isn't the most hacker-proof game atm + Limb selfLimb = controlled.AnimController.GetLimb(LimbType.Head); + if (selfLimb == null) selfLimb = controlled.AnimController.GetLimb(LimbType.Torso); + + Limb targHead = AnimController.GetLimb(LimbType.Head); + if (targHead == null) targHead = AnimController.GetLimb(LimbType.Torso); + + if (selfLimb != null && targHead != null) + { + Vector2 diff = ConvertUnits.ToSimUnits(targHead.WorldPosition - selfLimb.WorldPosition); + + Body closestBody = Submarine.CheckVisibility(selfLimb.SimPosition, selfLimb.SimPosition + diff); + Structure wall = null; + if (closestBody != null) wall = closestBody.UserData as Structure; + nameVisible = closestBody == null || wall == null || !wall.CastShadow; + } + else + { + nameVisible = false; + } + } + nameTimer = Rand.Range(0.5f, 1.0f); + } + } + public static void AddAllToGUIUpdateList() { for (int i = 0; i < CharacterList.Count; i++) @@ -242,48 +284,7 @@ namespace Barotrauma if (aiTarget != null) aiTarget.Draw(spriteBatch); } - - /*if (memPos != null && memPos.Count > 0 && controlled == this) - { - PosInfo serverPos = memPos.Last(); - Vector2 remoteVec = ConvertUnits.ToDisplayUnits(serverPos.Position); - if (Submarine != null) - { - remoteVec += Submarine.DrawPosition; - } - remoteVec.Y = -remoteVec.Y; - - PosInfo localPos = memLocalPos.Find(m => m.ID == serverPos.ID); - int mpind = memLocalPos.FindIndex(lp => lp.ID == localPos.ID); - PosInfo localPos1 = mpind > 0 ? memLocalPos[mpind - 1] : null; - PosInfo localPos2 = mpind < memLocalPos.Count-1 ? memLocalPos[mpind + 1] : null; - - Vector2 localVec = ConvertUnits.ToDisplayUnits(localPos.Position); - Vector2 localVec1 = localPos1 != null ? ConvertUnits.ToDisplayUnits(((PosInfo)localPos1).Position) : Vector2.Zero; - Vector2 localVec2 = localPos2 != null ? ConvertUnits.ToDisplayUnits(((PosInfo)localPos2).Position) : Vector2.Zero; - if (Submarine != null) - { - localVec += Submarine.DrawPosition; - localVec1 += Submarine.DrawPosition; - localVec2 += Submarine.DrawPosition; - } - localVec.Y = -localVec.Y; - localVec1.Y = -localVec1.Y; - localVec2.Y = -localVec2.Y; - - //GUI.DrawLine(spriteBatch, remoteVec, localVec, Color.Yellow, 0, 10); - if (localPos1 != null) GUI.DrawLine(spriteBatch, remoteVec, localVec1, Color.Lime, 0, 2); - if (localPos2 != null) GUI.DrawLine(spriteBatch, remoteVec + Vector2.One, localVec2 + Vector2.One, Color.Red, 0, 2); - } - - Vector2 mouseDrawPos = CursorWorldPosition; - mouseDrawPos.Y = -mouseDrawPos.Y; - GUI.DrawLine(spriteBatch, mouseDrawPos - new Vector2(0, 5), mouseDrawPos + new Vector2(0, 5), Color.Red, 0, 10); - - Vector2 closestItemPos = closestItem != null ? closestItem.DrawPosition : Vector2.Zero; - closestItemPos.Y = -closestItemPos.Y; - GUI.DrawLine(spriteBatch, closestItemPos - new Vector2(0, 5), closestItemPos + new Vector2(0, 5), Color.Lime, 0, 10);*/ - + if (this == controlled || GUI.DisableHUD) return; Vector2 pos = DrawPosition; @@ -298,33 +299,7 @@ namespace Barotrauma if (this == controlled) return; - nameTimer -= (float)Timing.Step; - if (nameTimer <= 0.0f) - { - //Ideally it shouldn't send the character entirely if we can't see them but /shrug, this isn't the most hacker-proof game atm - Limb selfHead = controlled != null ? controlled.AnimController.GetLimb(LimbType.Head) : null; - Limb targHead = this.AnimController.GetLimb(LimbType.Head); - - if (controlled != null && controlled.Submarine == Submarine) - { - if (selfHead != null && targHead != null) - { - Body closestBody = Submarine.CheckVisibility(selfHead.SimPosition, targHead.SimPosition); - Structure wall = null; - if (closestBody != null) - wall = closestBody.UserData as Structure; - nameVisible = closestBody == null || wall == null || !wall.CastShadow; - } - else - nameVisible = false; - } - else - nameVisible = true; //Ideally it should check for visibility from outside the sub/from sub-to-sub, but this will work for now. - nameTimer = Rand.Range(0.5f, 2f); - } - - if (!nameVisible) - return; + if (!nameVisible) return; if (info != null) { diff --git a/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml b/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml index 417336892..1c05f3b14 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml @@ -247,7 +247,7 @@ - + diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs index 5477b49ae..dc04384ba 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs @@ -1400,6 +1400,8 @@ namespace Barotrauma public virtual void Update(float deltaTime, Camera cam) { + UpdateProjSpecific(deltaTime, cam); + if (GameMain.Client != null && this == Controlled && !isSynced) return; if (!Enabled) return; @@ -1542,6 +1544,8 @@ namespace Barotrauma partial void UpdateControlled(float deltaTime, Camera cam); + partial void UpdateProjSpecific(float deltaTime, Camera cam); + private void UpdateOxygen(float deltaTime) { float prevOxygen = oxygen; From e5c49d929d306a9a390fa754a9c7881f1f64a985 Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Sun, 17 Dec 2017 23:13:50 +0300 Subject: [PATCH 37/53] 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 38/53] 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 26503a975cd837c30271d29776d8861c96ee5497 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Sun, 17 Dec 2017 22:34:24 +0200 Subject: [PATCH 39/53] Fixed sub->sub and outside->sub visibility checks when determining name tag visibility. --- .../Source/Characters/Character.cs | 55 +++++++------------ .../Source/Characters/Character.cs | 51 +++++++++++++++++ 2 files changed, 70 insertions(+), 36 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Character.cs b/Barotrauma/BarotraumaClient/Source/Characters/Character.cs index bd5705ffb..8f5912e56 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Character.cs @@ -206,43 +206,29 @@ namespace Barotrauma partial void UpdateProjSpecific(float deltaTime, Camera cam) { - nameTimer -= deltaTime; - if (nameTimer <= 0.0f) + if (info != null) { - if (controlled == null) + nameTimer -= deltaTime; + if (nameTimer <= 0.0f) { - nameVisible = true; - } - //if the character is not in the camera view, the name can't be visible and we can avoid the expensive visibility checks - else if (WorldPosition.X < cam.WorldView.X || WorldPosition.X > cam.WorldView.Right || - WorldPosition.Y > cam.WorldView.Y || WorldPosition.Y < cam.WorldView.Y - cam.WorldView.Height) - { - nameVisible = false; - } - else - { - //Ideally it shouldn't send the character entirely if we can't see them but /shrug, this isn't the most hacker-proof game atm - Limb selfLimb = controlled.AnimController.GetLimb(LimbType.Head); - if (selfLimb == null) selfLimb = controlled.AnimController.GetLimb(LimbType.Torso); - - Limb targHead = AnimController.GetLimb(LimbType.Head); - if (targHead == null) targHead = AnimController.GetLimb(LimbType.Torso); - - if (selfLimb != null && targHead != null) + if (controlled == null) { - Vector2 diff = ConvertUnits.ToSimUnits(targHead.WorldPosition - selfLimb.WorldPosition); - - Body closestBody = Submarine.CheckVisibility(selfLimb.SimPosition, selfLimb.SimPosition + diff); - Structure wall = null; - if (closestBody != null) wall = closestBody.UserData as Structure; - nameVisible = closestBody == null || wall == null || !wall.CastShadow; + nameVisible = true; } - else + + //if the character is not in the camera view, the name can't be visible and we can avoid the expensive visibility checks + else if (WorldPosition.X < cam.WorldView.X || WorldPosition.X > cam.WorldView.Right || + WorldPosition.Y > cam.WorldView.Y || WorldPosition.Y < cam.WorldView.Y - cam.WorldView.Height) { nameVisible = false; } + else + { + //Ideally it shouldn't send the character entirely if we can't see them but /shrug, this isn't the most hacker-proof game atm + nameVisible = controlled.CanSeeCharacter(this); + } + nameTimer = Rand.Range(0.5f, 1.0f); } - nameTimer = Rand.Range(0.5f, 1.0f); } } @@ -294,23 +280,20 @@ namespace Barotrauma { GUI.SpeechBubbleIcon.Draw(spriteBatch, pos - Vector2.UnitY * 100.0f, speechBubbleColor * Math.Min(speechBubbleTimer, 1.0f), 0.0f, - Math.Min((float)speechBubbleTimer, 1.0f)); + Math.Min(speechBubbleTimer, 1.0f)); } if (this == controlled) return; - if (!nameVisible) return; - - if (info != null) + if (nameVisible && info != null) { string name = Info.DisplayName; - if (controlled == null && name != Info.Name) - name += " (Disguised)"; + if (controlled == null && name != Info.Name) name += " (Disguised)"; Vector2 namePos = new Vector2(pos.X, pos.Y - 110.0f - (5.0f / cam.Zoom)) - GUI.Font.MeasureString(name) * 0.5f / cam.Zoom; Color nameColor = Color.White; - if (Character.Controlled != null && TeamID != Character.Controlled.TeamID) + if (Controlled != null && TeamID != Controlled.TeamID) { nameColor = Color.Red; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs index dc04384ba..6819a412f 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using System.Xml.Linq; using Barotrauma.Items.Components; +using FarseerPhysics.Dynamics; namespace Barotrauma { @@ -982,6 +983,56 @@ namespace Barotrauma } } } + + public bool CanSeeCharacter(Character character) + { + Limb selfLimb = AnimController.GetLimb(LimbType.Head); + if (selfLimb == null) selfLimb = AnimController.GetLimb(LimbType.Torso); + if (selfLimb == null) selfLimb = AnimController.Limbs[0]; + + Limb targetLimb = character.AnimController.GetLimb(LimbType.Head); + if (targetLimb == null) targetLimb = character.AnimController.GetLimb(LimbType.Torso); + if (targetLimb == null) targetLimb = character.AnimController.Limbs[0]; + + if (selfLimb != null && targetLimb != null) + { + Vector2 diff = ConvertUnits.ToSimUnits(targetLimb.WorldPosition - selfLimb.WorldPosition); + + Body closestBody = null; + //both inside the same sub (or both outside) + //OR the we're inside, the other character outside + if (character.Submarine == Submarine || character.Submarine == null) + { + closestBody = Submarine.CheckVisibility(selfLimb.SimPosition, selfLimb.SimPosition + diff); + if (closestBody == null) return true; + } + //we're outside, the other character inside + else if (Submarine == null) + { + closestBody = Submarine.CheckVisibility(targetLimb.SimPosition, targetLimb.SimPosition - diff); + if (closestBody == null) return true; + } + //both inside different subs + else + { + closestBody = Submarine.CheckVisibility(selfLimb.SimPosition, selfLimb.SimPosition + diff); + if (closestBody != null && closestBody.UserData is Structure) + { + if (((Structure)closestBody.UserData).CastShadow) return false; + } + closestBody = Submarine.CheckVisibility(targetLimb.SimPosition, targetLimb.SimPosition - diff); + if (closestBody == null) return true; + + } + + Structure wall = closestBody.UserData as Structure; + return wall == null || !wall.CastShadow; + } + else + { + return false; + } + } public bool HasEquippedItem(Item item) { From 33f0b1c967ff606521b20bd3a94d4cb414321f98 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Sun, 17 Dec 2017 22:38:13 +0200 Subject: [PATCH 40/53] Character.DisplayName looks at the Card slot directly instead of finding the first ID card in the inventory and seeing if it's in the Card slot. + reduced nesting --- .../Source/Characters/CharacterInfo.cs | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/Source/Characters/CharacterInfo.cs index 12e5d93c4..ff85a049e 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/CharacterInfo.cs @@ -14,30 +14,34 @@ namespace Barotrauma public string Name; public string DisplayName { - get { + get + { string disguiseName = "?"; - if (Character != null && Character.HideFace) + if (Character == null || !Character.HideFace) { - if (Character.Inventory != null) + return Name; + } + + if (Character.Inventory != null) + { + int cardSlotIndex = Character.Inventory.FindLimbSlot(InvSlotType.Card); + if (cardSlotIndex < 0) return disguiseName; + + var idCard = Character.Inventory.Items[cardSlotIndex]; + if (idCard == null) return disguiseName; + + //Disguise as the ID card name if it's equipped + string[] readTags = idCard.Tags.Split(','); + foreach (string tag in readTags) { - var idCard = Character.Inventory.FindItem("ID Card"); - if (idCard != null && Character.Inventory.IsInLimbSlot(idCard, InvSlotType.Card)) //Disguise as the ID card name if it's equipped + string[] s = tag.Split(':'); + if (s[0] == "name") { - string[] readTags = idCard.Tags.Split(','); - foreach (string tag in readTags) - { - string[] s = tag.Split(':'); - if (s[0] == "name") - { - disguiseName = s[1]; - break; - } - } + return s[1]; } } - return disguiseName; } - return Name; + return disguiseName; } } public Character Character; From 65fb67df540ce1fea9f51bffeee1b2f028861200 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Sun, 17 Dec 2017 22:40:30 +0200 Subject: [PATCH 41/53] Inventory.FindItem takes the aliases of the items into account (-> possible to change item names without breaking crew AIs and the tutorial) --- Barotrauma/BarotraumaShared/Source/Items/Inventory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs b/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs index 669b68f34..77e20af31 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs @@ -144,7 +144,7 @@ namespace Barotrauma { if (itemName == null) return null; - return Items.FirstOrDefault(i => i != null && (i.Name == itemName || i.HasTag(itemName))); + return Items.FirstOrDefault(i => i != null && (i.Prefab.NameMatches(itemName) || i.HasTag(itemName))); } public virtual void RemoveItem(Item item) From eedc3ffe4919454ed2dbfeb86d4a41776ceec76b Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 18 Dec 2017 20:23:03 +0200 Subject: [PATCH 42/53] Fixed submarines bouncing on other submarines that are laying on the ocean floor (see #152). + Reduced the cap of ice and structure damage particles per impact. TODO: the same thing for characters (and items?) --- .../BarotraumaShared/Source/Map/Structure.cs | 2 +- .../Source/Map/SubmarineBody.cs | 174 ++++++++++++------ 2 files changed, 117 insertions(+), 59 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Map/Structure.cs b/Barotrauma/BarotraumaShared/Source/Map/Structure.cs index 46a7fd781..6e2622276 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Structure.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Structure.cs @@ -566,7 +566,7 @@ namespace Barotrauma #if CLIENT float particleAmount = Math.Min(Health - section.damage, damage) * Rand.Range(0.01f, 1.0f); - particleAmount = Math.Min(particleAmount + Rand.Range(-5,1), 100); + particleAmount = Math.Min(particleAmount + Rand.Range(-5,1), 20); for (int i = 0; i < particleAmount; i++) { Vector2 particlePos = new Vector2( diff --git a/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs b/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs index dca73004c..6d8afa3aa 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs @@ -373,7 +373,6 @@ namespace Barotrauma Limb limb = f2.Body.UserData as Limb; if (limb != null) { - bool collision = HandleLimbCollision(contact, limb); if (collision && limb.Mass > 100.0f) @@ -395,59 +394,14 @@ namespace Barotrauma VoronoiCell cell = f2.Body.UserData as VoronoiCell; if (cell != null) { - var collisionNormal = Vector2.Normalize(ConvertUnits.ToDisplayUnits(Body.SimPosition) - cell.Center); - - float wallImpact = Vector2.Dot(Velocity, -collisionNormal); - - ApplyImpact(wallImpact, -collisionNormal, contact); - foreach (Submarine dockedSub in submarine.DockedTo) - { - dockedSub.SubBody.ApplyImpact(wallImpact, -collisionNormal, contact); - } - - Vector2 n; - FixedArray2 particlePos; - contact.GetWorldManifold(out n, out particlePos); - -#if CLIENT - int particleAmount = (int)(wallImpact * 10.0f); - for (int i = 0; i < particleAmount; i++) - { - GameMain.ParticleManager.CreateParticle("iceshards", - ConvertUnits.ToDisplayUnits(particlePos[0]) + Rand.Vector(Rand.Range(1.0f, 50.0f)), - Rand.Vector(Rand.Range(50.0f, 500.0f)) + Velocity); - } -#endif - + HandleLevelCollision(contact, cell); return true; } Submarine otherSub = f2.Body.UserData as Submarine; if (otherSub != null) { - Debug.Assert(otherSub != submarine); - - Vector2 normal; - FixedArray2 points; - contact.GetWorldManifold(out normal, out points); - if (contact.FixtureA.Body == otherSub.SubBody.Body.FarseerBody) - { - normal = -normal; - } - - float thisMass = Body.Mass + submarine.DockedTo.Sum(s => s.PhysicsBody.Mass); - float otherMass = otherSub.PhysicsBody.Mass + otherSub.DockedTo.Sum(s => s.PhysicsBody.Mass); - - float massRatio = otherMass / (thisMass + otherMass); - - float impact = (Vector2.Dot(Velocity - otherSub.Velocity, normal) / 2.0f) * massRatio; - - ApplyImpact(impact, normal, contact); - foreach (Submarine dockedSub in submarine.DockedTo) - { - dockedSub.SubBody.ApplyImpact(impact, normal, contact); - } - + HandleSubCollision(contact, otherSub); return true; } @@ -466,25 +420,19 @@ namespace Barotrauma Vector2.Zero : Vector2.Normalize(limb.character.AnimController.Collider.LinearVelocity); Vector2 targetPos = ConvertUnits.ToDisplayUnits(points[0] - normal2); - Hull newHull = Hull.FindHull(targetPos, null); if (newHull == null) { targetPos = ConvertUnits.ToDisplayUnits(points[0] + normalizedVel); - newHull = Hull.FindHull(targetPos, null); - if (newHull == null) return true; } var gaps = newHull.ConnectedGaps; - targetPos = limb.character.WorldPosition; - Gap adjacentGap = Gap.FindAdjacent(gaps, targetPos, 200.0f); - - if (adjacentGap==null) return true; + if (adjacentGap == null) return true; var ragdoll = limb.character.AnimController; ragdoll.FindHull(newHull.WorldPosition, true); @@ -492,13 +440,123 @@ namespace Barotrauma return false; } + private void HandleLevelCollision(Contact contact, VoronoiCell cell) + { + var collisionNormal = Vector2.Normalize(ConvertUnits.ToDisplayUnits(Body.SimPosition) - cell.Center); + + float wallImpact = Vector2.Dot(Velocity, -collisionNormal); + + ApplyImpact(wallImpact, -collisionNormal, contact); + foreach (Submarine dockedSub in submarine.DockedTo) + { + dockedSub.SubBody.ApplyImpact(wallImpact, -collisionNormal, contact); + } + + Vector2 n; + FixedArray2 particlePos; + contact.GetWorldManifold(out n, out particlePos); + +#if CLIENT + int particleAmount = (int)Math.Min(wallImpact * 10.0f, 50); + for (int i = 0; i < particleAmount; i++) + { + GameMain.ParticleManager.CreateParticle("iceshards", + ConvertUnits.ToDisplayUnits(particlePos[0]) + Rand.Vector(Rand.Range(1.0f, 50.0f)), + Rand.Vector(Rand.Range(50.0f, 500.0f)) + Velocity); + } +#endif + } + + private void HandleSubCollision(Contact contact, Submarine otherSub) + { + Debug.Assert(otherSub != submarine); + + Vector2 normal; + FixedArray2 points; + contact.GetWorldManifold(out normal, out points); + if (contact.FixtureA.Body == otherSub.SubBody.Body.FarseerBody) + { + normal = -normal; + } + + float thisMass = Body.Mass + submarine.DockedTo.Sum(s => s.PhysicsBody.Mass); + float otherMass = otherSub.PhysicsBody.Mass + otherSub.DockedTo.Sum(s => s.PhysicsBody.Mass); + float massRatio = otherMass / (thisMass + otherMass); + + float impact = (Vector2.Dot(Velocity - otherSub.Velocity, normal) / 2.0f) * massRatio; + + //apply impact to this sub (the other sub takes care of this in its own collision callback) + ApplyImpact(impact, normal, contact); + foreach (Submarine dockedSub in submarine.DockedTo) + { + dockedSub.SubBody.ApplyImpact(impact, normal, contact); + } + + //find all contacts between this sub and level walls + List levelContacts = new List(); + ContactEdge contactEdge = Body.FarseerBody.ContactList; + while (contactEdge.Next != null) + { + if (contactEdge.Contact.Enabled && + contactEdge.Other.UserData is VoronoiCell && + contactEdge.Contact.IsTouching) + { + levelContacts.Add(contactEdge.Contact); + } + + contactEdge = contactEdge.Next; + } + + if (levelContacts.Count == 0) return; + + //if this sub is in contact with the level, apply artifical impacts + //to both subs to prevent the other sub from bouncing on top of this one + //and to fake the other sub "crushing" this one against a wall + Vector2 avgContactNormal = Vector2.Zero; + foreach (Contact levelContact in levelContacts) + { + Vector2 contactNormal; + FixedArray2 temp; + levelContact.GetWorldManifold(out contactNormal, out temp); + + //if the contact normal is pointing from the sub towards the level cell we collided with, flip the normal + VoronoiCell cell = levelContact.FixtureB.Body.UserData is VoronoiCell ? + ((VoronoiCell)levelContact.FixtureB.Body.UserData) : ((VoronoiCell)levelContact.FixtureA.Body.UserData); + + var cellDiff = ConvertUnits.ToDisplayUnits(Body.SimPosition) - cell.Center; + if (Vector2.Dot(contactNormal, cellDiff) < 0) + { + contactNormal = -contactNormal; + } + + avgContactNormal += contactNormal; + + //apply impacts at the positions where this sub is touching the level + ApplyImpact((Vector2.Dot(Velocity - otherSub.Velocity, contactNormal) / 2.0f) * massRatio / levelContacts.Count, contactNormal, levelContact); + } + avgContactNormal /= levelContacts.Count; + + //apply an impact to the other sub + float contactDot = Vector2.Dot(otherSub.PhysicsBody.LinearVelocity, -avgContactNormal); + if (contactDot > 0.0f) + { + otherSub.PhysicsBody.LinearVelocity -= Vector2.Normalize(otherSub.PhysicsBody.LinearVelocity) * contactDot; + + impact = Vector2.Dot(otherSub.Velocity, normal); + otherSub.SubBody.ApplyImpact(impact, normal, contact); + foreach (Submarine dockedSub in otherSub.DockedTo) + { + dockedSub.SubBody.ApplyImpact(impact, normal, contact); + } + } + } + private void ApplyImpact(float impact, Vector2 direction, Contact contact) { if (impact < 3.0f) return; Vector2 tempNormal; - - FarseerPhysics.Common.FixedArray2 worldPoints; + FixedArray2 worldPoints; contact.GetWorldManifold(out tempNormal, out worldPoints); Vector2 lastContactPoint = worldPoints[0]; @@ -537,6 +595,7 @@ namespace Barotrauma var damagedStructures = Explosion.RangedStructureDamage(ConvertUnits.ToDisplayUnits(lastContactPoint), impact * 50.0f, impact * DamageMultiplier); +#if CLIENT //play a damage sound for the structure that took the most damage float maxDamage = 0.0f; Structure maxDamageStructure = null; @@ -549,7 +608,6 @@ namespace Barotrauma } } -#if CLIENT if (maxDamageStructure != null) { SoundPlayer.PlayDamageSound( From c87a582f21494ede6daaad0845dbf24417194dde Mon Sep 17 00:00:00 2001 From: Juan Pablo Arce Date: Mon, 18 Dec 2017 19:41:34 -0300 Subject: [PATCH 43/53] Added smallitem tag to Revolver It makes sense to be able to place it in containers IMO. --- Barotrauma/BarotraumaShared/Content/Items/Weapons/weapons.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/Content/Items/Weapons/weapons.xml b/Barotrauma/BarotraumaShared/Content/Items/Weapons/weapons.xml index 130d19aba..ea239b874 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Weapons/weapons.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Weapons/weapons.xml @@ -109,7 +109,7 @@ name="Revolver" category="Equipment" price="700" - tags="weapon"> + tags="smallitem,weapon"> From 3b65802a9584a57d2eddc333497e6e73ecf440af Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Tue, 19 Dec 2017 15:25:01 +0300 Subject: [PATCH 44/53] 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 4ce4b0a9345d5a9ba3b998d8e8935c83953dcdb5 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 19 Dec 2017 17:30:03 +0200 Subject: [PATCH 45/53] Fixed submarines bouncing on characters submarines that are laying on the ocean floor. Not a very realistic way to handle the collisions - the sub just stops as if it had collided with the level walls (making it seem as if the characters were made of concrete), maybe make the collisions gib the character? Closes #152 --- .../Source/Map/SubmarineBody.cs | 92 +++++++++++++++---- 1 file changed, 73 insertions(+), 19 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs b/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs index 6d8afa3aa..fb1bd935a 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs @@ -373,21 +373,8 @@ namespace Barotrauma Limb limb = f2.Body.UserData as Limb; if (limb != null) { - bool collision = HandleLimbCollision(contact, limb); - - if (collision && limb.Mass > 100.0f) - { - Vector2 normal = Vector2.Normalize(Body.SimPosition - limb.SimPosition); - - float impact = Math.Min(Vector2.Dot(Velocity - limb.LinearVelocity, -normal), 50.0f) / 5.0f * Math.Min(limb.Mass / 200.0f, 1); - - ApplyImpact(impact, -normal, contact); - foreach (Submarine dockedSub in submarine.DockedTo) - { - dockedSub.SubBody.ApplyImpact(impact, -normal, contact); - } - } - + bool collision = CheckLimbCollision(contact, limb); + if (collision) HandleLimbCollision(contact, limb); return collision; } @@ -408,18 +395,18 @@ namespace Barotrauma return true; } - private bool HandleLimbCollision(Contact contact, Limb limb) + private bool CheckLimbCollision(Contact contact, Limb limb) { if (limb.character.Submarine != null) return false; - Vector2 normal2; + Vector2 contactNormal; FixedArray2 points; - contact.GetWorldManifold(out normal2, out points); + contact.GetWorldManifold(out contactNormal, out points); Vector2 normalizedVel = limb.character.AnimController.Collider.LinearVelocity == Vector2.Zero ? Vector2.Zero : Vector2.Normalize(limb.character.AnimController.Collider.LinearVelocity); - Vector2 targetPos = ConvertUnits.ToDisplayUnits(points[0] - normal2); + Vector2 targetPos = ConvertUnits.ToDisplayUnits(points[0] - contactNormal); Hull newHull = Hull.FindHull(targetPos, null); if (newHull == null) @@ -440,6 +427,73 @@ namespace Barotrauma return false; } + private void HandleLimbCollision(Contact contact, Limb limb) + { + if (limb.Mass > 100.0f) + { + Vector2 normal = Vector2.Normalize(Body.SimPosition - limb.SimPosition); + + float impact = Math.Min(Vector2.Dot(Velocity - limb.LinearVelocity, -normal), 50.0f) / 5.0f * Math.Min(limb.Mass / 200.0f, 1); + + ApplyImpact(impact, -normal, contact); + foreach (Submarine dockedSub in submarine.DockedTo) + { + dockedSub.SubBody.ApplyImpact(impact, -normal, contact); + } + } + + //find all contacts between the limb and level walls + List levelContacts = new List(); + ContactEdge contactEdge = limb.body.FarseerBody.ContactList; + while (contactEdge.Next != null) + { + if (contactEdge.Contact.Enabled && + contactEdge.Other.UserData is VoronoiCell && + contactEdge.Contact.IsTouching) + { + levelContacts.Add(contactEdge.Contact); + } + contactEdge = contactEdge.Next; + } + + if (levelContacts.Count == 0) return; + + //if the limb is in contact with the level, apply an artifical impact to prevent the sub from bouncing on top of it + //not a very realistic way to handle the collisions (makes it seem as if the characters were made of reinforced concrete), + //but more realistic than bouncing and prevents using characters as "bumpers" that prevent all collision damage + + //TODO: apply impact damage and/or gib the character that got crushed between the sub and the level? + Vector2 avgContactNormal = Vector2.Zero; + foreach (Contact levelContact in levelContacts) + { + Vector2 contactNormal; + FixedArray2 temp; + levelContact.GetWorldManifold(out contactNormal, out temp); + + //if the contact normal is pointing from the limb towards the level cell it's touching, flip the normal + VoronoiCell cell = levelContact.FixtureB.Body.UserData is VoronoiCell ? + ((VoronoiCell)levelContact.FixtureB.Body.UserData) : ((VoronoiCell)levelContact.FixtureA.Body.UserData); + + var cellDiff = ConvertUnits.ToDisplayUnits(limb.body.SimPosition) - cell.Center; + if (Vector2.Dot(contactNormal, cellDiff) < 0) + { + contactNormal = -contactNormal; + } + + avgContactNormal += contactNormal; + + //apply impacts at the positions where this sub is touching the limb + ApplyImpact((Vector2.Dot(-Velocity, contactNormal) / 2.0f) / levelContacts.Count, contactNormal, levelContact); + } + avgContactNormal /= levelContacts.Count; + + float contactDot = Vector2.Dot(Body.LinearVelocity, -avgContactNormal); + if (contactDot > 0.0f) + { + Body.LinearVelocity -= Vector2.Normalize(Body.LinearVelocity) * contactDot; + } + } + private void HandleLevelCollision(Contact contact, VoronoiCell cell) { var collisionNormal = Vector2.Normalize(ConvertUnits.ToDisplayUnits(Body.SimPosition) - cell.Center); From d03dd40336f520b37af6c94fdfb5c04958bb7a4d Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 19 Dec 2017 18:11:47 +0200 Subject: [PATCH 46/53] Fixed item GUIMessages overlapping with each other --- Barotrauma/BarotraumaClient/Source/GUI/GUI.cs | 10 ++++++++-- .../BarotraumaClient/Source/GUI/GUIMessage.cs | 17 ++++++++++------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs index 08468d81f..5b758ad98 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs @@ -546,6 +546,9 @@ namespace Barotrauma } } + /// + /// Displays a message at the center of the screen, automatically preventing overlapping with other centered messages + /// public static void AddMessage(string message, Color color, float lifeTime = 3.0f, bool playSound = true) { if (messages.Count > 0 && messages[messages.Count - 1].Text == message) @@ -555,12 +558,15 @@ namespace Barotrauma } Vector2 pos = new Vector2(GameMain.GraphicsWidth / 2.0f, GameMain.GraphicsHeight * 0.7f); - pos.Y += messages.FindAll(m => m.Centered).Count * 30; + pos.Y += messages.FindAll(m => m.AutoCenter).Count * 30; messages.Add(new GUIMessage(message, color, pos, lifeTime, Alignment.Center, true)); if (playSound) PlayUISound(GUISoundType.Message); } + /// + /// Display and automatically fade out a piece of text at an arbitrary position on the screen + /// public static void AddMessage(string message, Vector2 position, Alignment alignment, Color color, float lifeTime = 3.0f, bool playSound = true) { if (messages.Count > 0 && messages[messages.Count - 1].Text == message) @@ -599,7 +605,7 @@ namespace Barotrauma alpha -= 1.0f - msg.LifeTime; } - if (msg.Centered) + if (msg.AutoCenter) { msg.Pos = MathUtils.SmoothStep(msg.Pos, currPos, deltaTime * 20.0f); currPos.Y += 30.0f; diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUIMessage.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUIMessage.cs index cb1f6b437..de437566d 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUIMessage.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUIMessage.cs @@ -46,14 +46,21 @@ namespace Barotrauma private set; } - public bool Centered; - public GUIMessage(string text, Color color, Vector2 position, float lifeTime, Alignment textAlignment, bool centered) + /// + /// Autocentered messages are automatically placed at the center of the screen and prevented from overlapping with each other + /// + public bool AutoCenter; + + public GUIMessage(string text, Color color, Vector2 position, float lifeTime, Alignment textAlignment, bool autoCenter) { coloredText = new ColoredText(text, color); pos = position; this.lifeTime = lifeTime; this.Alignment = textAlignment; + this.AutoCenter = autoCenter; + + size = GUI.Font.MeasureString(text); if (textAlignment.HasFlag(Alignment.Left)) Origin.X += size.X * 0.5f; @@ -67,14 +74,10 @@ namespace Barotrauma if (textAlignment.HasFlag(Alignment.Bottom)) Origin.Y -= size.Y * 0.5f; - if (centered) + if (autoCenter) { Origin = new Vector2((int)(0.5f * size.X), (int)(0.5f * size.Y)); } - else - { - size = GUI.Font.MeasureString(text); - } } } } From e56f5c49467aeb06c15863fd70aa92078b748671 Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Tue, 19 Dec 2017 22:11:45 +0300 Subject: [PATCH 47/53] 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 48/53] 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 49/53] 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 50/53] 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 51/53] 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 52/53] 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 53/53] 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;