diff --git a/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs index 652acf037..40fceb2b3 100644 --- a/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs +++ b/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.8.1.4")] -[assembly: AssemblyFileVersion("0.8.1.4")] +[assembly: AssemblyVersion("0.8.1.5")] +[assembly: AssemblyFileVersion("0.8.1.5")] diff --git a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs index 19d86a62f..b095508b2 100644 --- a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs @@ -394,7 +394,7 @@ namespace Barotrauma NewMessage(GUI.DisableHUD ? "Disabled HUD" : "Enabled HUD", Color.White); })); - commands.Add(new Command("followsub", "followsub: Toggle whether the ", (string[] args) => + commands.Add(new Command("followsub", "followsub: Toggle whether the camera should follow the nearest submarine.", (string[] args) => { Camera.FollowSub = !Camera.FollowSub; NewMessage(Camera.FollowSub ? "Set the camera to follow the closest submarine" : "Disabled submarine following.", Color.White); diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs index 020657672..47c0a4e7a 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs @@ -822,7 +822,7 @@ namespace Barotrauma.Networking } else { - submarines.Add(new Submarine(Path.Combine(Submarine.SavePath, subName), subHash, false)); + submarines.Add(new Submarine(Path.Combine(Submarine.SavePath, subName) + ".sub", subHash, false)); } } @@ -1441,6 +1441,8 @@ namespace Barotrauma.Networking msg.Write((byte)ClientPermissions.Ban); msg.Write(kickedName); msg.Write(reason); + msg.Write(range); + msg.Write(duration.HasValue ? duration.Value.TotalSeconds : 0.0); //0 = permaban client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); } diff --git a/Barotrauma/BarotraumaClient/Source/Program.cs b/Barotrauma/BarotraumaClient/Source/Program.cs index 910b4e60d..4cb2af6c2 100644 --- a/Barotrauma/BarotraumaClient/Source/Program.cs +++ b/Barotrauma/BarotraumaClient/Source/Program.cs @@ -3,10 +3,10 @@ using System; using System.IO; using System.Text; +using GameAnalyticsSDK.Net; #if WINDOWS using System.Windows.Forms; -using GameAnalyticsSDK.Net; using Microsoft.Xna.Framework.Graphics; #endif diff --git a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs index eee9771d2..6f5edabac 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs @@ -561,14 +561,18 @@ namespace Barotrauma var playYourself = new GUITickBox(new Rectangle(0, 0, 20, 20), TextManager.Get("PlayYourself"), Alignment.TopLeft, myPlayerFrame); playYourself.Selected = GameMain.NetworkMember.CharacterInfo != null; playYourself.OnSelected = TogglePlayYourself; - playYourself.UserData = "playyourself"; - - GUIButton toggleHead = new GUIButton(new Rectangle(0, 50, 15, 15), "<", "", myPlayerFrame); - toggleHead.UserData = -1; - toggleHead.OnClicked = ToggleHead; - toggleHead = new GUIButton(new Rectangle(60, 50, 15, 15), ">", "", myPlayerFrame); - toggleHead.UserData = 1; - toggleHead.OnClicked = ToggleHead; + playYourself.UserData = "playyourself"; + + GUIButton toggleHead = new GUIButton(new Rectangle(0, 50, 15, 15), "<", "", myPlayerFrame) + { + UserData = -1, + OnClicked = ToggleHead + }; + toggleHead = new GUIButton(new Rectangle(60, 50, 15, 15), ">", "", myPlayerFrame) + { + UserData = 1, + OnClicked = ToggleHead + }; new GUITextBlock(new Rectangle(100, 30, 200, 30), TextManager.Get("Gender"), "", myPlayerFrame); @@ -631,7 +635,10 @@ namespace Barotrauma { if (tickBox.Selected) { - GameMain.NetworkMember.CharacterInfo = new CharacterInfo(Character.HumanConfigFile, GameMain.NetworkMember.Name, Gender.None, null); + GameMain.NetworkMember.CharacterInfo = + new CharacterInfo(Character.HumanConfigFile, GameMain.NetworkMember.Name, GameMain.Config.CharacterGender, null); + GameMain.NetworkMember.CharacterInfo.HeadSpriteId = GameMain.Config.CharacterHeadIndex; + UpdatePlayerFrame(GameMain.NetworkMember.CharacterInfo); } else @@ -1249,14 +1256,12 @@ namespace Barotrauma private bool ToggleHead(GUIButton button, object userData) { - int dir = (int)userData; - if (GameMain.NetworkMember.CharacterInfo == null) return true; + int dir = (int)userData; GameMain.NetworkMember.CharacterInfo.HeadSpriteId += dir; - + GameMain.Config.CharacterHeadIndex = GameMain.NetworkMember.CharacterInfo.HeadSpriteId; UpdatePlayerHead(GameMain.NetworkMember.CharacterInfo); - return true; } @@ -1264,7 +1269,7 @@ namespace Barotrauma { Gender gender = (Gender)obj; GameMain.NetworkMember.CharacterInfo.Gender = gender; - + GameMain.Config.CharacterGender = GameMain.NetworkMember.CharacterInfo.Gender; UpdatePlayerHead(GameMain.NetworkMember.CharacterInfo); return true; } diff --git a/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs index 420567bf2..66089601e 100644 --- a/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs +++ b/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.8.1.4")] -[assembly: AssemblyFileVersion("0.8.1.4")] +[assembly: AssemblyVersion("0.8.1.5")] +[assembly: AssemblyFileVersion("0.8.1.5")] diff --git a/Barotrauma/BarotraumaServer/Source/DebugConsole.cs b/Barotrauma/BarotraumaServer/Source/DebugConsole.cs index 0d31c1d48..7204c5899 100644 --- a/Barotrauma/BarotraumaServer/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaServer/Source/DebugConsole.cs @@ -14,7 +14,7 @@ namespace Barotrauma { lock (QueuedCommands) { - while (QueuedCommands.Count>0) + while (QueuedCommands.Count > 0) { ExecuteCommand(QueuedCommands[0]); QueuedCommands.RemoveAt(0); @@ -190,6 +190,16 @@ namespace Barotrauma NewMessage(ent.ToString(), Color.Lime); } })); + + //"dummy commands" that only exist so that the server can give clients permissions to use them + commands.Add(new Command("control|controlcharacter", "control [character name]: Start controlling the specified character (client-only).", null)); + commands.Add(new Command("los", "Toggle the line of sight effect on/off (client-only).", null)); + commands.Add(new Command("lighting|lights", "Toggle lighting on/off (client-only).", null)); + commands.Add(new Command("debugdraw", "Toggle the debug drawing mode on/off (client-only).", null)); + commands.Add(new Command("togglehud|hud", "Toggle the character HUD (inventories, icons, buttons, etc) on/off (client-only).", null)); + commands.Add(new Command("followsub", "Toggle whether the camera should follow the nearest submarine (client-only).", null)); + commands.Add(new Command("toggleaitargets|aitargets", "Toggle the visibility of AI targets (= targets that enemies can detect and attack/escape from) (client-only).", null)); + #if DEBUG commands.Add(new Command("eventdata", "", (string[] args) => { diff --git a/Barotrauma/BarotraumaShared/Content/Items/Diving/divinggear.xml b/Barotrauma/BarotraumaShared/Content/Items/Diving/divinggear.xml index 59d9ee794..5b08da1d5 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Diving/divinggear.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Diving/divinggear.xml @@ -17,9 +17,11 @@ + + diff --git a/Barotrauma/BarotraumaShared/Content/Items/OxygenGenerator/oxygengenerator.xml b/Barotrauma/BarotraumaShared/Content/Items/OxygenGenerator/oxygengenerator.xml index bdf428f3f..4c23d5fe4 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/OxygenGenerator/oxygengenerator.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/OxygenGenerator/oxygengenerator.xml @@ -13,6 +13,7 @@ + diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Attack.cs b/Barotrauma/BarotraumaShared/Source/Characters/Attack.cs index 78bfb3145..bc498df98 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Attack.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Attack.cs @@ -52,7 +52,7 @@ namespace Barotrauma public float Range { get; private set; } [Serialize(0.0f, false)] - public float DamageRange { get; private set; } + public float DamageRange { get; set; } [Serialize(0.0f, false)] public float Duration { get; private set; } diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs index 3a9a9a714..ffb676918 100644 --- a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs @@ -88,6 +88,7 @@ namespace Barotrauma public void Execute(string[] args) { + if (onExecute == null) return; onExecute(args); } @@ -100,6 +101,7 @@ namespace Barotrauma { if (onClientRequestExecute == null) { + if (onExecute == null) return; onExecute(args); } else @@ -247,11 +249,10 @@ namespace Barotrauma }; })); - 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.", + commands.Add(new Command("spawnitem", "spawnitem [itemname] [cursor/inventory/random/[name]]: Spawn an item at the position of the cursor, in the inventory of the controlled character, in the inventory of the client with the given name, or at a random spawnpoint if the last parameter is omitted or \"random\".", (string[] args) => { - string errorMsg; - SpawnItem(args, GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), out errorMsg); + SpawnItem(args, GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), Character.Controlled, out string errorMsg); if (!string.IsNullOrWhiteSpace(errorMsg)) { ThrowError(errorMsg); @@ -260,20 +261,18 @@ namespace Barotrauma null, (Client client, Vector2 cursorWorldPos, string[] args) => { - string errorMsg; - SpawnItem(args, cursorWorldPos, out errorMsg); + SpawnItem(args, cursorWorldPos, client.Character, out string errorMsg); if (!string.IsNullOrWhiteSpace(errorMsg)) { - ThrowError(errorMsg); + GameMain.Server.SendConsoleMessage(errorMsg, client); } - }, + }, () => { List itemNames = new List(); foreach (MapEntityPrefab prefab in MapEntityPrefab.List) { - ItemPrefab itemPrefab = prefab as ItemPrefab; - if (itemPrefab != null) itemNames.Add(itemPrefab.Name); + if (prefab is ItemPrefab itemPrefab) itemNames.Add(itemPrefab.Name); } return new string[][] @@ -283,6 +282,7 @@ namespace Barotrauma }; })); + commands.Add(new Command("disablecrewai", "disablecrewai: Disable the AI of the NPCs in the crew.", (string[] args) => { HumanAIController.DisableCrewAI = true; @@ -849,8 +849,7 @@ namespace Barotrauma { if (args.Length < 2) return; - int id; - int.TryParse(args[0], out id); + int.TryParse(args[0], out int id); var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); if (client == null) { @@ -891,8 +890,7 @@ namespace Barotrauma return; } - int id; - int.TryParse(args[0], out id); + int.TryParse(args[0], out int id); var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); if (client == null) { @@ -1019,11 +1017,10 @@ namespace Barotrauma commands.Add(new Command("kickid", "kickid [id]: Kick the player with the specified client ID out of the server.", (string[] args) => { - if (GameMain.Server == null || args.Length == 0) return; + if (GameMain.NetworkMember == null || args.Length == 0) return; - int id = 0; - int.TryParse(args[0], out id); - var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); + int.TryParse(args[0], out int id); + var client = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == id); if (client == null) { ThrowError("Client id \"" + id + "\" not found."); @@ -1032,7 +1029,7 @@ namespace Barotrauma ShowQuestionPrompt("Reason for kicking \"" + client.Name + "\"?", (reason) => { - GameMain.Server.KickPlayer(client.Name, reason); + GameMain.NetworkMember.KickPlayer(client.Name, reason); }); })); @@ -1073,17 +1070,16 @@ namespace Barotrauma commands.Add(new Command("banid", "banid [id]: Kick and ban the player with the specified client ID from the server.", (string[] args) => { - if (GameMain.Server == null || args.Length == 0) return; + if (GameMain.NetworkMember == null || args.Length == 0) return; - int id = 0; - int.TryParse(args[0], out id); - var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); + int.TryParse(args[0], out int id); + var client = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == id); if (client == null) { ThrowError("Client id \"" + id + "\" not found."); return; } - + ShowQuestionPrompt("Reason for banning \"" + client.Name + "\"?", (reason) => { ShowQuestionPrompt("Enter the duration of the ban (leave empty to ban permanently, or use the format \"[days] d [hours] h\")", (duration) => @@ -1091,8 +1087,7 @@ namespace Barotrauma TimeSpan? banDuration = null; if (!string.IsNullOrWhiteSpace(duration)) { - TimeSpan parsedBanDuration; - if (!TryParseTimeSpan(duration, out parsedBanDuration)) + if (!TryParseTimeSpan(duration, out TimeSpan parsedBanDuration)) { ThrowError("\"" + duration + "\" is not a valid ban duration. Use the format \"[days] d [hours] h\", \"[days] d\" or \"[hours] h\"."); return; @@ -1100,7 +1095,7 @@ namespace Barotrauma banDuration = parsedBanDuration; } - GameMain.Server.BanPlayer(client.Name, reason, false, banDuration); + GameMain.NetworkMember.BanPlayer(client.Name, reason, false, banDuration); }); }); })); @@ -1117,8 +1112,7 @@ namespace Barotrauma TimeSpan? banDuration = null; if (!string.IsNullOrWhiteSpace(duration)) { - TimeSpan parsedBanDuration; - if (!TryParseTimeSpan(duration, out parsedBanDuration)) + if (!TryParseTimeSpan(duration, out TimeSpan parsedBanDuration)) { ThrowError("\"" + duration + "\" is not a valid ban duration. Use the format \"[days] d [hours] h\", \"[days] d\" or \"[hours] h\"."); return; @@ -1139,23 +1133,72 @@ namespace Barotrauma } } }); + }); + }, + (string[] args) => + { +#if CLIENT + if (GameMain.Client == null || args.Length == 0) return; + ShowQuestionPrompt("Reason for banning the ip \"" + args[0] + "\"?", (reason) => + { + ShowQuestionPrompt("Enter the duration of the ban (leave empty to ban permanently, or use the format \"[days] d [hours] h\")", (duration) => + { + TimeSpan? banDuration = null; + if (!string.IsNullOrWhiteSpace(duration)) + { + if (!TryParseTimeSpan(duration, out TimeSpan parsedBanDuration)) + { + ThrowError("\"" + duration + "\" is not a valid ban duration. Use the format \"[days] d [hours] h\", \"[days] d\" or \"[hours] h\"."); + return; + } + banDuration = parsedBanDuration; + } + + GameMain.Client.SendConsoleCommand( + "banip " + + args[0] + " " + + (banDuration.HasValue ? banDuration.Value.TotalSeconds.ToString() : "0") + " " + + reason); + }); }); - +#endif + }, + (Client client, Vector2 cursorPos, string[] args) => + { + if (args.Length < 1) return; + var clients = GameMain.Server.ConnectedClients.FindAll(c => c.Connection.RemoteEndPoint.Address.ToString() == args[0]); + TimeSpan? duration = null; + if (args.Length > 1) + { + if (double.TryParse(args[1], out double durationSeconds)) + { + if (durationSeconds > 0) duration = TimeSpan.FromSeconds(durationSeconds); + } + else + { + GameMain.Server.SendConsoleMessage("\"" + args[1] + "\" is not a valid ban duration.", client); + return; + } + } + string reason = ""; + if (args.Length > 2) reason = string.Join(" ", args.Skip(2)); + + if (clients.Count == 0) + { + GameMain.Server.BanList.BanPlayer("Unnamed", args[0], reason, duration); + } + else + { + foreach (Client cl in clients) + { + GameMain.Server.BanClient(cl, reason, false, duration); + } + } })); commands.Add(new Command("teleportcharacter|teleport", "teleport [character name]: Teleport the specified character to the position of the cursor. If the name parameter is omitted, the controlled character will be teleported.", (string[] args) => { - Character tpCharacter = null; - - if (args.Length == 0) - { - tpCharacter = Character.Controlled; - } - else - { - tpCharacter = FindMatchingCharacter(args, false); - } - + Character tpCharacter = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args, false); if (tpCharacter == null) return; var cam = GameMain.GameScreen.Cam; @@ -1167,17 +1210,7 @@ namespace Barotrauma null, (Client client, Vector2 cursorWorldPos, string[] args) => { - Character tpCharacter = null; - - if (args.Length == 0) - { - tpCharacter = client.Character; - } - else - { - tpCharacter = FindMatchingCharacter(args, false); - } - + Character tpCharacter = (args.Length == 0) ? client.Character : FindMatchingCharacter(args, false); if (tpCharacter == null) return; var cam = GameMain.GameScreen.Cam; @@ -1234,18 +1267,29 @@ namespace Barotrauma } })); + commands.Add(new Command("findentityids", "findentityids [entityname]", (string[] args) => + { + if (args.Length == 0) return; + args[0] = args[0].ToLowerInvariant(); + foreach (MapEntity mapEntity in MapEntity.mapEntityList) + { + if (mapEntity.Name.ToLowerInvariant() == args[0]) + { + ThrowError(mapEntity.ID + ": " + mapEntity.Name.ToString()); + } + } + foreach (Character character in Character.CharacterList) + { + if (character.Name.ToLowerInvariant() == args[0] || character.SpeciesName.ToLowerInvariant() == args[0]) + { + ThrowError(character.ID + ": " + character.Name.ToString()); + } + } + })); + commands.Add(new Command("heal", "heal [character name]: Restore the specified character to full health. If the name parameter is omitted, the controlled character will be healed.", (string[] args) => { - Character healedCharacter = null; - if (args.Length == 0) - { - healedCharacter = Character.Controlled; - } - else - { - healedCharacter = FindMatchingCharacter(args); - } - + Character healedCharacter = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args); if (healedCharacter != null) { healedCharacter.AddDamage(CauseOfDeath.Damage, -healedCharacter.MaxHealth, null); @@ -1257,16 +1301,7 @@ namespace Barotrauma null, (Client client, Vector2 cursorWorldPos, string[] args) => { - Character healedCharacter = null; - if (args.Length == 0) - { - healedCharacter = client.Character; - } - else - { - healedCharacter = FindMatchingCharacter(args); - } - + Character healedCharacter = (args.Length == 0) ? client.Character : FindMatchingCharacter(args); if (healedCharacter != null) { healedCharacter.AddDamage(CauseOfDeath.Damage, -healedCharacter.MaxHealth, null); @@ -1285,16 +1320,7 @@ namespace Barotrauma commands.Add(new Command("revive", "revive [character name]: Bring the specified character back from the dead. If the name parameter is omitted, the controlled character will be revived.", (string[] args) => { - Character revivedCharacter = null; - if (args.Length == 0) - { - revivedCharacter = Character.Controlled; - } - else - { - revivedCharacter = FindMatchingCharacter(args); - } - + Character revivedCharacter = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args); if (revivedCharacter == null) return; revivedCharacter.Revive(false); @@ -1313,16 +1339,7 @@ namespace Barotrauma null, (Client client, Vector2 cursorWorldPos, string[] args) => { - Character revivedCharacter = null; - if (args.Length == 0) - { - revivedCharacter = client.Character; - } - else - { - revivedCharacter = FindMatchingCharacter(args); - } - + Character revivedCharacter = (args.Length == 0) ? client.Character : FindMatchingCharacter(args); if (revivedCharacter == null) return; revivedCharacter.Revive(false); @@ -1358,16 +1375,16 @@ namespace Barotrauma commands.Add(new Command("ragdoll", "ragdoll [character name]: Force-ragdoll the specified character. If the name parameter is omitted, the controlled character will be ragdolled.", (string[] args) => { - Character ragdolledCharacter = null; - if (args.Length == 0) + Character ragdolledCharacter = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args); + if (ragdolledCharacter != null) { - ragdolledCharacter = Character.Controlled; + ragdolledCharacter.IsForceRagdolled = !ragdolledCharacter.IsForceRagdolled; } - else - { - ragdolledCharacter = FindMatchingCharacter(args); - } - + }, + null, + (Client client, Vector2 cursorWorldPos, string[] args) => + { + Character ragdolledCharacter = (args.Length == 0) ? client.Character : FindMatchingCharacter(args); if (ragdolledCharacter != null) { ragdolledCharacter.IsForceRagdolled = !ragdolledCharacter.IsForceRagdolled; @@ -1477,20 +1494,14 @@ namespace Barotrauma commands.Add(new Command("kill", "kill [character]: Immediately kills the specified character.", (string[] args) => { - Character killedCharacter = null; - if (args.Length == 0) - { - killedCharacter = Character.Controlled; - } - else - { - killedCharacter = FindMatchingCharacter(args); - } - - if (killedCharacter != null) - { - killedCharacter.AddDamage(CauseOfDeath.Damage, killedCharacter.MaxHealth * 2, null); - } + Character killedCharacter = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args); + killedCharacter?.AddDamage(CauseOfDeath.Damage, killedCharacter.MaxHealth * 2, null); + }, + null, + (Client client, Vector2 cursorWorldPos, string[] args) => + { + Character killedCharacter = (args.Length == 0) ? client.Character : FindMatchingCharacter(args); + killedCharacter?.AddDamage(CauseOfDeath.Damage, killedCharacter.MaxHealth * 2, null); })); commands.Add(new Command("killmonsters", "killmonsters: Immediately kills all AI-controlled enemies in the level.", (string[] args) => @@ -2120,7 +2131,7 @@ namespace Barotrauma } } - private static void SpawnItem(string[] args, Vector2 cursorPos, out string errorMsg) + private static void SpawnItem(string[] args, Vector2 cursorPos, Character controlledCharacter, out string errorMsg) { errorMsg = ""; if (args.Length < 1) return; @@ -2128,23 +2139,48 @@ namespace Barotrauma Vector2? spawnPos = null; Inventory spawnInventory = null; - int extraParams = 0; - switch (args.Last()) + if (args.Length > 1) { - case "cursor": - extraParams = 1; - spawnPos = cursorPos; - break; - case "inventory": - extraParams = 1; - spawnInventory = Character.Controlled == null ? null : Character.Controlled.Inventory; - break; - default: - extraParams = 0; - break; + switch (args[1]) + { + case "cursor": + spawnPos = cursorPos; + break; + case "inventory": + spawnInventory = controlledCharacter?.Inventory; + break; + default: + //Check if last arg matches the name of an in-game player + if (GameMain.Server != null) + { + var client = GameMain.Server.ConnectedClients.Find(c => c.Name.ToLower() == args.Last().ToLower()); + if (client == null) + { + NewMessage("No player found with the name \"" + args.Last() + "\". Spawning item at random location. If the player you want to give the item to has a space in their name, try surrounding their name with quotes (\").", Color.Red); + break; + } + else if (client.Character == null) + { + errorMsg = "The player \"" + args.Last() + "\" is connected, but hasn't spawned yet."; + return; + } + else + { + //If the last arg matches the name of an in-game player, set the destination to their inventory. + spawnInventory = client.Character.Inventory; + break; + } + } + else + { + var matchingCharacter = FindMatchingCharacter(args.Skip(1).ToArray()); + if (matchingCharacter?.Inventory != null) spawnInventory = matchingCharacter.Inventory; + } + break; + } } - string itemName = string.Join(" ", args.Take(args.Length - extraParams)).ToLowerInvariant(); + string itemName = args[0]; var itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab; if (itemPrefab == null) @@ -2162,7 +2198,6 @@ namespace Barotrauma if (spawnPos != null) { Entity.Spawner.AddToSpawnQueue(itemPrefab, (Vector2)spawnPos); - } else if (spawnInventory != null) { diff --git a/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/MultiPlayerCampaign.cs index ffcbcf961..54155e44a 100644 --- a/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/MultiPlayerCampaign.cs @@ -55,13 +55,7 @@ namespace Barotrauma public override void Start() { - base.Start(); - - if (GameMain.Server != null) - { - CargoManager.CreateItems(); - } - + base.Start(); lastUpdateID++; } diff --git a/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/TraitorManager.cs b/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/TraitorManager.cs index 0e87247bc..b071b3a27 100644 --- a/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/TraitorManager.cs +++ b/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/TraitorManager.cs @@ -108,10 +108,9 @@ namespace Barotrauma codeWords = ToolBox.GetRandomLine(wordsTxt) + ", " + ToolBox.GetRandomLine(wordsTxt); codeResponse = ToolBox.GetRandomLine(wordsTxt) + ", " + ToolBox.GetRandomLine(wordsTxt); - while (traitorCount-- >= 0) + while (traitorCount-- > 0) { - if (traitorCandidates.Count <= 0) - break; + if (traitorCandidates.Count <= 0) break; int traitorIndex = Rand.Int(traitorCandidates.Count); Character traitorCharacter = traitorCandidates[traitorIndex]; diff --git a/Barotrauma/BarotraumaShared/Source/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/Source/GameSession/GameSession.cs index 855db7f5f..ef9c4f3fb 100644 --- a/Barotrauma/BarotraumaShared/Source/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/Source/GameSession/GameSession.cs @@ -226,7 +226,14 @@ namespace Barotrauma EventManager.StartRound(level); - if (GameMode != null) GameMode.MsgBox(); + if (GameMode != null) + { + GameMode.MsgBox(); + if (GameMode is MultiPlayerCampaign campaign && GameMain.Server != null) + { + campaign.CargoManager.CreateItems(); + } + } if (GameSettings.SendUserStatistics) { @@ -234,7 +241,7 @@ namespace Barotrauma GameAnalyticsSDK.Net.GameAnalytics.AddProgressionEvent(GameAnalyticsSDK.Net.EGAProgressionStatus.Start, GameMode.Name, (Mission == null ? "None" : Mission.GetType().ToString())); } - + #if CLIENT roundSummary = new RoundSummary(this); diff --git a/Barotrauma/BarotraumaShared/Source/GameSettings.cs b/Barotrauma/BarotraumaShared/Source/GameSettings.cs index 718e320bf..a476f143f 100644 --- a/Barotrauma/BarotraumaShared/Source/GameSettings.cs +++ b/Barotrauma/BarotraumaShared/Source/GameSettings.cs @@ -25,7 +25,7 @@ namespace Barotrauma public bool EnableSplashScreen { get; set; } //public bool FullScreenEnabled { get; set; } - + private KeyOrMouse[] keyMapping; private WindowMode windowMode; @@ -51,8 +51,37 @@ namespace Barotrauma } } - private bool unsavedSettings; + private int characterHeadIndex; + public int CharacterHeadIndex + { + get { return characterHeadIndex; } + set + { + if (value == characterHeadIndex) return; + // Begin saving coroutine. Remove any existing save coroutines if one is running. + if (CoroutineManager.IsCoroutineRunning("saveCoroutine")) { CoroutineManager.StopCoroutines("saveCoroutine"); } + CoroutineManager.StartCoroutine(ApplyUnsavedChanges(), "saveCoroutine"); + characterHeadIndex = value; + } + } + + private Gender characterGender; + public Gender CharacterGender + { + get { return characterGender; } + set + { + if (value == characterGender) return; + // Begin saving coroutine. Remove any existing save coroutines if one is running. + if (CoroutineManager.IsCoroutineRunning("saveCoroutine")) { CoroutineManager.StopCoroutines("saveCoroutine"); } + CoroutineManager.StartCoroutine(ApplyUnsavedChanges(), "saveCoroutine"); + + characterGender = value; + } + } + + private bool unsavedSettings; public bool UnsavedSettings { get @@ -230,18 +259,15 @@ namespace Barotrauma case "keymapping": foreach (XAttribute attribute in subElement.Attributes()) { - InputType inputType; - if (Enum.TryParse(attribute.Name.ToString(), true, out inputType)) + if (Enum.TryParse(attribute.Name.ToString(), true, out InputType inputType)) { - int mouseButton; - if (int.TryParse(attribute.Value.ToString(), out mouseButton)) + if (int.TryParse(attribute.Value.ToString(), out int mouseButton)) { keyMapping[(int)inputType] = new KeyOrMouse(mouseButton); } else { - Keys key; - if (Enum.TryParse(attribute.Value.ToString(), true, out key)) + if (Enum.TryParse(attribute.Value.ToString(), true, out Keys key)) { keyMapping[(int)inputType] = new KeyOrMouse(key); } @@ -258,6 +284,9 @@ namespace Barotrauma break; case "player": defaultPlayerName = subElement.GetAttributeString("name", ""); + characterHeadIndex = subElement.GetAttributeInt("headindex", Rand.Int(10)); + characterGender = subElement.GetAttributeString("gender", Rand.Range(0.0f, 1.0f) < 0.5f ? "male" : "female") + .ToLowerInvariant() == "male" ? Gender.Male : Gender.Female; break; } } @@ -363,8 +392,10 @@ namespace Barotrauma gameplay.Add(jobPreferences); doc.Root.Add(gameplay); - var playerElement = new XElement("player"); - playerElement.Add(new XAttribute("name", defaultPlayerName ?? "")); + var playerElement = new XElement("player", + new XAttribute("name", defaultPlayerName ?? ""), + new XAttribute("headindex", characterHeadIndex), + new XAttribute("gender", characterGender)); doc.Root.Add(playerElement); doc.Save(filePath); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs index 3d6e36af4..5ee79b5d7 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs @@ -93,6 +93,26 @@ namespace Barotrauma.Items.Components } } + public override void OnItemLoaded() + { + if (attack != null && attack.DamageRange <= 0.0f && item.body != null) + { + switch (item.body.BodyShape) + { + case PhysicsBody.Shape.Circle: + attack.DamageRange = item.body.radius; + break; + case PhysicsBody.Shape.Capsule: + attack.DamageRange = item.body.height / 2 + item.body.radius; + break; + case PhysicsBody.Shape.Rectangle: + attack.DamageRange = new Vector2(item.body.width / 2.0f, item.body.height / 2.0f).Length(); + break; + } + attack.DamageRange = ConvertUnits.ToDisplayUnits(attack.DamageRange); + } + } + public override bool Use(float deltaTime, Character character = null) { if (character != null && !characterUsable) return false; @@ -196,7 +216,6 @@ namespace Barotrauma.Items.Components if (OnProjectileCollision(fixture, normal)) { hitSomething = true; - //Character.Controlled.AnimController.Teleport(point - Character.Controlled.SimPosition, Vector2.Zero); break; } } @@ -281,8 +300,7 @@ namespace Barotrauma.Items.Components Character character = null; if (attack != null) { - var submarine = target.Body.UserData as Submarine; - if (submarine != null) + if (target.Body.UserData is Submarine submarine) { item.Move(-submarine.Position); item.Submarine = submarine; @@ -290,9 +308,8 @@ namespace Barotrauma.Items.Components return true; } - Limb limb = target.Body.UserData as Limb; Structure structure; - if (limb != null) + if (target.Body.UserData is Limb limb) { attackResult = attack.DoDamageToLimb(User, limb, item.WorldPosition, 1.0f); if (limb.character != null) @@ -350,13 +367,12 @@ namespace Barotrauma.Items.Components { contained.SetTransform(item.SimPosition, contained.body.Rotation); } - //contained.Condition = 0.0f; //Let the freaking .xml handle it jeez } } if (RemoveOnHit) { - Item.Spawner.AddToRemoveQueue(item); + Entity.Spawner.AddToRemoveQueue(item); } return true; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index 3248be891..32da8efeb 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -315,9 +315,9 @@ namespace Barotrauma public override string ToString() { #if CLIENT - return (GameMain.DebugDraw) ? Name + "(ID: " + ID + ")" : Name; + return (GameMain.DebugDraw) ? Name + " (ID: " + ID + ")" : Name; #elif SERVER - return Name + "(ID: " + ID + ")"; + return Name + " (ID: " + ID + ")"; #endif } diff --git a/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs b/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs index 02bd404de..a11ce2b95 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs @@ -80,7 +80,7 @@ namespace Barotrauma if (distSqr > displayRangeSqr) continue; //ignore reactors (don't want to blow them up) - if (item.GetComponent() == null) continue; + if (item.GetComponent() != null) continue; float distFactor = 1.0f - (float)Math.Sqrt(distSqr) / displayRange; diff --git a/Barotrauma/BarotraumaShared/Source/Map/Structure.cs b/Barotrauma/BarotraumaShared/Source/Map/Structure.cs index 1b5a52e8a..7348e0aa5 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Structure.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Structure.cs @@ -664,19 +664,20 @@ namespace Barotrauma float damageAmount = 0.0f; for (int i = 0; i < SectionCount; i++) { - if (Vector2.DistanceSquared(SectionPosition(i, true), worldPosition) <= attack.DamageRange * attack.DamageRange) + Rectangle sectionRect = sections[i].rect; + sectionRect.Y -= sections[i].rect.Height; + if (MathUtils.CircleIntersectsRectangle(transformedPos, attack.DamageRange, sectionRect)) { damageAmount = attack.GetStructureDamage(deltaTime); AddDamage(i, damageAmount, attacker); - #if CLIENT - GameMain.ParticleManager.CreateParticle("dustcloud", SectionPosition(i), 0.0f, 0.0f); + GameMain.ParticleManager.CreateParticle("dustcloud", SectionPosition(i), 0.0f, 0.0f); #endif } } #if CLIENT - if (playSound)// && !SectionBodyDisabled(i)) + if (playSound) { string damageSoundType = (attack.DamageType == DamageType.Blunt) ? "StructureBlunt" : "StructureSlash"; SoundPlayer.PlayDamageSound(damageSoundType, damageAmount, worldPosition, tags: Tags); diff --git a/Barotrauma/BarotraumaShared/Source/Networking/Client.cs b/Barotrauma/BarotraumaShared/Source/Networking/Client.cs index fff97a991..80965f69a 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/Client.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/Client.cs @@ -29,10 +29,20 @@ namespace Barotrauma.Networking public byte TeamID = 0; - public Character Character; + private Character character; + public Character Character + { + get { return character; } + set + { + character = value; + if (character != null) HasSpawned = true; + } + } public CharacterInfo CharacterInfo; public NetConnection Connection { get; set; } - public bool InGame; + public bool InGame; + public bool HasSpawned; //has the client spawned as a character during the current round public UInt16 LastRecvGeneralUpdate = 0; diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs index 81d633979..6dbd84ce9 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs @@ -780,11 +780,21 @@ namespace Barotrauma.Networking case ClientPermissions.Ban: string bannedName = inc.ReadString().ToLowerInvariant(); string banReason = inc.ReadString(); + bool range = inc.ReadBoolean(); + double durationSeconds = inc.ReadDouble(); + var bannedClient = connectedClients.Find(cl => cl != sender && cl.Name.ToLowerInvariant() == bannedName); if (bannedClient != null) { Log("Client \"" + sender.Name + "\" banned \"" + bannedClient.Name + "\".", ServerLog.MessageType.ServerMessage); - BanClient(bannedClient, string.IsNullOrEmpty(banReason) ? "Banned by " + sender.Name : banReason, false); + if (durationSeconds > 0) + { + BanClient(bannedClient, string.IsNullOrEmpty(banReason) ? "Banned by " + sender.Name : banReason, range, TimeSpan.FromSeconds(durationSeconds)); + } + else + { + BanClient(bannedClient, string.IsNullOrEmpty(banReason) ? "Banned by " + sender.Name : banReason, range); + } } break; case ClientPermissions.EndRound: @@ -1343,11 +1353,12 @@ namespace Barotrauma.Networking List characters = new List(); foreach (Client client in ConnectedClients) { - if (client.Character != null) - characters.Add(client.Character); + if (client.Character != null) characters.Add(client.Character); } - var max = (int)Math.Round(characters.Count * 0.2f, 1); - var traitorCount = Math.Max(1, TraitorsEnabled == YesNoMaybe.Maybe ? Rand.Int(max) + 1 : max); + if (Character != null) characters.Add(Character); + + int max = Math.Max(TraitorUseRatio ? (int)Math.Round(characters.Count * TraitorRatio, 1) : 1, 1); + int traitorCount = Rand.Int(max + 1); TraitorManager = new TraitorManager(this, traitorCount); if (TraitorManager.TraitorList.Count > 0) @@ -1501,6 +1512,7 @@ namespace Barotrauma.Networking foreach (Client client in connectedClients) { client.Character = null; + client.HasSpawned = false; client.InGame = false; } } @@ -1620,6 +1632,7 @@ namespace Barotrauma.Networking } client.Character = null; + client.HasSpawned = false; client.InGame = false; if (string.IsNullOrWhiteSpace(msg)) msg = client.Name + " has left the server"; diff --git a/Barotrauma/BarotraumaShared/Source/Networking/Voting.cs b/Barotrauma/BarotraumaShared/Source/Networking/Voting.cs index 28d430888..c78a1bede 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/Voting.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/Voting.cs @@ -111,11 +111,11 @@ namespace Barotrauma #endif break; case VoteType.EndRound: - if (sender.Character == null) return; + if (!sender.HasSpawned) return; sender.SetVote(voteType, inc.ReadBoolean()); - GameMain.NetworkMember.EndVoteCount = GameMain.Server.ConnectedClients.Count(c => c.Character != null && c.GetVote(VoteType.EndRound)); - GameMain.NetworkMember.EndVoteMax = GameMain.Server.ConnectedClients.Count(c => c.Character != null); + GameMain.NetworkMember.EndVoteCount = GameMain.Server.ConnectedClients.Count(c => c.HasSpawned && c.GetVote(VoteType.EndRound)); + GameMain.NetworkMember.EndVoteMax = GameMain.Server.ConnectedClients.Count(c => c.HasSpawned); break; case VoteType.Kick: diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index f1fc4f36e..3c33067bd 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,34 @@ +--------------------------------------------------------------------------------------------------------- +v0.8.1.5 +--------------------------------------------------------------------------------------------------------- + +- Added the option to automatically send crash reports and anonymous usage statistics to the developers. +The usage statistics include information such as the selected game mode, selected submarine, causes of +death and mission outcomes. When the game is started for the first time, a message box prompts you to +select whether you want to send the information or not. +- Fixed a bug that caused clients to get desynced when purchasing items in the multiplayer campaign. +- Added a signal component that adds the received signals together. +- Devices outside the submarine can be rewired in-game (not just in the sub editor). +- Fixed a crash caused by vision obstruction logic. +- Fixed clients being unable to give non-permanent or range bans. +- Clients are allowed to vote to end the round if they have spawned at some point during the round, +even if the character they controlled doesn't exist anymore. +- Dedicated servers can give clients the permission to use console commands that aren't available in +for dedicated server (e.g. los, lights, control) +- Banip, banid & kickid commands can be used by clients now (if they have the permission to do so). +- Spawnitem [item] inventory, ragdoll and kill commands target the character of the client using +the command instead of the host's character. +- Spawnitem can be used to spawn items in the inventory of a specific character. +- Fixed explosions with an EMP value only damaging reactors (when they should only ignore reactors). +- Fire can only explode oxygen tanks that are >25% full (otherwise the condition of the tank just drops +to 0). Prevents infinite explosions when an oxygen generator is on fire with oxygen tanks inside. +- Fixed projectiles with a damage range of 0 not applying their structuredamage value to structures. +- Items with a physics body can be used as pumps, so now it's possible to make portable items that remove +water from inside the sub. +- Fixed traitor ratio setting being ignored and the minimum number of traitors being 2. +- Fixed crashes caused by custom characters with no AI configuration. +- Character head and gender settings are saved. + --------------------------------------------------------------------------------------------------------- v0.8.1.4 ---------------------------------------------------------------------------------------------------------