From 1ef2cb750055db16500f2b9557f9cb39cef95c4e Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 18 Jul 2018 11:24:56 +0300 Subject: [PATCH 001/198] Fixed crashing if the Use method of a wire is triggered by a StatusEffect --- .../BarotraumaShared/Source/Items/Components/Signal/Wire.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs index 2e4cac0fe..ef4b6774d 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs @@ -222,6 +222,7 @@ namespace Barotrauma.Items.Components public override bool Use(float deltaTime, Character character = null) { + if (character == null) return false; if (character == Character.Controlled && character.SelectedConstruction != null) return false; if (newNodePos != Vector2.Zero && nodes.Count > 0 && Vector2.Distance(newNodePos, nodes[nodes.Count - 1]) > nodeDistance) From afb420810ee7f501d80ee0e57b282cbced2327e7 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 18 Jul 2018 11:41:39 +0300 Subject: [PATCH 002/198] Fixed dedicated server stopping GameAnalytics and never restarting it when the "restart" console command is used. + Fixed typo in GameAnalyticsManager. Closes #492 --- Barotrauma/BarotraumaServer/Source/GameMain.cs | 2 -- Barotrauma/BarotraumaServer/Source/Program.cs | 1 + Barotrauma/BarotraumaShared/Source/GameAnalyticsManager.cs | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Barotrauma/BarotraumaServer/Source/GameMain.cs b/Barotrauma/BarotraumaServer/Source/GameMain.cs index 699dc4b63..15f52d864 100644 --- a/Barotrauma/BarotraumaServer/Source/GameMain.cs +++ b/Barotrauma/BarotraumaServer/Source/GameMain.cs @@ -124,7 +124,6 @@ namespace Barotrauma public void CloseServer() { - if (GameSettings.SendUserStatistics) GameAnalytics.OnStop(); Server.Disconnect(); Server = null; } @@ -171,7 +170,6 @@ namespace Barotrauma stopwatch.Stop(); CloseServer(); - } public void ProcessInput() diff --git a/Barotrauma/BarotraumaServer/Source/Program.cs b/Barotrauma/BarotraumaServer/Source/Program.cs index 8d0c41ba1..4e6ea36bc 100644 --- a/Barotrauma/BarotraumaServer/Source/Program.cs +++ b/Barotrauma/BarotraumaServer/Source/Program.cs @@ -31,6 +31,7 @@ namespace Barotrauma inputThread.Start(); game.Run(); inputThread.Abort(); inputThread.Join(); + if (GameSettings.SendUserStatistics) GameAnalytics.OnStop(); } catch (Exception e) { diff --git a/Barotrauma/BarotraumaShared/Source/GameAnalyticsManager.cs b/Barotrauma/BarotraumaShared/Source/GameAnalyticsManager.cs index fa24b69e9..c9983f7a5 100644 --- a/Barotrauma/BarotraumaShared/Source/GameAnalyticsManager.cs +++ b/Barotrauma/BarotraumaShared/Source/GameAnalyticsManager.cs @@ -7,7 +7,7 @@ namespace Barotrauma { public static void Init() { -#if DEBUB +#if DEBUG GameAnalytics.SetEnabledInfoLog(true); #endif GameAnalytics.ConfigureBuild(GameMain.Version.ToString()); From e0455caaaaa52beb5693ae755178e4390544424a Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 13 Jul 2018 13:15:14 +0300 Subject: [PATCH 003/198] Cherry-picked 869b725 from dev --- .../Source/Items/Components/Signal/Wire.cs | 8 +++++ .../Components/Signal/ConnectionPanel.cs | 1 + .../Source/Items/Components/Signal/Wire.cs | 34 +++++++++++++++---- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Wire.cs index 297a045d0..57fc2835d 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Wire.cs @@ -81,6 +81,14 @@ namespace Barotrauma.Items.Components if (IsActive && nodes.Count > 0 && Vector2.Distance(newNodePos, nodes[nodes.Count - 1]) > nodeDistance) { + Submarine sub = null; + if (connections[0] != null && connections[0].Item.Submarine != null) sub = connections[0].Item.Submarine; + if (connections[1] != null && connections[1].Item.Submarine != null) sub = connections[1].Item.Submarine; + if (sub != null) + { + drawOffset = sub.DrawPosition + sub.HiddenSubPosition; + } + WireSection.Draw( spriteBatch, new Vector2(nodes[nodes.Count - 1].X, nodes[nodes.Count - 1].Y) + drawOffset, diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs index f01912854..7cffc8836 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs @@ -1,4 +1,5 @@ using Barotrauma.Networking; +using FarseerPhysics; using Lidgren.Network; using System; using System.Collections.Generic; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs index ef4b6774d..63c32afde 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs @@ -35,6 +35,7 @@ namespace Barotrauma.Items.Components private Connection[] connections; + private bool canPlaceNode; private Vector2 newNodePos; public bool Hidden, Locked; @@ -158,13 +159,14 @@ namespace Barotrauma.Items.Components ic.Drop(null); } if (item.Container != null) item.Container.RemoveContained(this.item); - if (item.body != null) item.body.Enabled = false; IsActive = false; CleanNodes(); } + + if (item.body != null) item.Submarine = newConnection.Item.Submarine; if (sendNetworkEvent) { @@ -211,13 +213,31 @@ namespace Barotrauma.Items.Components if (connections[0] != null && connections[0].Item.Submarine != null) sub = connections[0].Item.Submarine; if (connections[1] != null && connections[1].Item.Submarine != null) sub = connections[1].Item.Submarine; - if ((item.Submarine != sub || sub == null) && Screen.Selected != GameMain.SubEditorScreen) + if (Screen.Selected != GameMain.SubEditorScreen) { - ClearConnections(); - return; - } + //cannot run wires from sub to another + if (sub == null || (item.Submarine != sub && sub != null && item.Submarine != null)) + { + ClearConnections(); + return; + } - newNodePos = RoundNode(item.Position, item.CurrentHull) - sub.HiddenSubPosition; + if (item.CurrentHull == null) + { + newNodePos = item.WorldPosition - sub.Position - sub.HiddenSubPosition; + canPlaceNode = false; + } + else + { + newNodePos = RoundNode(item.Position, item.CurrentHull) - sub.HiddenSubPosition; + canPlaceNode = true; + } + } + else + { + newNodePos = RoundNode(item.Position, item.CurrentHull) - sub.HiddenSubPosition; + canPlaceNode = true; + } } public override bool Use(float deltaTime, Character character = null) @@ -225,7 +245,7 @@ namespace Barotrauma.Items.Components if (character == null) return false; if (character == Character.Controlled && character.SelectedConstruction != null) return false; - if (newNodePos != Vector2.Zero && nodes.Count > 0 && Vector2.Distance(newNodePos, nodes[nodes.Count - 1]) > nodeDistance) + if (newNodePos != Vector2.Zero && canPlaceNode && nodes.Count > 0 && Vector2.Distance(newNodePos, nodes[nodes.Count - 1]) > nodeDistance) { nodes.Add(newNodePos); UpdateSections(); From 41800aff7718de2ca8882a8bb85563ec85fc20c4 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 13 Jul 2018 14:32:42 +0300 Subject: [PATCH 004/198] Cherry-picked d0b61b3 from dev. --- .../Source/Items/Components/Signal/Wire.cs | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs index 63c32afde..cdbc012e9 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs @@ -37,13 +37,20 @@ namespace Barotrauma.Items.Components private bool canPlaceNode; private Vector2 newNodePos; - + public bool Hidden, Locked; public Connection[] Connections { get { return connections; } } + + [Serialize(5000.0f, false)] + public float MaxLength + { + get; + set; + } public Wire(Item item, XElement element) : base(item, element) @@ -232,14 +239,43 @@ namespace Barotrauma.Items.Components newNodePos = RoundNode(item.Position, item.CurrentHull) - sub.HiddenSubPosition; canPlaceNode = true; } + + //prevent the wire from extending too far when rewiring + if (nodes.Count > 0) + { + Character user = item.ParentInventory?.Owner as Character; + if (user == null) return; + + Vector2 prevNodePos = nodes[nodes.Count - 1]; + prevNodePos += sub.HiddenSubPosition; + + float currLength = 0.0f; + for (int i = 0; i < nodes.Count - 1; i++) + { + currLength += Vector2.Distance(nodes[i], nodes[i + 1]); + } + currLength += Vector2.Distance(nodes[nodes.Count - 1], newNodePos); + + if (currLength > MaxLength) + { + Vector2 pullBackDir = Vector2.Normalize(nodes[nodes.Count - 1] - newNodePos); + user.AnimController.Collider.ApplyForce(pullBackDir * user.Mass * 50.0f); + user.AnimController.UpdateUseItem(true, user.SimPosition + pullBackDir * 2.0f); + if (currLength > MaxLength * 1.5f) + { + ClearConnections(); + return; + } + } + } } else { newNodePos = RoundNode(item.Position, item.CurrentHull) - sub.HiddenSubPosition; canPlaceNode = true; - } + } } - + public override bool Use(float deltaTime, Character character = null) { if (character == null) return false; From d00b52882f75851dbb3a9ea2532a5b5d35742f24 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Sun, 1 Apr 2018 22:42:53 +0300 Subject: [PATCH 005/198] Added UpdateUseItem method from d253863 --- .../Characters/Animation/AnimController.cs | 1 + .../Animation/HumanoidAnimController.cs | 35 +++++++++++++++++++ .../Items/Components/Holdable/Pickable.cs | 14 ++------ .../Source/Items/Components/Signal/Wire.cs | 3 +- 4 files changed, 40 insertions(+), 13 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/AnimController.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/AnimController.cs index 7fcc25d17..018805efd 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/AnimController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/AnimController.cs @@ -68,6 +68,7 @@ namespace Barotrauma public virtual void DragCharacter(Character target) { } + public virtual void UpdateUseItem(bool allowMovement, Vector2 handPos) { } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs index ae83fb868..a83e895c0 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs @@ -24,6 +24,8 @@ namespace Barotrauma private float inWaterTimer; private bool swimming; + + private float useItemTimer; protected override float TorsoPosition { @@ -180,6 +182,12 @@ namespace Barotrauma case Animation.UsingConstruction: default: + if (Anim == Animation.UsingConstruction) + { + useItemTimer -= deltaTime; + if (useItemTimer <= 0.0f) Anim = Animation.None; + } + if (character.SelectedCharacter != null) DragCharacter(character.SelectedCharacter); //0.5 second delay for switching between swimming and walking @@ -1277,6 +1285,33 @@ namespace Barotrauma hand.body.SmoothRotate((ang2 + handAngle * Dir), 100.0f * force); } + public override void UpdateUseItem(bool allowMovement, Vector2 handPos) + { + var leftHand = GetLimb(LimbType.LeftHand); + var rightHand = GetLimb(LimbType.RightHand); + + useItemTimer = 0.5f; + Anim = Animation.UsingConstruction; + + if (!allowMovement) + { + TargetMovement = Vector2.Zero; + TargetDir = handPos.X > character.SimPosition.X ? Direction.Right : Direction.Left; + if (Vector2.Distance(character.SimPosition, handPos) > 1.0f) + { + TargetMovement = Vector2.Normalize(handPos - character.SimPosition); + } + } + + leftHand.Disabled = true; + leftHand.pullJoint.Enabled = true; + leftHand.pullJoint.WorldAnchorB = handPos; + + rightHand.Disabled = true; + rightHand.pullJoint.Enabled = true; + rightHand.pullJoint.WorldAnchorB = handPos; + } + public override void Flip() { base.Flip(); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Pickable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Pickable.cs index e9daebd1d..58d06df34 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Pickable.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Pickable.cs @@ -127,18 +127,8 @@ namespace Barotrauma.Items.Components Color.Red, Color.Green); #endif - picker.AnimController.Anim = AnimController.Animation.UsingConstruction; - - picker.AnimController.TargetMovement = Vector2.Zero; - - leftHand.Disabled = true; - leftHand.pullJoint.Enabled = true; - leftHand.pullJoint.WorldAnchorB = item.SimPosition + Vector2.UnitY * ((pickTimer / 10.0f) % 0.1f); - - rightHand.Disabled = true; - rightHand.pullJoint.Enabled = true; - rightHand.pullJoint.WorldAnchorB = item.SimPosition + Vector2.UnitY * ((pickTimer / 10.0f) % 0.1f); - + picker.AnimController.UpdateUseItem(true, item.SimPosition + Vector2.UnitY * ((pickTimer / 10.0f) % 0.1f)); + pickTimer += CoroutineManager.DeltaTime; yield return CoroutineStatus.Running; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs index cdbc012e9..e253efe36 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs @@ -258,7 +258,8 @@ namespace Barotrauma.Items.Components if (currLength > MaxLength) { - Vector2 pullBackDir = Vector2.Normalize(nodes[nodes.Count - 1] - newNodePos); + Vector2 diff = nodes[nodes.Count - 1] - newNodePos; + Vector2 pullBackDir = diff == Vector2.Zero ? Vector2.Zero : Vector2.Normalize(diff); user.AnimController.Collider.ApplyForce(pullBackDir * user.Mass * 50.0f); user.AnimController.UpdateUseItem(true, user.SimPosition + pullBackDir * 2.0f); if (currLength > MaxLength * 1.5f) From 7799e053695b27eb12faae3fed8976e7058e4d35 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 18 Jul 2018 12:59:17 +0300 Subject: [PATCH 006/198] GameAnalyticsManager sends the name and MD5 hash of the currently running exe (can be used to recognize code modifications that use the vanilla content package) --- .../Source/GameAnalyticsManager.cs | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/Source/GameAnalyticsManager.cs b/Barotrauma/BarotraumaShared/Source/GameAnalyticsManager.cs index c9983f7a5..81374dab6 100644 --- a/Barotrauma/BarotraumaShared/Source/GameAnalyticsManager.cs +++ b/Barotrauma/BarotraumaShared/Source/GameAnalyticsManager.cs @@ -1,5 +1,9 @@ using GameAnalyticsSDK.Net; using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Security.Cryptography; namespace Barotrauma { @@ -11,9 +15,31 @@ namespace Barotrauma GameAnalytics.SetEnabledInfoLog(true); #endif GameAnalytics.ConfigureBuild(GameMain.Version.ToString()); + + string exePath = Assembly.GetEntryAssembly().Location; + string exeName = null; + Md5Hash exeHash = null; + exeName = Path.GetFileNameWithoutExtension(exePath).Replace(":", ""); + var md5 = MD5.Create(); + try + { + using (var stream = File.OpenRead(exePath)) + { + exeHash = new Md5Hash(stream); + } + } + catch (Exception e) + { + DebugConsole.ThrowError("Error while calculating MD5 hash for the executable \"" + exePath + "\"", e); + } + + GameAnalytics.AddDesignEvent("Executable:" + + (string.IsNullOrEmpty(exeName) ? "Unknown" : exeName) + ":" + + ((exeHash == null) ? "Unknown" : exeHash.ShortHash)); + GameAnalytics.ConfigureAvailableCustomDimensions01("singleplayer", "multiplayer", "editor"); GameAnalytics.Initialize("a3a073c20982de7c15d21e840e149122", "9010ad9a671233b8d9610d76cec8c897d9ff3ba7"); - + string contentPackageName = GameMain.Config?.SelectedContentPackage?.Name; if (!string.IsNullOrEmpty(contentPackageName)) { From cbb207dbca53ea8b0fdd28722b1a86681c054284 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 18 Jul 2018 12:59:59 +0300 Subject: [PATCH 007/198] Cleanbuild console command resets the user statistics setting --- Barotrauma/BarotraumaClient/Source/DebugConsole.cs | 2 ++ Barotrauma/BarotraumaClient/Source/GameMain.cs | 10 ++++++++-- Barotrauma/BarotraumaShared/Source/GameSettings.cs | 10 +++++++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs index b095508b2..0a3a91434 100644 --- a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs @@ -438,6 +438,8 @@ namespace Barotrauma NewMessage("Resolution set to 0 x 0 (screen resolution will be used)", Color.Green); NewMessage("Fullscreen enabled", Color.Green); + GameSettings.ShowUserStatisticsPrompt = true; + GameSettings.VerboseLogging = false; if (GameMain.Config.MasterServerUrl != "http://www.undertowgames.com/baromaster") diff --git a/Barotrauma/BarotraumaClient/Source/GameMain.cs b/Barotrauma/BarotraumaClient/Source/GameMain.cs index 73363e61b..5a572c463 100644 --- a/Barotrauma/BarotraumaClient/Source/GameMain.cs +++ b/Barotrauma/BarotraumaClient/Source/GameMain.cs @@ -134,7 +134,7 @@ namespace Barotrauma Config.WasGameUpdated = false; Config.Save("config.xml"); } - + ApplyGraphicsSettings(); Content.RootDirectory = "Content"; @@ -244,12 +244,18 @@ namespace Barotrauma new string[] { "Yes", "No" }); userStatsPrompt.Buttons[0].OnClicked += (btn, userdata) => { + GameSettings.ShowUserStatisticsPrompt = false; GameSettings.SendUserStatistics = true; GameAnalyticsManager.Init(); return true; }; userStatsPrompt.Buttons[0].OnClicked += userStatsPrompt.Close; - userStatsPrompt.Buttons[1].OnClicked += (btn, userdata) => { GameSettings.SendUserStatistics = false; return true; }; + userStatsPrompt.Buttons[1].OnClicked += (btn, userdata) => + { + GameSettings.ShowUserStatisticsPrompt = false; + GameSettings.SendUserStatistics = false; + return true; + }; userStatsPrompt.Buttons[1].OnClicked += userStatsPrompt.Close; } else if (GameSettings.SendUserStatistics) diff --git a/Barotrauma/BarotraumaShared/Source/GameSettings.cs b/Barotrauma/BarotraumaShared/Source/GameSettings.cs index a476f143f..fbc3b2fe8 100644 --- a/Barotrauma/BarotraumaShared/Source/GameSettings.cs +++ b/Barotrauma/BarotraumaShared/Source/GameSettings.cs @@ -164,7 +164,7 @@ namespace Barotrauma GameMain.Config.Save("config.xml"); } } - public static bool ShowUserStatisticsPrompt { get; private set; } + public static bool ShowUserStatisticsPrompt { get; set; } public GameSettings(string filePath) { @@ -334,8 +334,12 @@ namespace Barotrauma new XAttribute("soundvolume", soundVolume), new XAttribute("verboselogging", VerboseLogging), new XAttribute("savedebugconsolelogs", SaveDebugConsoleLogs), - new XAttribute("enablesplashscreen", EnableSplashScreen), - new XAttribute("senduserstatistics", sendUserStatistics)); + new XAttribute("enablesplashscreen", EnableSplashScreen)); + + if (!ShowUserStatisticsPrompt) + { + doc.Root.Add(new XAttribute("senduserstatistics", sendUserStatistics)); + } if (WasGameUpdated) { From adb63adbb1f19a8282549933a16fb2a64f720079 Mon Sep 17 00:00:00 2001 From: Nilanth Animosus Date: Wed, 18 Jul 2018 11:01:53 +0100 Subject: [PATCH 008/198] Update Homoglyphs Updated the list of Homoglyphs from https://github.com/codebox/homoglyph/ --- .../Source/Utils/Homoglyphs.cs | 233 ++++++++++-------- 1 file changed, 137 insertions(+), 96 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Utils/Homoglyphs.cs b/Barotrauma/BarotraumaShared/Source/Utils/Homoglyphs.cs index 70157f98f..30ce32a0c 100644 --- a/Barotrauma/BarotraumaShared/Source/Utils/Homoglyphs.cs +++ b/Barotrauma/BarotraumaShared/Source/Utils/Homoglyphs.cs @@ -13,7 +13,7 @@ namespace Barotrauma new uint[]{0x24,0xff04}, new uint[]{0x25,0xff05}, new uint[]{0x26,0xa778,0xff06}, - new uint[]{0x27,0x60,0xb4,0x2b9,0x2bb,0x2bc,0x2bd,0x2be,0x2c8,0x2ca,0x2cb,0x2f4,0x374,0x384,0x55a,0x55d,0x5d9,0x5f3,0x7f4,0x7f5,0x144a,0x16cc,0x1fbd,0x1fbf,0x1fef,0x1ffd,0x1ffe,0x2018,0x2019,0x201b,0x2032,0x2035,0xa78c,0xff07,0xff40}, + new uint[]{0x27,0x60,0xb4,0x2b9,0x2bb,0x2bc,0x2bd,0x2be,0x2c8,0x2ca,0x2cb,0x2f4,0x374,0x384,0x55a,0x55d,0x5d9,0x5f3,0x7f4,0x7f5,0x144a,0x16cc,0x1fbd,0x1fbf,0x1fef,0x1ffd,0x1ffe,0x2018,0x2019,0x201b,0x2032,0x2035,0xa78c,0xff07,0xff40,0x16f51,0x16f52}, new uint[]{0x28,0x2768,0x2772,0x3014,0xfd3e,0xff08,0xff3b}, new uint[]{0x29,0x2769,0x2773,0x3015,0xfd3f,0xff09,0xff3d}, new uint[]{0x2a,0x66d,0x204e,0x2217,0xff0a,0x1031f}, @@ -21,76 +21,75 @@ namespace Barotrauma new uint[]{0x2c,0xb8,0x60d,0x66b,0x201a,0xa4f9,0xff0c}, new uint[]{0x2d,0x2d7,0x6d4,0x2010,0x2011,0x2012,0x2013,0x2043,0x2212,0x2796,0x2cba,0xfe58}, new uint[]{0x2e,0x660,0x6f0,0x701,0x702,0x2024,0xa4f8,0xa60e,0xff0e,0x10a50,0x1d16d}, - new uint[]{0x2f,0x1735,0x2041,0x2044,0x2215,0x2571,0x27cb,0x29f8,0x2cc6,0x2f03,0x3033,0x31d3,0x4e3f,0xff0f}, - new uint[]{0x30,0x4f,0x39f,0x41e,0x555,0x7c0,0x9e6,0xb20,0xb66,0xd20,0x2c9e,0x2d54,0x3007,0xa4f3,0xff10,0xff2f,0x10292,0x102ab,0x10404,0x10516,0x114d0,0x118b5,0x118e0,0x1d40e,0x1d442,0x1d476,0x1d4aa,0x1d4de,0x1d512,0x1d546,0x1d57a,0x1d5ae,0x1d5e2,0x1d616,0x1d64a,0x1d67e,0x1d6b6,0x1d6f0,0x1d72a,0x1d764,0x1d79e,0x1d7ce,0x1d7d8,0x1d7e2,0x1d7ec,0x1d7f6}, - new uint[]{0x31,0x49,0x6c,0x7c,0x196,0x1c0,0x399,0x406,0x4c0,0x5c0,0x5d5,0x5df,0x627,0x661,0x6f1,0x7ca,0x16c1,0x2110,0x2111,0x2113,0x2160,0x217c,0x2223,0x2c92,0x2d4f,0xa4f2,0xfe8d,0xfe8e,0xff11,0xff29,0xff4c,0xffe8,0x1028a,0x10309,0x10320,0x1d408,0x1d425,0x1d43c,0x1d459,0x1d470,0x1d48d,0x1d4c1,0x1d4d8,0x1d4f5,0x1d529,0x1d540,0x1d55d,0x1d574,0x1d591,0x1d5a8,0x1d5c5,0x1d5dc,0x1d5f9,0x1d610,0x1d62d,0x1d644,0x1d661,0x1d678,0x1d695,0x1d6b0,0x1d6ea,0x1d724,0x1d75e,0x1d798,0x1d7cf,0x1d7d9,0x1d7e3,0x1d7ed,0x1d7f7,0x1e8c7,0x1ee00,0x1ee80}, - new uint[]{0x32,0x1a7,0x3e8,0x14bf,0xa644,0xa75a,0xff12,0x1d7d0,0x1d7da,0x1d7e4,0x1d7ee,0x1d7f8}, - new uint[]{0x33,0x1b7,0x21c,0x417,0x4e0,0x2ccc,0xa76a,0xa7ab,0xff13,0x118ca,0x1d7d1,0x1d7db,0x1d7e5,0x1d7ef,0x1d7f9}, + new uint[]{0x2f,0x1735,0x2041,0x2044,0x2215,0x2571,0x27cb,0x29f8,0x2cc6,0x2f03,0x3033,0x30ce,0x31d3,0x4e3f,0xff0f,0x1d23a}, + new uint[]{0x30,0x4f,0x6f,0x39f,0x3bf,0x3c3,0x41e,0x43e,0x555,0x585,0x5e1,0x647,0x665,0x6be,0x6c1,0x6d5,0x6f5,0x7c0,0x966,0x9e6,0xa66,0xae6,0xb20,0xb66,0xbe6,0xc02,0xc66,0xc82,0xce6,0xd02,0xd20,0xd66,0xd82,0xe50,0xed0,0x101d,0x1040,0x10ff,0x12d0,0x1d0f,0x1d11,0x2134,0x2c9e,0x2c9f,0x2d54,0x3007,0xa4f3,0xab3d,0xfba6,0xfba7,0xfba8,0xfba9,0xfbaa,0xfbab,0xfbac,0xfbad,0xfee9,0xfeea,0xfeeb,0xfeec,0xff10,0xff2f,0xff4f,0x10292,0x102ab,0x10404,0x1042c,0x104c2,0x104ea,0x10516,0x114d0,0x118b5,0x118c8,0x118d7,0x118e0,0x1d40e,0x1d428,0x1d442,0x1d45c,0x1d476,0x1d490,0x1d4aa,0x1d4de,0x1d4f8,0x1d512,0x1d52c,0x1d546,0x1d560,0x1d57a,0x1d594,0x1d5ae,0x1d5c8,0x1d5e2,0x1d5fc,0x1d616,0x1d630,0x1d64a,0x1d664,0x1d67e,0x1d698,0x1d6b6,0x1d6d0,0x1d6d4,0x1d6f0,0x1d70a,0x1d70e,0x1d72a,0x1d744,0x1d748,0x1d764,0x1d77e,0x1d782,0x1d79e,0x1d7b8,0x1d7bc,0x1d7ce,0x1d7d8,0x1d7e2,0x1d7ec,0x1d7f6,0x1ee24,0x1ee64,0x1ee84}, + new uint[]{0x31,0x49,0x6c,0x7c,0x196,0x1c0,0x399,0x406,0x4c0,0x5c0,0x5d5,0x5df,0x627,0x661,0x6f1,0x7ca,0x16c1,0x2110,0x2111,0x2113,0x2160,0x217c,0x2223,0x23fd,0x2c92,0x2d4f,0xa4f2,0xfe8d,0xfe8e,0xff11,0xff29,0xff4c,0xffe8,0x1028a,0x10309,0x10320,0x16f28,0x1d408,0x1d425,0x1d43c,0x1d459,0x1d470,0x1d48d,0x1d4c1,0x1d4d8,0x1d4f5,0x1d529,0x1d540,0x1d55d,0x1d574,0x1d591,0x1d5a8,0x1d5c5,0x1d5dc,0x1d5f9,0x1d610,0x1d62d,0x1d644,0x1d661,0x1d678,0x1d695,0x1d6b0,0x1d6ea,0x1d724,0x1d75e,0x1d798,0x1d7cf,0x1d7d9,0x1d7e3,0x1d7ed,0x1d7f7,0x1e8c7,0x1ee00,0x1ee80}, + new uint[]{0x32,0x1a7,0x3e8,0x14bf,0xa644,0xa6ef,0xa75a,0xff12,0x1d7d0,0x1d7da,0x1d7e4,0x1d7ee,0x1d7f8}, + new uint[]{0x33,0x1b7,0x21c,0x417,0x4e0,0x2ccc,0xa76a,0xa7ab,0xff13,0x118ca,0x16f3b,0x1d206,0x1d7d1,0x1d7db,0x1d7e5,0x1d7ef,0x1d7f9}, new uint[]{0x34,0x13ce,0xff14,0x118af,0x1d7d2,0x1d7dc,0x1d7e6,0x1d7f0,0x1d7fa}, new uint[]{0x35,0x1bc,0xff15,0x118bb,0x1d7d3,0x1d7dd,0x1d7e7,0x1d7f1,0x1d7fb}, new uint[]{0x36,0x431,0x13ee,0x2cd2,0xff16,0x118d5,0x1d7d4,0x1d7de,0x1d7e8,0x1d7f2,0x1d7fc}, - new uint[]{0x37,0xff17,0x118c6,0x1d7d5,0x1d7df,0x1d7e9,0x1d7f3,0x1d7fd}, + new uint[]{0x37,0xff17,0x104d2,0x118c6,0x1d212,0x1d7d5,0x1d7df,0x1d7e9,0x1d7f3,0x1d7fd}, new uint[]{0x38,0x222,0x223,0x9ea,0xa6a,0xb03,0xff18,0x1031a,0x1d7d6,0x1d7e0,0x1d7ea,0x1d7f4,0x1d7fe,0x1e8cb}, - new uint[]{0x39,0x9ed,0xa67,0xb68,0x2cca,0xa76e,0xff19,0x118ac,0x118cc,0x118d6,0x1d7d7,0x1d7e1,0x1d7eb,0x1d7f5,0x1d7ff}, + new uint[]{0x39,0x9ed,0xa67,0xb68,0xd6d,0x2cca,0xa76e,0xff19,0x118ac,0x118cc,0x118d6,0x1d7d7,0x1d7e1,0x1d7eb,0x1d7f5,0x1d7ff}, new uint[]{0x3a,0x2d0,0x2f8,0x589,0x5c3,0x703,0x704,0x903,0xa83,0x16ec,0x1803,0x1809,0x205a,0x2236,0xa4fd,0xa789,0xfe30,0xff1a}, new uint[]{0x3b,0x37e,0xff1b}, - new uint[]{0x3c,0x2c2,0x1438,0x16b2,0x2039,0x276e,0xff1c}, + new uint[]{0x3c,0x2c2,0x1438,0x16b2,0x2039,0x276e,0xff1c,0x1d236}, new uint[]{0x3d,0x1400,0x2e40,0x30a0,0xa4ff,0xff1d}, - new uint[]{0x3e,0x2c3,0x1433,0x203a,0x276f,0xff1e}, - new uint[]{0x3f,0x241,0x294,0x97d,0x13ae,0xff1f}, + new uint[]{0x3e,0x2c3,0x1433,0x203a,0x276f,0xff1e,0x16f3f,0x1d237}, + new uint[]{0x3f,0x241,0x294,0x97d,0x13ae,0xa6eb,0xff1f}, new uint[]{0x40,0xff20}, - new uint[]{0x41,0x391,0x410,0x13aa,0x15c5,0x1d00,0xa4ee,0xff21,0x102a0,0x1d400,0x1d434,0x1d468,0x1d49c,0x1d4d0,0x1d504,0x1d538,0x1d56c,0x1d5a0,0x1d5d4,0x1d608,0x1d63c,0x1d670,0x1d6a8,0x1d6e2,0x1d71c,0x1d756,0x1d790}, - new uint[]{0x42,0x299,0x392,0x412,0x432,0x13f4,0x15f7,0x16d2,0x212c,0xa4d0,0xa7b4,0xff22,0x10282,0x102a1,0x10301,0x1d401,0x1d435,0x1d469,0x1d4d1,0x1d505,0x1d539,0x1d56d,0x1d5a1,0x1d5d5,0x1d609,0x1d63d,0x1d671,0x1d6a9,0x1d6e3,0x1d71d,0x1d757,0x1d791}, + new uint[]{0x41,0x391,0x410,0x13aa,0x15c5,0x1d00,0xa4ee,0xab7a,0xff21,0x102a0,0x16f40,0x1d400,0x1d434,0x1d468,0x1d49c,0x1d4d0,0x1d504,0x1d538,0x1d56c,0x1d5a0,0x1d5d4,0x1d608,0x1d63c,0x1d670,0x1d6a8,0x1d6e2,0x1d71c,0x1d756,0x1d790}, + new uint[]{0x42,0x299,0x392,0x412,0x432,0x13f4,0x13fc,0x15f7,0x16d2,0x212c,0xa4d0,0xa7b4,0xff22,0x10282,0x102a1,0x10301,0x1d401,0x1d435,0x1d469,0x1d4d1,0x1d505,0x1d539,0x1d56d,0x1d5a1,0x1d5d5,0x1d609,0x1d63d,0x1d671,0x1d6a9,0x1d6e3,0x1d71d,0x1d757,0x1d791}, new uint[]{0x43,0x3f9,0x421,0x13df,0x2102,0x212d,0x216d,0x2ca4,0xa4da,0xff23,0x102a2,0x10302,0x10415,0x1051c,0x118e9,0x118f2,0x1d402,0x1d436,0x1d46a,0x1d49e,0x1d4d2,0x1d56e,0x1d5a2,0x1d5d6,0x1d60a,0x1d63e,0x1d672,0x1f74c}, - new uint[]{0x44,0x13a0,0x15de,0x15ea,0x2145,0x216e,0xa4d3,0xff24,0x1d403,0x1d437,0x1d46b,0x1d49f,0x1d4d3,0x1d507,0x1d53b,0x1d56f,0x1d5a3,0x1d5d7,0x1d60b,0x1d63f,0x1d673}, - new uint[]{0x45,0x395,0x415,0x13ac,0x2130,0x22ff,0x2d39,0xa4f0,0xff25,0x10286,0x118a6,0x118ae,0x1d404,0x1d438,0x1d46c,0x1d4d4,0x1d508,0x1d53c,0x1d570,0x1d5a4,0x1d5d8,0x1d60c,0x1d640,0x1d674,0x1d6ac,0x1d6e6,0x1d720,0x1d75a,0x1d794}, - new uint[]{0x46,0x3dc,0x15b4,0x2131,0xa4dd,0xa798,0xff26,0x10287,0x102a5,0x10525,0x118a2,0x118c2,0x1d405,0x1d439,0x1d46d,0x1d4d5,0x1d509,0x1d53d,0x1d571,0x1d5a5,0x1d5d9,0x1d60d,0x1d641,0x1d675,0x1d7ca}, - new uint[]{0x47,0x262,0x50c,0x50d,0x13c0,0x13f3,0xa4d6,0xff27,0x1d406,0x1d43a,0x1d46e,0x1d4a2,0x1d4d6,0x1d50a,0x1d53e,0x1d572,0x1d5a6,0x1d5da,0x1d60e,0x1d642,0x1d676}, - new uint[]{0x48,0x29c,0x397,0x41d,0x43d,0x13bb,0x157c,0x210b,0x210c,0x210d,0x2c8e,0xa4e7,0xff28,0x102cf,0x1d407,0x1d43b,0x1d46f,0x1d4d7,0x1d573,0x1d5a7,0x1d5db,0x1d60f,0x1d643,0x1d677,0x1d6ae,0x1d6e8,0x1d722,0x1d75c,0x1d796}, - new uint[]{0x4a,0x37f,0x408,0x13ab,0x148d,0xa4d9,0xa7b2,0xff2a,0x1d409,0x1d43d,0x1d471,0x1d4a5,0x1d4d9,0x1d50d,0x1d541,0x1d575,0x1d5a9,0x1d5dd,0x1d611,0x1d645,0x1d679}, + new uint[]{0x44,0x13a0,0x15de,0x15ea,0x1d05,0x2145,0x216e,0xa4d3,0xab70,0xff24,0x1d403,0x1d437,0x1d46b,0x1d49f,0x1d4d3,0x1d507,0x1d53b,0x1d56f,0x1d5a3,0x1d5d7,0x1d60b,0x1d63f,0x1d673}, + new uint[]{0x45,0x395,0x415,0x13ac,0x1d07,0x2130,0x22ff,0x2d39,0xa4f0,0xab7c,0xff25,0x10286,0x118a6,0x118ae,0x1d404,0x1d438,0x1d46c,0x1d4d4,0x1d508,0x1d53c,0x1d570,0x1d5a4,0x1d5d8,0x1d60c,0x1d640,0x1d674,0x1d6ac,0x1d6e6,0x1d720,0x1d75a,0x1d794}, + new uint[]{0x46,0x3dc,0x15b4,0x2131,0xa4dd,0xa798,0xff26,0x10287,0x102a5,0x10525,0x118a2,0x118c2,0x1d213,0x1d405,0x1d439,0x1d46d,0x1d4d5,0x1d509,0x1d53d,0x1d571,0x1d5a5,0x1d5d9,0x1d60d,0x1d641,0x1d675,0x1d7ca}, + new uint[]{0x47,0x262,0x50c,0x50d,0x13c0,0x13f3,0x13fb,0xa4d6,0xab90,0xff27,0x1d406,0x1d43a,0x1d46e,0x1d4a2,0x1d4d6,0x1d50a,0x1d53e,0x1d572,0x1d5a6,0x1d5da,0x1d60e,0x1d642,0x1d676}, + new uint[]{0x48,0x29c,0x397,0x41d,0x43d,0x13bb,0x157c,0x210b,0x210c,0x210d,0x2c8e,0xa4e7,0xab8b,0xff28,0x102cf,0x1d407,0x1d43b,0x1d46f,0x1d4d7,0x1d573,0x1d5a7,0x1d5db,0x1d60f,0x1d643,0x1d677,0x1d6ae,0x1d6e8,0x1d722,0x1d75c,0x1d796}, + new uint[]{0x4a,0x37f,0x408,0x13ab,0x148d,0x1d0a,0xa4d9,0xa7b2,0xab7b,0xff2a,0x1d409,0x1d43d,0x1d471,0x1d4a5,0x1d4d9,0x1d50d,0x1d541,0x1d575,0x1d5a9,0x1d5dd,0x1d611,0x1d645,0x1d679}, new uint[]{0x4b,0x39a,0x41a,0x13e6,0x16d5,0x212a,0x2c94,0xa4d7,0xff2b,0x10518,0x1d40a,0x1d43e,0x1d472,0x1d4a6,0x1d4da,0x1d50e,0x1d542,0x1d576,0x1d5aa,0x1d5de,0x1d612,0x1d646,0x1d67a,0x1d6b1,0x1d6eb,0x1d725,0x1d75f,0x1d799}, - new uint[]{0x4c,0x29f,0x13de,0x14aa,0x2112,0x216c,0x2cd0,0x2cd1,0xa4e1,0xff2c,0x1041b,0x10443,0x10526,0x118a3,0x118b2,0x1d40b,0x1d43f,0x1d473,0x1d4db,0x1d50f,0x1d543,0x1d577,0x1d5ab,0x1d5df,0x1d613,0x1d647,0x1d67b}, + new uint[]{0x4c,0x29f,0x13de,0x14aa,0x2112,0x216c,0x2cd0,0x2cd1,0xa4e1,0xabae,0xff2c,0x1041b,0x10443,0x10526,0x118a3,0x118b2,0x16f16,0x1d22a,0x1d40b,0x1d43f,0x1d473,0x1d4db,0x1d50f,0x1d543,0x1d577,0x1d5ab,0x1d5df,0x1d613,0x1d647,0x1d67b}, new uint[]{0x4d,0x39c,0x3fa,0x41c,0x13b7,0x15f0,0x16d6,0x2133,0x216f,0x2c98,0xa4df,0xff2d,0x102b0,0x10311,0x1d40c,0x1d440,0x1d474,0x1d4dc,0x1d510,0x1d544,0x1d578,0x1d5ac,0x1d5e0,0x1d614,0x1d648,0x1d67c,0x1d6b3,0x1d6ed,0x1d727,0x1d761,0x1d79b}, new uint[]{0x4e,0x274,0x39d,0x2115,0x2c9a,0xa4e0,0xff2e,0x10513,0x1d40d,0x1d441,0x1d475,0x1d4a9,0x1d4dd,0x1d511,0x1d579,0x1d5ad,0x1d5e1,0x1d615,0x1d649,0x1d67d,0x1d6b4,0x1d6ee,0x1d728,0x1d762,0x1d79c}, - new uint[]{0x50,0x3a1,0x420,0x13e2,0x146d,0x2119,0x2ca2,0xa4d1,0xff30,0x10295,0x1d40f,0x1d443,0x1d477,0x1d4ab,0x1d4df,0x1d513,0x1d57b,0x1d5af,0x1d5e3,0x1d617,0x1d64b,0x1d67f,0x1d6b8,0x1d6f2,0x1d72c,0x1d766,0x1d7a0}, + new uint[]{0x50,0x3a1,0x420,0x13e2,0x146d,0x1d18,0x1d29,0x2119,0x2ca2,0xa4d1,0xabb2,0xff30,0x10295,0x1d40f,0x1d443,0x1d477,0x1d4ab,0x1d4df,0x1d513,0x1d57b,0x1d5af,0x1d5e3,0x1d617,0x1d64b,0x1d67f,0x1d6b8,0x1d6f2,0x1d72c,0x1d766,0x1d7a0}, new uint[]{0x51,0x211a,0x2d55,0xff31,0x1d410,0x1d444,0x1d478,0x1d4ac,0x1d4e0,0x1d514,0x1d57c,0x1d5b0,0x1d5e4,0x1d618,0x1d64c,0x1d680}, - new uint[]{0x52,0x1a6,0x280,0x13a1,0x13d2,0x1587,0x16b1,0x211b,0x211c,0x211d,0xa4e3,0xff32,0x1d411,0x1d445,0x1d479,0x1d4e1,0x1d57d,0x1d5b1,0x1d5e5,0x1d619,0x1d64d,0x1d681}, - new uint[]{0x53,0x405,0x54f,0x13d5,0x13da,0xa4e2,0xff33,0x10296,0x10420,0x1d412,0x1d446,0x1d47a,0x1d4ae,0x1d4e2,0x1d516,0x1d54a,0x1d57e,0x1d5b2,0x1d5e6,0x1d61a,0x1d64e,0x1d682}, - new uint[]{0x54,0x3a4,0x422,0x13a2,0x22a4,0x27d9,0x2ca6,0xa4d4,0xff34,0x10297,0x102b1,0x10315,0x118bc,0x1d413,0x1d447,0x1d47b,0x1d4af,0x1d4e3,0x1d517,0x1d54b,0x1d57f,0x1d5b3,0x1d5e7,0x1d61b,0x1d64f,0x1d683,0x1d6bb,0x1d6f5,0x1d72f,0x1d769,0x1d7a3,0x1f768}, - new uint[]{0x55,0x54d,0x144c,0x222a,0x22c3,0xa4f4,0xff35,0x118b8,0x1d414,0x1d448,0x1d47c,0x1d4b0,0x1d4e4,0x1d518,0x1d54c,0x1d580,0x1d5b4,0x1d5e8,0x1d61c,0x1d650,0x1d684}, - new uint[]{0x56,0x474,0x667,0x6f7,0x13d9,0x142f,0x2164,0x2d38,0xa4e6,0xff36,0x1051d,0x118a0,0x1d415,0x1d449,0x1d47d,0x1d4b1,0x1d4e5,0x1d519,0x1d54d,0x1d581,0x1d5b5,0x1d5e9,0x1d61d,0x1d651,0x1d685}, + new uint[]{0x52,0x1a6,0x280,0x13a1,0x13d2,0x1587,0x16b1,0x211b,0x211c,0x211d,0xa4e3,0xab71,0xaba2,0xff32,0x104b4,0x16f35,0x1d216,0x1d411,0x1d445,0x1d479,0x1d4e1,0x1d57d,0x1d5b1,0x1d5e5,0x1d619,0x1d64d,0x1d681}, + new uint[]{0x53,0x405,0x54f,0x13d5,0x13da,0xa4e2,0xff33,0x10296,0x10420,0x16f3a,0x1d412,0x1d446,0x1d47a,0x1d4ae,0x1d4e2,0x1d516,0x1d54a,0x1d57e,0x1d5b2,0x1d5e6,0x1d61a,0x1d64e,0x1d682}, + new uint[]{0x54,0x3a4,0x3c4,0x422,0x442,0x13a2,0x1d1b,0x22a4,0x27d9,0x2ca6,0xa4d4,0xab72,0xff34,0x10297,0x102b1,0x10315,0x118bc,0x16f0a,0x1d413,0x1d447,0x1d47b,0x1d4af,0x1d4e3,0x1d517,0x1d54b,0x1d57f,0x1d5b3,0x1d5e7,0x1d61b,0x1d64f,0x1d683,0x1d6bb,0x1d6d5,0x1d6f5,0x1d70f,0x1d72f,0x1d749,0x1d769,0x1d783,0x1d7a3,0x1d7bd,0x1f768}, + new uint[]{0x55,0x54d,0x1200,0x144c,0x222a,0x22c3,0xa4f4,0xff35,0x104ce,0x118b8,0x16f42,0x1d414,0x1d448,0x1d47c,0x1d4b0,0x1d4e4,0x1d518,0x1d54c,0x1d580,0x1d5b4,0x1d5e8,0x1d61c,0x1d650,0x1d684}, + new uint[]{0x56,0x474,0x667,0x6f7,0x13d9,0x142f,0x2164,0x2d38,0xa4e6,0xa6df,0xff36,0x1051d,0x118a0,0x16f08,0x1d20d,0x1d415,0x1d449,0x1d47d,0x1d4b1,0x1d4e5,0x1d519,0x1d54d,0x1d581,0x1d5b5,0x1d5e9,0x1d61d,0x1d651,0x1d685}, new uint[]{0x57,0x51c,0x13b3,0x13d4,0xa4ea,0xff37,0x118e6,0x118ef,0x1d416,0x1d44a,0x1d47e,0x1d4b2,0x1d4e6,0x1d51a,0x1d54e,0x1d582,0x1d5b6,0x1d5ea,0x1d61e,0x1d652,0x1d686}, new uint[]{0x58,0x3a7,0x425,0x166d,0x16b7,0x2169,0x2573,0x2cac,0x2d5d,0xa4eb,0xa7b3,0xff38,0x10290,0x102b4,0x10317,0x10322,0x10527,0x118ec,0x1d417,0x1d44b,0x1d47f,0x1d4b3,0x1d4e7,0x1d51b,0x1d54f,0x1d583,0x1d5b7,0x1d5eb,0x1d61f,0x1d653,0x1d687,0x1d6be,0x1d6f8,0x1d732,0x1d76c,0x1d7a6}, - new uint[]{0x59,0x3a5,0x3d2,0x4ae,0x13a9,0x13bd,0x2ca8,0xa4ec,0xff39,0x102b2,0x118a4,0x1d418,0x1d44c,0x1d480,0x1d4b4,0x1d4e8,0x1d51c,0x1d550,0x1d584,0x1d5b8,0x1d5ec,0x1d620,0x1d654,0x1d688,0x1d6bc,0x1d6f6,0x1d730,0x1d76a,0x1d7a4}, + new uint[]{0x59,0x3a5,0x3d2,0x423,0x4ae,0x13a9,0x13bd,0x2ca8,0xa4ec,0xff39,0x102b2,0x118a4,0x16f43,0x1d418,0x1d44c,0x1d480,0x1d4b4,0x1d4e8,0x1d51c,0x1d550,0x1d584,0x1d5b8,0x1d5ec,0x1d620,0x1d654,0x1d688,0x1d6bc,0x1d6f6,0x1d730,0x1d76a,0x1d7a4}, new uint[]{0x5a,0x396,0x13c3,0x2124,0x2128,0xa4dc,0xff3a,0x102f5,0x118a9,0x118e5,0x1d419,0x1d44d,0x1d481,0x1d4b5,0x1d4e9,0x1d585,0x1d5b9,0x1d5ed,0x1d621,0x1d655,0x1d689,0x1d6ad,0x1d6e7,0x1d721,0x1d75b,0x1d795}, - new uint[]{0x5c,0x2216,0x27cd,0x29f5,0x29f9,0x2f02,0x31d4,0x4e36,0xfe68,0xff3c}, + new uint[]{0x5c,0x2216,0x27cd,0x29f5,0x29f9,0x2f02,0x31d4,0x4e36,0xfe68,0xff3c,0x1d20f,0x1d23b}, new uint[]{0x5e,0x2c4,0x2c6}, new uint[]{0x5f,0x7fa,0xfe4d,0xfe4e,0xfe4f,0xff3f}, new uint[]{0x61,0x251,0x3b1,0x430,0x237a,0xff41,0x1d41a,0x1d44e,0x1d482,0x1d4b6,0x1d4ea,0x1d51e,0x1d552,0x1d586,0x1d5ba,0x1d5ee,0x1d622,0x1d656,0x1d68a,0x1d6c2,0x1d6fc,0x1d736,0x1d770,0x1d7aa}, new uint[]{0x62,0x184,0x42c,0x13cf,0x15af,0xff42,0x1d41b,0x1d44f,0x1d483,0x1d4b7,0x1d4eb,0x1d51f,0x1d553,0x1d587,0x1d5bb,0x1d5ef,0x1d623,0x1d657,0x1d68b}, - new uint[]{0x63,0x3f2,0x441,0x1d04,0x217d,0x2ca5,0xff43,0x1043d,0x1d41c,0x1d450,0x1d484,0x1d4b8,0x1d4ec,0x1d520,0x1d554,0x1d588,0x1d5bc,0x1d5f0,0x1d624,0x1d658,0x1d68c}, + new uint[]{0x63,0x3f2,0x441,0x1d04,0x217d,0x2ca5,0xabaf,0xff43,0x1043d,0x1d41c,0x1d450,0x1d484,0x1d4b8,0x1d4ec,0x1d520,0x1d554,0x1d588,0x1d5bc,0x1d5f0,0x1d624,0x1d658,0x1d68c}, new uint[]{0x64,0x501,0x13e7,0x146f,0x2146,0x217e,0xa4d2,0xff44,0x1d41d,0x1d451,0x1d485,0x1d4b9,0x1d4ed,0x1d521,0x1d555,0x1d589,0x1d5bd,0x1d5f1,0x1d625,0x1d659,0x1d68d}, new uint[]{0x65,0x435,0x4bd,0x212e,0x212f,0x2147,0xab32,0xff45,0x1d41e,0x1d452,0x1d486,0x1d4ee,0x1d522,0x1d556,0x1d58a,0x1d5be,0x1d5f2,0x1d626,0x1d65a,0x1d68e}, - new uint[]{0x66,0x17f,0x584,0x1e9d,0xa799,0xab35,0xff46,0x1d41f,0x1d453,0x1d487,0x1d4bb,0x1d4ef,0x1d523,0x1d557,0x1d58b,0x1d5bf,0x1d5f3,0x1d627,0x1d65b,0x1d68f}, + new uint[]{0x66,0x17f,0x3dd,0x584,0x1e9d,0xa799,0xab35,0xff46,0x1d41f,0x1d453,0x1d487,0x1d4bb,0x1d4ef,0x1d523,0x1d557,0x1d58b,0x1d5bf,0x1d5f3,0x1d627,0x1d65b,0x1d68f,0x1d7cb}, new uint[]{0x67,0x18d,0x261,0x581,0x1d83,0x210a,0xff47,0x1d420,0x1d454,0x1d488,0x1d4f0,0x1d524,0x1d558,0x1d58c,0x1d5c0,0x1d5f4,0x1d628,0x1d65c,0x1d690}, new uint[]{0x68,0x4bb,0x570,0x13c2,0x210e,0xff48,0x1d421,0x1d489,0x1d4bd,0x1d4f1,0x1d525,0x1d559,0x1d58d,0x1d5c1,0x1d5f5,0x1d629,0x1d65d,0x1d691}, - new uint[]{0x69,0x131,0x269,0x26a,0x2db,0x37a,0x3b9,0x456,0x4cf,0x13a5,0x1fbe,0x2139,0x2148,0x2170,0x2373,0xa647,0xff49,0x118c3,0x1d422,0x1d456,0x1d48a,0x1d4be,0x1d4f2,0x1d526,0x1d55a,0x1d58e,0x1d5c2,0x1d5f6,0x1d62a,0x1d65e,0x1d692,0x1d6a4,0x1d6ca,0x1d704,0x1d73e,0x1d778,0x1d7b2}, + new uint[]{0x69,0x131,0x269,0x26a,0x2db,0x37a,0x3b9,0x456,0x4cf,0x13a5,0x1fbe,0x2139,0x2148,0x2170,0x2373,0xa647,0xab75,0xff49,0x118c3,0x1d422,0x1d456,0x1d48a,0x1d4be,0x1d4f2,0x1d526,0x1d55a,0x1d58e,0x1d5c2,0x1d5f6,0x1d62a,0x1d65e,0x1d692,0x1d6a4,0x1d6ca,0x1d704,0x1d73e,0x1d778,0x1d7b2}, new uint[]{0x6a,0x3f3,0x458,0x2149,0xff4a,0x1d423,0x1d457,0x1d48b,0x1d4bf,0x1d4f3,0x1d527,0x1d55b,0x1d58f,0x1d5c3,0x1d5f7,0x1d62b,0x1d65f,0x1d693}, - new uint[]{0x6b,0x138,0x3ba,0x3f0,0x43a,0x1d0b,0x2c95,0xff4b,0x1d424,0x1d458,0x1d48c,0x1d4c0,0x1d4f4,0x1d528,0x1d55c,0x1d590,0x1d5c4,0x1d5f8,0x1d62c,0x1d660,0x1d694,0x1d6cb,0x1d6de,0x1d705,0x1d718,0x1d73f,0x1d752,0x1d779,0x1d78c,0x1d7b3,0x1d7c6}, + new uint[]{0x6b,0xff4b,0x1d424,0x1d458,0x1d48c,0x1d4c0,0x1d4f4,0x1d528,0x1d55c,0x1d590,0x1d5c4,0x1d5f8,0x1d62c,0x1d660,0x1d694}, new uint[]{0x6d,0xff4d}, - new uint[]{0x6e,0x3c0,0x3d6,0x43f,0x578,0x57c,0x1d28,0x213c,0xff4e,0x1d427,0x1d45b,0x1d48f,0x1d4c3,0x1d4f7,0x1d52b,0x1d55f,0x1d593,0x1d5c7,0x1d5fb,0x1d62f,0x1d663,0x1d697,0x1d6d1,0x1d6e1,0x1d70b,0x1d71b,0x1d745,0x1d755,0x1d77f,0x1d78f,0x1d7b9,0x1d7c9}, - new uint[]{0x6f,0x3bf,0x3c3,0x43e,0x585,0x5e1,0x647,0x665,0x6be,0x6c1,0x6d5,0x6f5,0x966,0xa66,0xae6,0xbe6,0xc02,0xc66,0xc82,0xce6,0xd02,0xd66,0xd82,0xe50,0xed0,0x101d,0x1040,0x10ff,0x1d0f,0x1d11,0x2134,0x2c9f,0xab3d,0xfba6,0xfba7,0xfba8,0xfba9,0xfbaa,0xfbab,0xfbac,0xfbad,0xfee9,0xfeea,0xfeeb,0xfeec,0xff4f,0x1042c,0x118c8,0x118d7,0x1d428,0x1d45c,0x1d490,0x1d4f8,0x1d52c,0x1d560,0x1d594,0x1d5c8,0x1d5fc,0x1d630,0x1d664,0x1d698,0x1d6d0,0x1d6d4,0x1d70a,0x1d70e,0x1d744,0x1d748,0x1d77e,0x1d782,0x1d7b8,0x1d7bc,0x1ee24,0x1ee64,0x1ee84}, + new uint[]{0x6e,0x578,0x57c,0xff4e,0x1d427,0x1d45b,0x1d48f,0x1d4c3,0x1d4f7,0x1d52b,0x1d55f,0x1d593,0x1d5c7,0x1d5fb,0x1d62f,0x1d663,0x1d697}, new uint[]{0x70,0x3c1,0x3f1,0x440,0x2374,0x2ca3,0xff50,0x1d429,0x1d45d,0x1d491,0x1d4c5,0x1d4f9,0x1d52d,0x1d561,0x1d595,0x1d5c9,0x1d5fd,0x1d631,0x1d665,0x1d699,0x1d6d2,0x1d6e0,0x1d70c,0x1d71a,0x1d746,0x1d754,0x1d780,0x1d78e,0x1d7ba,0x1d7c8}, new uint[]{0x71,0x51b,0x563,0x566,0xff51,0x1d42a,0x1d45e,0x1d492,0x1d4c6,0x1d4fa,0x1d52e,0x1d562,0x1d596,0x1d5ca,0x1d5fe,0x1d632,0x1d666,0x1d69a}, - new uint[]{0x72,0x433,0x1d26,0x2c85,0xab47,0xab48,0xff52,0x1d42b,0x1d45f,0x1d493,0x1d4c7,0x1d4fb,0x1d52f,0x1d563,0x1d597,0x1d5cb,0x1d5ff,0x1d633,0x1d667,0x1d69b}, - new uint[]{0x73,0x1bd,0x455,0xa731,0xff53,0x10448,0x118c1,0x1d42c,0x1d460,0x1d494,0x1d4c8,0x1d4fc,0x1d530,0x1d564,0x1d598,0x1d5cc,0x1d600,0x1d634,0x1d668,0x1d69c}, - new uint[]{0x74,0x3c4,0x442,0x1d1b,0xff54,0x1d42d,0x1d461,0x1d495,0x1d4c9,0x1d4fd,0x1d531,0x1d565,0x1d599,0x1d5cd,0x1d601,0x1d635,0x1d669,0x1d69d,0x1d6d5,0x1d70f,0x1d749,0x1d783,0x1d7bd}, - new uint[]{0x75,0x28b,0x3c5,0x446,0x57d,0x1d1c,0xa79f,0xab4e,0xab52,0xff55,0x118d8,0x1d42e,0x1d462,0x1d496,0x1d4ca,0x1d4fe,0x1d532,0x1d566,0x1d59a,0x1d5ce,0x1d602,0x1d636,0x1d66a,0x1d69e,0x1d6d6,0x1d710,0x1d74a,0x1d784,0x1d7be}, - new uint[]{0x76,0x3bd,0x475,0x5d8,0x1d20,0x2174,0x2228,0x22c1,0xff56,0x118c0,0x1d42f,0x1d463,0x1d497,0x1d4cb,0x1d4ff,0x1d533,0x1d567,0x1d59b,0x1d5cf,0x1d603,0x1d637,0x1d66b,0x1d69f,0x1d6ce,0x1d708,0x1d742,0x1d77c,0x1d7b6}, - new uint[]{0x77,0xff57}, + new uint[]{0x72,0x433,0x1d26,0x2c85,0xab47,0xab48,0xab81,0xff52,0x1d42b,0x1d45f,0x1d493,0x1d4c7,0x1d4fb,0x1d52f,0x1d563,0x1d597,0x1d5cb,0x1d5ff,0x1d633,0x1d667,0x1d69b}, + new uint[]{0x73,0x1bd,0x455,0xa731,0xabaa,0xff53,0x10448,0x118c1,0x1d42c,0x1d460,0x1d494,0x1d4c8,0x1d4fc,0x1d530,0x1d564,0x1d598,0x1d5cc,0x1d600,0x1d634,0x1d668,0x1d69c}, + new uint[]{0x74,0xff54,0x1d42d,0x1d461,0x1d495,0x1d4c9,0x1d4fd,0x1d531,0x1d565,0x1d599,0x1d5cd,0x1d601,0x1d635,0x1d669,0x1d69d}, + new uint[]{0x75,0x28b,0x3c5,0x57d,0x1d1c,0xa79f,0xab4e,0xab52,0xff55,0x104f6,0x118d8,0x1d42e,0x1d462,0x1d496,0x1d4ca,0x1d4fe,0x1d532,0x1d566,0x1d59a,0x1d5ce,0x1d602,0x1d636,0x1d66a,0x1d69e,0x1d6d6,0x1d710,0x1d74a,0x1d784,0x1d7be}, + new uint[]{0x76,0x3bd,0x475,0x5d8,0x1d20,0x2174,0x2228,0x22c1,0xaba9,0xff56,0x11706,0x118c0,0x1d42f,0x1d463,0x1d497,0x1d4cb,0x1d4ff,0x1d533,0x1d567,0x1d59b,0x1d5cf,0x1d603,0x1d637,0x1d66b,0x1d69f,0x1d6ce,0x1d708,0x1d742,0x1d77c,0x1d7b6}, + new uint[]{0x77,0x26f,0x461,0x51d,0x561,0x1d21,0xab83,0xff57,0x1170a,0x1170e,0x1170f,0x1d430,0x1d464,0x1d498,0x1d4cc,0x1d500,0x1d534,0x1d568,0x1d59c,0x1d5d0,0x1d604,0x1d638,0x1d66c,0x1d6a0}, new uint[]{0x78,0xd7,0x445,0x1541,0x157d,0x166e,0x2179,0x292b,0x292c,0x2a2f,0xff58,0x1d431,0x1d465,0x1d499,0x1d4cd,0x1d501,0x1d535,0x1d569,0x1d59d,0x1d5d1,0x1d605,0x1d639,0x1d66d,0x1d6a1}, new uint[]{0x79,0x263,0x28f,0x3b3,0x443,0x4af,0x10e7,0x1d8c,0x1eff,0x213d,0xab5a,0xff59,0x118dc,0x1d432,0x1d466,0x1d49a,0x1d4ce,0x1d502,0x1d536,0x1d56a,0x1d59e,0x1d5d2,0x1d606,0x1d63a,0x1d66e,0x1d6a2,0x1d6c4,0x1d6fe,0x1d738,0x1d772,0x1d7ac}, - new uint[]{0x7a,0x1d22,0xff5a,0x118c4,0x1d433,0x1d467,0x1d49b,0x1d4cf,0x1d503,0x1d537,0x1d56b,0x1d59f,0x1d5d3,0x1d607,0x1d63b,0x1d66f,0x1d6a3}, + new uint[]{0x7a,0x1d22,0xab93,0xff5a,0x118c4,0x1d433,0x1d467,0x1d49b,0x1d4cf,0x1d503,0x1d537,0x1d56b,0x1d59f,0x1d5d3,0x1d607,0x1d63b,0x1d66f,0x1d6a3}, new uint[]{0x7b,0x2774,0xff5b,0x1d114}, new uint[]{0x7d,0x2775,0xff5d}, new uint[]{0x7e,0x2dc,0x1fc0,0x2053,0x223c}, @@ -106,7 +105,7 @@ namespace Barotrauma new uint[]{0xc4,0x4d2}, new uint[]{0xc5,0x226}, new uint[]{0xd6,0x150,0x4e6,0x2365}, - new uint[]{0xde,0x3f7}, + new uint[]{0xde,0x3f7,0x104c4}, new uint[]{0xdf,0x3b2,0x3d0,0x13f0,0xa7b5,0x1d6c3,0x1d6fd,0x1d737,0x1d771,0x1d7ab}, new uint[]{0xe4,0x4d3}, new uint[]{0xe5,0x227}, @@ -122,6 +121,7 @@ namespace Barotrauma new uint[]{0x123,0x1f5}, new uint[]{0x12c,0x1cf}, new uint[]{0x12d,0x1d0}, + new uint[]{0x138,0x3ba,0x3f0,0x43a,0x1d0b,0x2c95,0xabb6,0x1d6cb,0x1d6de,0x1d705,0x1d718,0x1d73f,0x1d752,0x1d779,0x1d78c,0x1d7b3,0x1d7c6}, new uint[]{0x146,0x272}, new uint[]{0x14e,0x1d1}, new uint[]{0x14f,0x1d2}, @@ -129,11 +129,11 @@ namespace Barotrauma new uint[]{0x163,0x1ab,0x21b,0x13bf}, new uint[]{0x16c,0x1d3}, new uint[]{0x16d,0x1d4}, - new uint[]{0x185,0x44c}, + new uint[]{0x185,0x44c,0xab9f}, new uint[]{0x186,0x3fd,0x2183,0xa4db,0x10423}, new uint[]{0x18e,0x2203,0x2d3a,0xa4f1}, new uint[]{0x18f,0x4d8}, - new uint[]{0x190,0x510,0x13cb,0x2107,0x10401}, + new uint[]{0x190,0x510,0x13cb,0x2107,0x10401,0x16f2d,0x1d221}, new uint[]{0x1a8,0x3e9,0x1d24,0xa645}, new uint[]{0x1a9,0x3a3,0x2140,0x2211,0x2d49,0x1d6ba,0x1d6f4,0x1d72e,0x1d768,0x1d7a2}, new uint[]{0x1b1,0x162e,0x1634,0x2127}, @@ -141,20 +141,23 @@ namespace Barotrauma new uint[]{0x1f6,0x50a}, new uint[]{0x21d,0x292,0x4e1,0x10f3,0x2ccd,0xa76b}, new uint[]{0x237,0x575,0x1d6a5}, - new uint[]{0x245,0x39b,0x41b,0x668,0x6f8,0x1431,0x2d37,0xa4e5,0x1028d,0x1d6b2,0x1d6ec,0x1d726,0x1d760,0x1d79a}, + new uint[]{0x242,0xab7e}, + new uint[]{0x245,0x39b,0x41b,0x668,0x6f8,0x1431,0x2d37,0xa4e5,0xa6ce,0x1028d,0x104b0,0x16f3d,0x1d6b2,0x1d6ec,0x1d726,0x1d760,0x1d79a}, new uint[]{0x24b,0x1d90}, new uint[]{0x254,0x37b,0x1d10,0x2184,0x1044b}, - new uint[]{0x25b,0x3b5,0x3f5,0x454,0x511,0x22f4,0x2c89,0xa793,0x10429,0x118ce,0x1d6c6,0x1d6dc,0x1d700,0x1d716,0x1d73a,0x1d750,0x1d774,0x1d78a,0x1d7ae,0x1d7c4}, + new uint[]{0x25b,0x3b5,0x3f5,0x454,0x511,0x22f4,0x2c89,0xa793,0xab9b,0x10429,0x118ce,0x1d6c6,0x1d6dc,0x1d700,0x1d716,0x1d73a,0x1d750,0x1d774,0x1d78a,0x1d7ae,0x1d7c4}, new uint[]{0x25c,0x437,0x1d08}, - new uint[]{0x25e,0xa79d,0x10442}, - new uint[]{0x270,0x57a}, + new uint[]{0x25e,0x10442}, + new uint[]{0x270,0x57a,0x1223}, new uint[]{0x277,0x1043f}, new uint[]{0x278,0x3c6,0x3d5,0x444,0x2cab,0x1d6d7,0x1d6df,0x1d711,0x1d719,0x1d74b,0x1d753,0x1d785,0x1d78d,0x1d7bf,0x1d7c7}, new uint[]{0x27f,0x2129}, new uint[]{0x283,0x222b,0xab4d}, - new uint[]{0x28c,0x1d27}, - new uint[]{0x298,0x2299,0x2609,0x2a00,0x2d59,0xa668}, - new uint[]{0x29a,0x1042a}, + new uint[]{0x28c,0x1d27,0x104d8}, + new uint[]{0x28d,0x43c,0x1d0d,0xab87}, + new uint[]{0x298,0x2299,0x2609,0x2a00,0x2d59,0xa668,0x104c3}, + new uint[]{0x29a,0xa79d,0x1042a}, + new uint[]{0x2a1,0xa6cd}, new uint[]{0x2b3,0x18f4}, new uint[]{0x2bf,0x2d3,0x559}, new uint[]{0x2c1,0x2e4}, @@ -162,20 +165,20 @@ namespace Barotrauma new uint[]{0x2cf,0x375}, new uint[]{0x2d9,0x971,0xd4e}, new uint[]{0x2e1,0x18f3}, - new uint[]{0x2e2,0x18f5}, + new uint[]{0x2e2,0x18db,0x18f5}, new uint[]{0x2ea,0x2fb,0xa716}, new uint[]{0x2eb,0xa714}, new uint[]{0x2f3,0x3002}, new uint[]{0x300,0x340,0x953}, new uint[]{0x301,0x341,0x59c,0x59d,0x618,0x64e,0x747,0x954}, - new uint[]{0x302,0x311,0x65b,0x7ee,0x1cd0}, + new uint[]{0x302,0x311,0x65b,0x7ee,0x1cd0,0xa6f0}, new uint[]{0x303,0x342,0x653}, - new uint[]{0x304,0x305,0x659,0x7eb,0x1cd2}, + new uint[]{0x304,0x305,0x659,0x7eb,0x1cd2,0xa6f1}, new uint[]{0x306,0x30c,0x36e,0x658,0x65a,0xa67c}, new uint[]{0x307,0x358,0x5b9,0x5ba,0x5c1,0x5c2,0x5c4,0x6ec,0x740,0x741,0x7ed,0x8ea,0x902,0xa02,0xa82,0xbcd}, new uint[]{0x308,0x7f3,0x8eb}, new uint[]{0x309,0x302c}, - new uint[]{0x30a,0x366,0x5af,0x652,0x6df,0xb82,0xe4d,0xecd,0x1036,0x17c6,0x17d3,0x309a}, + new uint[]{0x30a,0x366,0x5af,0x652,0x6df,0xb82,0xe4d,0xecd,0x1036,0x17c6,0x17d3,0x2dea,0x309a,0x11300}, new uint[]{0x30b,0x64b,0x8f0}, new uint[]{0x30d,0x670}, new uint[]{0x30e,0x1cda}, @@ -187,9 +190,9 @@ namespace Barotrauma new uint[]{0x320,0x331,0x952}, new uint[]{0x321,0x326,0x327,0x339}, new uint[]{0x322,0x328,0x345,0x1ab7}, - new uint[]{0x323,0x5b4,0x5c5,0x65c,0x8ed,0x93c,0x9bc,0xa3c,0xabc,0xb3c,0x1cdd,0x10a3a,0x114c3}, + new uint[]{0x323,0x5b4,0x5c5,0x65c,0x8ed,0x93c,0x9bc,0xa3c,0xabc,0xb3c,0x1cdd,0x10a3a,0x111ca,0x114c3}, new uint[]{0x324,0x8ee,0x1cde}, - new uint[]{0x325,0x302d}, + new uint[]{0x325,0xf37,0x302d}, new uint[]{0x329,0x656,0x1cdc}, new uint[]{0x32b,0x1cd5}, new uint[]{0x32d,0x1cd9}, @@ -201,31 +204,36 @@ namespace Barotrauma new uint[]{0x352,0x900}, new uint[]{0x354,0x8f9}, new uint[]{0x355,0x8fa}, - new uint[]{0x370,0x13a8,0x13b0,0x2c75}, - new uint[]{0x376,0x418,0x10425}, + new uint[]{0x363,0x2df6}, + new uint[]{0x364,0x2df7}, + new uint[]{0x368,0x2ded}, + new uint[]{0x36f,0x2def}, + new uint[]{0x370,0x13a8,0x13b0,0x2c75,0xa6b1}, + new uint[]{0x376,0x418,0xa6a1,0x10425,0x1d20b}, new uint[]{0x377,0x438,0x1d0e,0x1044d}, new uint[]{0x37d,0xa73f}, - new uint[]{0x393,0x413,0x13b1,0x14a5,0x213e,0x2c84,0x1d6aa,0x1d6e4,0x1d71e,0x1d758,0x1d792}, - new uint[]{0x394,0x1403,0x2206,0x25b3,0x2c86,0x2d60,0x10285,0x102a3,0x1d6ab,0x1d6e5,0x1d71f,0x1d759,0x1d793,0x1f702}, + new uint[]{0x393,0x413,0x13b1,0x14a5,0x213e,0x2c84,0x16f07,0x1d6aa,0x1d6e4,0x1d71e,0x1d758,0x1d792}, + new uint[]{0x394,0x1403,0x2206,0x25b3,0x2c86,0x2d60,0x10285,0x102a3,0x16f1a,0x1d6ab,0x1d6e5,0x1d71f,0x1d759,0x1d793,0x1f702}, new uint[]{0x39e,0x1d6b5,0x1d6ef,0x1d729,0x1d763,0x1d79d}, - new uint[]{0x3a0,0x41f,0x213f,0x220f,0x2ca0,0x1d6b7,0x1d6f1,0x1d72b,0x1d765,0x1d79f}, - new uint[]{0x3a6,0x424,0x553,0x16f0,0x2caa,0x102b3,0x1d6bd,0x1d6f7,0x1d731,0x1d76b,0x1d7a5}, - new uint[]{0x3a8,0x470,0x16d8,0x2cae,0x102b5,0x1d6bf,0x1d6f9,0x1d733,0x1d76d,0x1d7a7}, + new uint[]{0x3a0,0x41f,0x213f,0x220f,0x2ca0,0xa6db,0x1d6b7,0x1d6f1,0x1d72b,0x1d765,0x1d79f}, + new uint[]{0x3a6,0x424,0x553,0x1240,0x16f0,0x2caa,0x102b3,0x1d6bd,0x1d6f7,0x1d731,0x1d76b,0x1d7a5}, + new uint[]{0x3a8,0x470,0x16d8,0x2cae,0x102b5,0x104d1,0x1d6bf,0x1d6f9,0x1d733,0x1d76d,0x1d7a7}, new uint[]{0x3a9,0x162f,0x1635,0x2126,0x102b6,0x1d6c0,0x1d6fa,0x1d734,0x1d76e,0x1d7a8}, new uint[]{0x3b4,0x56e,0x1577,0x1e9f,0x2e39,0x1d6c5,0x1d6ff,0x1d739,0x1d773,0x1d7ad}, new uint[]{0x3b6,0x1d6c7,0x1d701,0x1d73b,0x1d775,0x1d7af}, - new uint[]{0x3bb,0x2c96,0x1d6cc,0x1d706,0x1d740,0x1d77a,0x1d7b4}, + new uint[]{0x3bb,0x2c96,0x104db,0x1d6cc,0x1d706,0x1d740,0x1d77a,0x1d7b4}, new uint[]{0x3be,0x1d6cf,0x1d709,0x1d743,0x1d77d,0x1d7b7}, + new uint[]{0x3c0,0x3d6,0x43f,0x1d28,0x213c,0x1d6d1,0x1d6e1,0x1d70b,0x1d71b,0x1d745,0x1d755,0x1d77f,0x1d78f,0x1d7b9,0x1d7c9}, new uint[]{0x3c2,0x3db,0x1d6d3,0x1d70d,0x1d747,0x1d781,0x1d7bb}, new uint[]{0x3c7,0x2cad,0xab53,0xab55,0x1d6d8,0x1d712,0x1d74c,0x1d786,0x1d7c0}, - new uint[]{0x3c8,0x471,0x1d6d9,0x1d713,0x1d74d,0x1d787,0x1d7c1}, + new uint[]{0x3c8,0x471,0x104f9,0x1d6d9,0x1d713,0x1d74d,0x1d787,0x1d7c1}, new uint[]{0x3c9,0x2375,0x2cb1,0xa64d,0xa7b7,0x1d6da,0x1d714,0x1d74e,0x1d788,0x1d7c2}, new uint[]{0x3d7,0x2ce4}, new uint[]{0x3d8,0x102ad,0x10312}, - new uint[]{0x3dd,0x1d7cb}, new uint[]{0x3ec,0x2cdc}, new uint[]{0x3ff,0xa73e}, new uint[]{0x404,0x20ac,0x2c88,0xa792}, + new uint[]{0x40b,0x104cd}, new uint[]{0x40d,0x419}, new uint[]{0x428,0x2cbc}, new uint[]{0x42d,0x2108}, @@ -234,11 +242,18 @@ namespace Barotrauma new uint[]{0x448,0x2cbd}, new uint[]{0x44f,0x1d19}, new uint[]{0x459,0xab60}, - new uint[]{0x460,0x13c7,0x15ef}, + new uint[]{0x460,0x13c7,0x15ef,0x1d222}, new uint[]{0x4b6,0x4cb}, new uint[]{0x4b7,0x4cc}, - new uint[]{0x548,0x144e,0x2229,0x22c2,0xa4f5}, + new uint[]{0x4c3,0x104bc}, + new uint[]{0x4fe,0x1d202}, + new uint[]{0x53b,0x12ae}, + new uint[]{0x544,0x1206}, + new uint[]{0x548,0x1260,0x144e,0x2229,0x22c2,0xa4f5,0x1d245}, + new uint[]{0x54a,0x1323}, + new uint[]{0x54c,0x1261}, new uint[]{0x554,0x20bd}, + new uint[]{0x571,0x1294}, new uint[]{0x596,0x5ad}, new uint[]{0x598,0x5ae}, new uint[]{0x599,0x5a8}, @@ -282,7 +297,7 @@ namespace Barotrauma new uint[]{0x645,0xfee1,0xfee2,0xfee3,0xfee4,0x1ee0c,0x1ee2c,0x1ee6c,0x1ee8c,0x1eeac}, new uint[]{0x646,0xfee5,0xfee6,0xfee7,0xfee8,0x1ee0d,0x1ee2d,0x1ee4d,0x1ee6d,0x1ee8d,0x1eead}, new uint[]{0x648,0x8b1,0xfeed,0xfeee,0x102e4,0x1ee05,0x1ee85,0x1eea5}, - new uint[]{0x649,0x64a,0x66e,0x6ba,0x6cc,0x6d2,0xfb9e,0xfb9f,0xfbae,0xfbaf,0xfbe8,0xfbe9,0xfbfc,0xfbfd,0xfbfe,0xfbff,0xfeef,0xfef0,0xfef1,0xfef2,0xfef3,0xfef4,0x1ee09,0x1ee1c,0x1ee1d,0x1ee29,0x1ee49,0x1ee5d,0x1ee69,0x1ee7c,0x1ee89,0x1eea9}, + new uint[]{0x649,0x64a,0x66e,0x6ba,0x6cc,0x6d2,0x8bd,0xfb9e,0xfb9f,0xfbae,0xfbaf,0xfbe8,0xfbe9,0xfbfc,0xfbfd,0xfbfe,0xfbff,0xfeef,0xfef0,0xfef1,0xfef2,0xfef3,0xfef4,0x1ee09,0x1ee1c,0x1ee1d,0x1ee29,0x1ee49,0x1ee5d,0x1ee69,0x1ee7c,0x1ee89,0x1eea9}, new uint[]{0x64c,0x8e5,0x8e8,0x8f1}, new uint[]{0x64d,0x8f2}, new uint[]{0x655,0x65f}, @@ -291,7 +306,7 @@ namespace Barotrauma new uint[]{0x664,0x6f4}, new uint[]{0x666,0x6f6}, new uint[]{0x669,0x6f9,0x967,0x118e4}, - new uint[]{0x66f,0x6a1,0x1ee1e,0x1ee1f,0x1ee5f,0x1ee7e}, + new uint[]{0x66f,0x6a1,0x8bb,0x8bc,0x1ee1e,0x1ee1f,0x1ee5f,0x1ee7e}, new uint[]{0x671,0xfb50,0xfb51}, new uint[]{0x67a,0xfb5e,0xfb5f,0xfb60,0xfb61}, new uint[]{0x67b,0x6d0,0xfb52,0xfb53,0xfb54,0xfb55,0xfbe4,0xfbe5,0xfbe6,0xfbe7}, @@ -314,11 +329,13 @@ namespace Barotrauma new uint[]{0x6db,0x1ab4,0x20db}, new uint[]{0x73c,0x742}, new uint[]{0x754,0x767,0x8a9}, + new uint[]{0x93a,0x111cb}, new uint[]{0x93d,0xabd}, new uint[]{0x941,0xac1}, new uint[]{0x942,0xac2}, new uint[]{0x946,0xa4b}, new uint[]{0x94d,0xa4d,0xacd}, + new uint[]{0x964,0xa830}, new uint[]{0x968,0xae8}, new uint[]{0x969,0xae9}, new uint[]{0x96a,0xaea}, @@ -421,6 +438,8 @@ namespace Barotrauma new uint[]{0xe5b,0x17da}, new uint[]{0xf0b,0xf0c}, new uint[]{0xf62,0xf6a}, + new uint[]{0xfd5,0x5350}, + new uint[]{0xfd6,0x534d}, new uint[]{0x1041,0x1065}, new uint[]{0x10a0,0xa786}, new uint[]{0x1100,0x11a8,0x3131}, @@ -466,25 +485,30 @@ namespace Barotrauma new uint[]{0x1546,0x1623}, new uint[]{0x154a,0x1624}, new uint[]{0x15b5,0x2132,0xa4de}, - new uint[]{0x15b7,0xa7fb}, - new uint[]{0x15c4,0x2200,0x2c6f,0xa4ef}, + new uint[]{0x15b7,0xa7fb,0x1d230}, + new uint[]{0x15c4,0x2200,0x2c6f,0xa4ef,0x1d217}, new uint[]{0x15d2,0x2aab}, new uint[]{0x15d5,0x2aaa}, new uint[]{0x15e1,0xa4f7}, new uint[]{0x1646,0x1dbb}, new uint[]{0x1660,0xa4ed}, + new uint[]{0x16b9,0xa6b0}, new uint[]{0x16bc,0x16e1}, new uint[]{0x16bd,0x16c2,0x237f}, + new uint[]{0x16cb,0x1d23f}, new uint[]{0x16cf,0x2191}, new uint[]{0x16d0,0x21bf}, new uint[]{0x16da,0x21be,0x2a21}, new uint[]{0x16dc,0x22c4,0x25c7,0x25ca,0x2662,0x10294,0x118b7,0x1f754}, new uint[]{0x16de,0x22c8,0x2a1d}, + new uint[]{0x16e6,0x104d0}, new uint[]{0x16e8,0x2195}, new uint[]{0x16ef,0x2d63}, new uint[]{0x17a2,0x17a3}, new uint[]{0x1835,0x1855}, new uint[]{0x185c,0x1896}, + new uint[]{0x18d4,0x1dba}, + new uint[]{0x18d6,0x1d3e}, new uint[]{0x199e,0x19d0}, new uint[]{0x19b1,0x19d1}, new uint[]{0x1a45,0x1a80,0x1a90}, @@ -492,19 +516,24 @@ namespace Barotrauma new uint[]{0x1b11,0x1b53}, new uint[]{0x1b28,0x1b58}, new uint[]{0x1b50,0x1b5c}, - new uint[]{0x1d18,0x1d29}, new uint[]{0x1d34,0x1d78}, new uint[]{0x1d4b,0x1d9f}, new uint[]{0x1d4d,0x1da2}, new uint[]{0x1ddf,0x2de8}, + new uint[]{0x1dee,0x2dec}, + new uint[]{0x1e43,0xab51}, new uint[]{0x1e9a,0x1ea3}, new uint[]{0x1f7d,0x1ff4}, new uint[]{0x205d,0x22ee,0x2d57,0xfe19}, new uint[]{0x205e,0x2999,0x2d42,0x2e3d}, + new uint[]{0x2079,0xa770}, new uint[]{0x20b8,0x3012,0x3036}, + new uint[]{0x20e9,0xa66f}, new uint[]{0x2117,0x24c5}, new uint[]{0x2141,0xa4e8}, - new uint[]{0x2142,0xa4f6,0x10411}, + new uint[]{0x2142,0xa4f6,0x10411,0x16f26,0x1d215,0x1d22b}, + new uint[]{0x2143,0x16f00}, + new uint[]{0x2144,0x1d21b}, new uint[]{0x219e,0x2bec}, new uint[]{0x219f,0x2bed}, new uint[]{0x21a0,0x2bee}, @@ -525,21 +554,23 @@ namespace Barotrauma new uint[]{0x2261,0x2263}, new uint[]{0x228d,0x2a03}, new uint[]{0x228e,0x2a04}, + new uint[]{0x228f,0x1d238}, + new uint[]{0x2290,0x1d239}, new uint[]{0x2293,0x2a05}, new uint[]{0x2294,0x2a06}, - new uint[]{0x2295,0x2a01,0x102a8,0x1f728}, + new uint[]{0x2295,0x2a01,0xa69a,0x102a8,0x1f728}, new uint[]{0x2297,0x2a02}, new uint[]{0x229b,0x235f}, new uint[]{0x22a0,0x1f771}, new uint[]{0x22a1,0x1f755}, - new uint[]{0x22a5,0x27c2,0xa4d5,0xa7b1}, + new uint[]{0x22a5,0x27c2,0xa4d5,0xa7b1,0x1d21c}, new uint[]{0x22b2,0x25c1}, new uint[]{0x22b3,0x25b7}, new uint[]{0x2307,0xfe34}, new uint[]{0x2312,0x25e0}, new uint[]{0x2319,0x2a3d}, new uint[]{0x2324,0x2325}, - new uint[]{0x2329,0x276c,0x27e8,0x3008}, + new uint[]{0x2329,0x276c,0x27e8,0x3008,0x304f,0x31db,0x21fe8}, new uint[]{0x232a,0x276d,0x27e9,0x3009}, new uint[]{0x233b,0x29c7}, new uint[]{0x233e,0x25ce,0x29be}, @@ -558,6 +589,8 @@ namespace Barotrauma new uint[]{0x23e0,0xfe39}, new uint[]{0x23e1,0xfe3a}, new uint[]{0x23e5,0x25b1}, + new uint[]{0x23fb,0x23fc}, + new uint[]{0x23fe,0x263e,0x1f318}, new uint[]{0x2460,0x2780}, new uint[]{0x2461,0x2781}, new uint[]{0x2462,0x2782}, @@ -578,23 +611,26 @@ namespace Barotrauma new uint[]{0x25a1,0x2610}, new uint[]{0x25aa,0xffed}, new uint[]{0x25b6,0x25b8,0x25ba}, - new uint[]{0x25bd,0x102bc,0x1f704}, + new uint[]{0x25bd,0x102bc,0x1d214,0x1f704}, new uint[]{0x2625,0x1099e,0x132f9}, new uint[]{0x2627,0x2ce9}, new uint[]{0x2629,0x1f70a}, new uint[]{0x2630,0x2cb6}, new uint[]{0x263d,0x1f312,0x1f319}, - new uint[]{0x263e,0x1f318}, new uint[]{0x27e6,0x301a}, new uint[]{0x27e7,0x301b}, new uint[]{0x299a,0x29d9}, new uint[]{0x29d6,0x102c0}, new uint[]{0x29df,0x1f73a}, new uint[]{0x2a1f,0x2a3e}, + new uint[]{0x2c3f,0xa992}, new uint[]{0x2c70,0x1041f}, + new uint[]{0x2c76,0xab80}, + new uint[]{0x2ce8,0x101a0}, new uint[]{0x2d40,0x102b8}, + new uint[]{0x2e82,0x31d6,0x4e5b}, new uint[]{0x2e83,0x31df,0x4e5a}, - new uint[]{0x2e85,0x4ebb}, + new uint[]{0x2e85,0x30a4,0x4ebb}, new uint[]{0x2e89,0x5202}, new uint[]{0x2e8b,0x353e}, new uint[]{0x2e8e,0x5140,0xfa0c}, @@ -662,36 +698,34 @@ namespace Barotrauma new uint[]{0x2ef3,0x9f9f}, new uint[]{0x2f04,0x31e0,0x4e59}, new uint[]{0x2f05,0x31da,0x4e85}, - new uint[]{0x2f06,0x4e8c}, + new uint[]{0x2f06,0x30cb,0x4e8c}, new uint[]{0x2f07,0x4ea0}, new uint[]{0x2f08,0x4eba}, new uint[]{0x2f09,0x513f}, new uint[]{0x2f0a,0x5165}, - new uint[]{0x2f0b,0x516b}, + new uint[]{0x2f0b,0x30cf,0x516b}, new uint[]{0x2f0c,0x5182}, new uint[]{0x2f0d,0x5196}, new uint[]{0x2f0e,0x51ab}, new uint[]{0x2f0f,0x51e0}, new uint[]{0x2f10,0x51f5,0x2f81d}, new uint[]{0x2f11,0x5200}, - new uint[]{0x2f12,0x529b,0xf98a}, + new uint[]{0x2f12,0x30ab,0x529b,0xf98a}, new uint[]{0x2f13,0x52f9}, new uint[]{0x2f14,0x5315}, new uint[]{0x2f15,0x531a}, new uint[]{0x2f16,0x5338}, new uint[]{0x2f17,0x3038,0x5341}, - new uint[]{0x2f18,0x535c}, + new uint[]{0x2f18,0x30c8,0x535c}, new uint[]{0x2f19,0x5369}, new uint[]{0x2f1a,0x5382}, new uint[]{0x2f1b,0x53b6}, new uint[]{0x2f1c,0x53c8}, - new uint[]{0x2f1d,0x53e3}, - new uint[]{0x2f1e,0x56d7}, - new uint[]{0x2f1f,0x571f}, - new uint[]{0x2f20,0x58eb}, + new uint[]{0x2f1d,0x2f1e,0x30ed,0x53e3,0x56d7}, + new uint[]{0x2f1f,0x2f20,0x571f,0x58eb}, new uint[]{0x2f21,0x5902}, new uint[]{0x2f22,0x590a}, - new uint[]{0x2f23,0x5915}, + new uint[]{0x2f23,0x30bf,0x5915}, new uint[]{0x2f24,0x5927}, new uint[]{0x2f25,0x5973,0xf981}, new uint[]{0x2f26,0x5b50}, @@ -702,7 +736,7 @@ namespace Barotrauma new uint[]{0x2f2c,0x5c6e,0xfa3c,0x2f878}, new uint[]{0x2f2d,0x5c71}, new uint[]{0x2f2e,0x5ddb}, - new uint[]{0x2f2f,0x5de5}, + new uint[]{0x2f2f,0x30a8,0x5de5}, new uint[]{0x2f30,0x5df1}, new uint[]{0x2f31,0x5dfe}, new uint[]{0x2f32,0x5e72}, @@ -871,7 +905,6 @@ namespace Barotrauma new uint[]{0x3078,0x30d8}, new uint[]{0x309b,0xff9e}, new uint[]{0x309c,0xff9f}, - new uint[]{0x31d6,0x4e5b}, new uint[]{0x349e,0x2f80c}, new uint[]{0x34b9,0x2f813}, new uint[]{0x34bb,0x2f9ca}, @@ -1688,12 +1721,15 @@ namespace Barotrauma new uint[]{0xa458,0xa4a7}, new uint[]{0xa4e4,0xa79e}, new uint[]{0xa64c,0xa7b6}, + new uint[]{0xa658,0x16f1c,0x1f701}, + new uint[]{0xa669,0x104eb}, new uint[]{0xa727,0xa795}, new uint[]{0xa779,0xa77a}, new uint[]{0xa79a,0x10412}, new uint[]{0xa79b,0x1043a}, new uint[]{0xa8fb,0x111dc}, new uint[]{0xa8fc,0x111db}, + new uint[]{0xa99d,0xa9a3}, new uint[]{0xa9c6,0xa9d0}, new uint[]{0xaa01,0xaa53}, new uint[]{0xaa23,0xaa56}, @@ -1713,9 +1749,14 @@ namespace Barotrauma new uint[]{0x10393,0x103d3}, new uint[]{0x1039a,0x12038}, new uint[]{0x10486,0x104a0}, + new uint[]{0x10c82,0x10cfc}, + new uint[]{0x10ca5,0x10cfa}, new uint[]{0x11582,0x115d8,0x115d9}, new uint[]{0x11583,0x115da}, new uint[]{0x11584,0x115db}, + new uint[]{0x115b2,0x115dc}, + new uint[]{0x115b3,0x115dd}, + new uint[]{0x11caa,0x11cb2}, new uint[]{0x20122,0x2f803}, new uint[]{0x2051c,0x2f812}, new uint[]{0x20525,0x2f91b}, @@ -1821,17 +1862,17 @@ namespace Barotrauma new uint[]{0x2a600,0x2fa1d}, }; - public static bool Compare(string a,string b) + public static bool Compare(string a, string b) { if (a.Equals(b, StringComparison.InvariantCulture)) return true; if (a.Length != b.Length) return false; - for (int i=0;i g.Contains((uint)a[i])); - if (glyphGroup==null || !glyphGroup.Contains((uint)b[i])) return false; + if (glyphGroup == null || !glyphGroup.Contains((uint)b[i])) return false; } return true; } } -} +} \ No newline at end of file From ad681d5a268317b8b29db812f904f0df43103f62 Mon Sep 17 00:00:00 2001 From: Nilanth Animosus Date: Wed, 18 Jul 2018 11:03:04 +0100 Subject: [PATCH 009/198] Chatbox removes from the first entry instead of the second Removes for clients "playername has joined" staying at the top of the chat history, as this feels unnecessary for both server and client. --- Barotrauma/BarotraumaShared/Source/Networking/NetworkMember.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/Source/Networking/NetworkMember.cs b/Barotrauma/BarotraumaShared/Source/Networking/NetworkMember.cs index c0ecc0749..742f6cbab 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/NetworkMember.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/NetworkMember.cs @@ -162,7 +162,7 @@ namespace Barotrauma.Networking while (chatBox.CountChildren > 20) { - chatBox.RemoveChild(chatBox.children[1]); + chatBox.RemoveChild(chatBox.children[0]); } if (!string.IsNullOrWhiteSpace(message.SenderName)) From af4d60db1da92668d49d0b1c4c2ba7d1b12bfff1 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 18 Jul 2018 13:05:58 +0300 Subject: [PATCH 010/198] Fixed GameMain.Server.Character not being set to null when the character is removed. Closes #488 --- Barotrauma/BarotraumaClient/Source/Characters/Character.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Character.cs b/Barotrauma/BarotraumaClient/Source/Characters/Character.cs index f5feec38b..597bdc902 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Character.cs @@ -206,8 +206,8 @@ namespace Barotrauma { GameMain.GameSession.CrewManager.RemoveCharacter(this); } - - if (GameMain.Client != null && GameMain.Client.Character == this) GameMain.Client.Character = null; + + if (GameMain.NetworkMember?.Character == this) GameMain.NetworkMember.Character = null; if (Lights.LightManager.ViewTarget == this) Lights.LightManager.ViewTarget = null; } From 2df7d6a4642fbf163ae81b53df76e46269cf165a Mon Sep 17 00:00:00 2001 From: Nilanth Animosus Date: Wed, 18 Jul 2018 11:06:49 +0100 Subject: [PATCH 011/198] Tweak item spawning to combine item names without quotes Retains ability to spawn items to characters, its unlikely that player names will match at the last half of an item - and the item must also not exist for this to be an issue. --- .../BarotraumaShared/Source/DebugConsole.cs | 95 +++++++++---------- 1 file changed, 47 insertions(+), 48 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs index ffb676918..2b65dccf9 100644 --- a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs @@ -249,7 +249,7 @@ namespace Barotrauma }; })); - 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\".", + commands.Add(new Command("spawnitem", "spawnitem [itemname] [cursor/inventory/cargo/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) => { SpawnItem(args, GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), Character.Controlled, out string errorMsg); @@ -2131,65 +2131,63 @@ namespace Barotrauma } } - private static void SpawnItem(string[] args, Vector2 cursorPos, Character controlledCharacter, out string errorMsg) - { + private static void SpawnItem(string[] args, Vector2 cursorPos, Character controlledCharacter, out string errorMsg) + { errorMsg = ""; if (args.Length < 1) return; Vector2? spawnPos = null; Inventory spawnInventory = null; - if (args.Length > 1) + int extraParams = 0; + switch (args.Last().ToLowerInvariant()) { - 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; + case "cursor": + extraParams = 1; + spawnPos = cursorPos; + break; + case "inventory": + extraParams = 1; + spawnInventory = controlledCharacter == null ? null : controlledCharacter.Inventory; + break; + case "cargo": + var wp = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub); + spawnPos = wp == null ? Vector2.Zero : wp.WorldPosition; + break; + //Dont do a thing, random is basically Human points anyways - its in the help description. + case "random": + extraParams = 1; + return; + default: + extraParams = 0; + break; + } + + string itemName = string.Join(" ", args.Take(args.Length - extraParams)).ToLowerInvariant(); + + ItemPrefab itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab; + if (itemPrefab == null && extraParams == 0) + { + if (GameMain.Server != null) + { + var client = GameMain.Server.ConnectedClients.Find(c => c.Name.ToLower() == args.Last().ToLower()); + if (client != null) + { + extraParams += 1; + itemName = string.Join(" ", args.Take(args.Length - extraParams)).ToLowerInvariant(); + if (client.Character != null && client.Character.Name == args.Last().ToLower()) spawnInventory = client.Character.Inventory; + itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab; + } } } - - string itemName = args[0]; - - var itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab; - if (itemPrefab == null) - { + //Check again if the item can be found again after having checked for a character + if (itemPrefab == null) + { errorMsg = "Item \"" + itemName + "\" not found!"; - return; + return; } - if (spawnPos == null && spawnInventory == null) + if ((spawnPos == null || spawnPos == Vector2.Zero) && spawnInventory == null) { var wp = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub); spawnPos = wp == null ? Vector2.Zero : wp.WorldPosition; @@ -2198,6 +2196,7 @@ namespace Barotrauma if (spawnPos != null) { Entity.Spawner.AddToSpawnQueue(itemPrefab, (Vector2)spawnPos); + } else if (spawnInventory != null) { From 568cf1a02ff415b020bb1f17c3ca14b730d3a146 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 18 Jul 2018 14:23:43 +0300 Subject: [PATCH 012/198] Fixed memory leak caused by submarine preview images, changed Submarine.SavedSubmarines to a property that prevents removing submarines from outside the class without disposing the preview image. Closes #498 --- .../Source/Networking/GameClient.cs | 10 +- .../Source/Networking/Voting.cs | 3 +- .../Source/Screens/CampaignSetupUI.cs | 2 +- .../Source/Screens/NetLobbyScreen.cs | 8 +- .../BarotraumaClient/Source/Sprite/Sprite.cs | 10 +- .../Source/Screens/NetLobbyScreen.cs | 4 +- .../BarotraumaShared/Source/Map/Submarine.cs | 92 ++++++++++++------- .../Networking/FileTransfer/FileSender.cs | 2 +- .../Source/Networking/Voting.cs | 2 +- 9 files changed, 81 insertions(+), 52 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs index 47c0a4e7a..5e097dffd 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs @@ -815,7 +815,7 @@ namespace Barotrauma.Networking string subName = inc.ReadString(); string subHash = inc.ReadString(); - var matchingSub = Submarine.SavedSubmarines.Find(s => s.Name == subName && s.MD5Hash.Hash == subHash); + var matchingSub = Submarine.SavedSubmarines.FirstOrDefault(s => s.Name == subName && s.MD5Hash.Hash == subHash); if (matchingSub != null) { submarines.Add(matchingSub); @@ -1181,8 +1181,12 @@ namespace Barotrauma.Networking case FileTransferType.Submarine: new GUIMessageBox("Download finished", "File \"" + transfer.FileName + "\" was downloaded succesfully."); var newSub = new Submarine(transfer.FilePath); - Submarine.SavedSubmarines.RemoveAll(s => s.Name == newSub.Name && s.MD5Hash.Hash == newSub.MD5Hash.Hash); - Submarine.SavedSubmarines.Add(newSub); + var existingSubs = Submarine.SavedSubmarines.Where(s => s.Name == newSub.Name && s.MD5Hash.Hash == newSub.MD5Hash.Hash).ToList(); + foreach (Submarine existingSub in existingSubs) + { + existingSub.Dispose(); + } + Submarine.AddToSavedSubs(newSub); for (int i = 0; i < 2; i++) { diff --git a/Barotrauma/BarotraumaClient/Source/Networking/Voting.cs b/Barotrauma/BarotraumaClient/Source/Networking/Voting.cs index b0a6294bb..6b41a5dac 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/Voting.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/Voting.cs @@ -2,6 +2,7 @@ using Lidgren.Network; using Microsoft.Xna.Framework; using System.Collections.Generic; +using System.Linq; namespace Barotrauma { @@ -151,7 +152,7 @@ namespace Barotrauma { int votes = inc.ReadByte(); string subName = inc.ReadString(); - Submarine sub = Submarine.SavedSubmarines.Find(sm => sm.Name == subName); + Submarine sub = Submarine.SavedSubmarines.FirstOrDefault(sm => sm.Name == subName); SetVoteText(GameMain.NetLobbyScreen.SubList, sub, votes); } } diff --git a/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs b/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs index b477aee46..578ee4bf0 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs @@ -144,7 +144,7 @@ namespace Barotrauma return true; }; } - if (Submarine.SavedSubmarines.Count > 0) subList.Select(Submarine.SavedSubmarines[0]); + if (Submarine.SavedSubmarines.Any()) subList.Select(Submarine.SavedSubmarines.First()); } public void UpdateLoadMenu() diff --git a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs index 6f5edabac..287edb042 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs @@ -782,8 +782,8 @@ namespace Barotrauma CanBeFocused = false }; - var matchingSub = Submarine.SavedSubmarines.Find(s => s.Name == sub.Name && s.MD5Hash.Hash == sub.MD5Hash.Hash); - if (matchingSub == null) matchingSub = Submarine.SavedSubmarines.Find(s => s.Name == sub.Name); + var matchingSub = Submarine.SavedSubmarines.FirstOrDefault(s => s.Name == sub.Name && s.MD5Hash.Hash == sub.MD5Hash.Hash); + if (matchingSub == null) matchingSub = Submarine.SavedSubmarines.FirstOrDefault(s => s.Name == sub.Name); if (matchingSub == null) { @@ -1467,8 +1467,8 @@ namespace Barotrauma return false; } - Submarine sub = Submarine.SavedSubmarines.Find(m => m.Name == subName && m.MD5Hash.Hash == md5Hash); - if (sub == null) sub = Submarine.SavedSubmarines.Find(m => m.Name == subName); + Submarine sub = Submarine.SavedSubmarines.FirstOrDefault(m => m.Name == subName && m.MD5Hash.Hash == md5Hash); + if (sub == null) sub = Submarine.SavedSubmarines.FirstOrDefault(m => m.Name == subName); var matchingListSub = subList.children.Find(c => c.UserData == sub); if (matchingListSub != null) diff --git a/Barotrauma/BarotraumaClient/Source/Sprite/Sprite.cs b/Barotrauma/BarotraumaClient/Source/Sprite/Sprite.cs index c0e65a76f..d09deb6b0 100644 --- a/Barotrauma/BarotraumaClient/Source/Sprite/Sprite.cs +++ b/Barotrauma/BarotraumaClient/Source/Sprite/Sprite.cs @@ -254,11 +254,14 @@ namespace Barotrauma partial void DisposeTexture() { //check if another sprite is using the same texture - foreach (Sprite s in list) + if (!string.IsNullOrEmpty(file)) //file can be empty if the sprite is created directly from a Texture2D instance { - if (s.file == file) return; + foreach (Sprite s in list) + { + if (s.file == file) return; + } } - + //if not, free the texture if (texture != null) { @@ -267,6 +270,5 @@ namespace Barotrauma } } } - } diff --git a/Barotrauma/BarotraumaServer/Source/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaServer/Source/Screens/NetLobbyScreen.cs index 959d85b2c..79fffa92f 100644 --- a/Barotrauma/BarotraumaServer/Source/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaServer/Source/Screens/NetLobbyScreen.cs @@ -119,7 +119,7 @@ namespace Barotrauma subs = Submarine.SavedSubmarines.Where(s => !s.HasTag(SubmarineTag.HideInMenus)).ToList(); - if (subs == null || subs.Count()==0) + if (subs == null || subs.Count() == 0) { throw new Exception("No submarines are available."); } @@ -187,7 +187,7 @@ namespace Barotrauma if (GameMain.Server.SubSelectionMode == SelectionMode.Random) { - var nonShuttles = Submarine.SavedSubmarines.FindAll(c => !c.HasTag(SubmarineTag.Shuttle) && !c.HasTag(SubmarineTag.HideInMenus)); + var nonShuttles = Submarine.SavedSubmarines.Where(c => !c.HasTag(SubmarineTag.Shuttle) && !c.HasTag(SubmarineTag.HideInMenus)).ToList(); SelectedSub = nonShuttles[Rand.Range(0, nonShuttles.Count)]; } if (GameMain.Server.ModeSelectionMode == SelectionMode.Random) diff --git a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs index 63277d538..e5ade2ae9 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs @@ -51,8 +51,12 @@ namespace Barotrauma public static bool LockX, LockY; - public static List SavedSubmarines = new List(); - + private static List savedSubmarines = new List(); + public static IEnumerable SavedSubmarines + { + get { return savedSubmarines; } + } + public static readonly Vector2 GridSize = new Vector2(16.0f, 16.0f); public static Submarine[] MainSubs = new Submarine[2]; @@ -214,10 +218,10 @@ namespace Barotrauma return ConvertUnits.ToSimUnits(Position); } } - + public Vector2 Velocity { - get { return subBody==null ? Vector2.Zero : subBody.Velocity; } + get { return subBody == null ? Vector2.Zero : subBody.Velocity; } set { if (subBody == null) return; @@ -244,7 +248,7 @@ namespace Barotrauma public override string ToString() { - return "Barotrauma.Submarine ("+name+")"; + return "Barotrauma.Submarine (" + name + ")"; } public override bool Removed @@ -344,7 +348,7 @@ namespace Barotrauma { if (dockedSub == this) continue; - Vector2 diff = dockedSub.Submarine == this ? dockedSub.WorldPosition : dockedSub.WorldPosition - WorldPosition; + Vector2 diff = dockedSub.Submarine == this ? dockedSub.WorldPosition : dockedSub.WorldPosition - WorldPosition; Rectangle dockedSubBorders = dockedSub.Borders; dockedSubBorders.Y -= dockedSubBorders.Height; @@ -383,15 +387,15 @@ namespace Barotrauma public Vector2 FindSpawnPos(Vector2 spawnPos) { Rectangle dockedBorders = GetDockedBorders(); - + int iterations = 0; bool wallTooClose = false; do { Rectangle worldBorders = new Rectangle( dockedBorders.X + (int)spawnPos.X, - dockedBorders.Y + (int)spawnPos.Y, - dockedBorders.Width, + dockedBorders.Y + (int)spawnPos.Y, + dockedBorders.Width, dockedBorders.Height); wallTooClose = false; @@ -507,8 +511,8 @@ namespace Barotrauma public Rectangle CalculateDimensions(bool onlyHulls = true) { - List entities = onlyHulls ? - Hull.hullList.FindAll(h => h.Submarine == this).Cast().ToList() : + List entities = onlyHulls ? + Hull.hullList.FindAll(h => h.Submarine == this).Cast().ToList() : MapEntity.mapEntityList.FindAll(me => me.Submarine == this); if (entities.Count == 0) return Rectangle.Empty; @@ -557,7 +561,7 @@ namespace Barotrauma } } - public static bool RectsOverlap(Rectangle rect1, Rectangle rect2, bool inclusive=true) + public static bool RectsOverlap(Rectangle rect1, Rectangle rect2, bool inclusive = true) { if (inclusive) { @@ -584,25 +588,25 @@ namespace Barotrauma { if (fixture == null || (ignoreSensors && fixture.IsSensor) || - fixture.CollisionCategories == Category.None || + fixture.CollisionCategories == Category.None || fixture.CollisionCategories == Physics.CollisionItem) return -1; - - if (collisionCategory != null && + + if (collisionCategory != null && !fixture.CollisionCategories.HasFlag((Category)collisionCategory) && !((Category)collisionCategory).HasFlag(fixture.CollisionCategories)) return -1; - + if (ignoredBodies != null && ignoredBodies.Contains(fixture.Body)) return -1; - - Structure structure = fixture.Body.UserData as Structure; + + Structure structure = fixture.Body.UserData as Structure; if (structure != null) { if (structure.IsPlatform && collisionCategory != null && !((Category)collisionCategory).HasFlag(Physics.CollisionPlatform)) return -1; - } + } if (fraction < closestFraction) { closestFraction = fraction; - if (fixture.Body!=null) closestBody = fixture.Body; + if (fixture.Body != null) closestBody = fixture.Body; } return fraction; } @@ -669,7 +673,7 @@ namespace Barotrauma get { return flippedX; } } - public void FlipX(List parents=null) + public void FlipX(List parents = null) { if (parents == null) parents = new List(); parents.Add(this); @@ -729,7 +733,7 @@ namespace Barotrauma { if (bodyItems.Contains(item)) { - item.Submarine = this; + item.Submarine = this; if (Position == Vector2.Zero) item.Move(-HiddenSubPosition); } else if (item.Submarine != this) @@ -751,7 +755,7 @@ namespace Barotrauma if (Level.Loaded == null || subBody == null) return; if (WorldPosition.Y < Level.MaxEntityDepth && - subBody.Body.Enabled && + subBody.Body.Enabled && (GameMain.NetworkMember?.RespawnManager == null || this != GameMain.NetworkMember.RespawnManager.RespawnShuttle)) { subBody.Body.ResetDynamics(); @@ -778,26 +782,26 @@ namespace Barotrauma } subBody.Body.LinearVelocity = new Vector2( - LockX ? 0.0f : subBody.Body.LinearVelocity.X, + LockX ? 0.0f : subBody.Body.LinearVelocity.X, LockY ? 0.0f : subBody.Body.LinearVelocity.Y); - - + + subBody.Update(deltaTime); - for (int i = 0; i < 2; i++ ) + for (int i = 0; i < 2; i++) { if (MainSubs[i] == null) continue; if (this != MainSubs[i] && MainSubs[i].DockedTo.Contains(this)) return; } //send updates more frequently if moving fast - networkUpdateTimer -= MathHelper.Clamp(Velocity.Length()*10.0f, 0.1f, 5.0f) * deltaTime; + networkUpdateTimer -= MathHelper.Clamp(Velocity.Length() * 10.0f, 0.1f, 5.0f) * deltaTime; if (networkUpdateTimer < 0.0f) { networkUpdateTimer = 1.0f; } - + } public void ApplyForce(Vector2 force) @@ -867,7 +871,7 @@ namespace Barotrauma subBorders.Inflate(500.0f, 500.0f); - if (subBorders.Contains(position)) return sub; + if (subBorders.Contains(position)) return sub; } return null; @@ -875,9 +879,18 @@ namespace Barotrauma //saving/loading ---------------------------------------------------- + public static void AddToSavedSubs(Submarine sub) + { + savedSubmarines.Add(sub); + } + public static void RefreshSavedSubs() { - SavedSubmarines.Clear(); + for (int i = savedSubmarines.Count - 1; i>= 0; i--) + { + savedSubmarines[i].Dispose(); + } + System.Diagnostics.Debug.Assert(savedSubmarines.Count == 0); if (!Directory.Exists(SavePath)) { @@ -921,7 +934,7 @@ namespace Barotrauma foreach (string path in filePaths) { - SavedSubmarines.Add(new Submarine(path)); + savedSubmarines.Add(new Submarine(path)); } } @@ -971,7 +984,7 @@ namespace Barotrauma catch (Exception e) { - DebugConsole.ThrowError("Loading submarine \"" + file + "\" failed! ("+e.Message+")"); + DebugConsole.ThrowError("Loading submarine \"" + file + "\" failed! (" + e.Message + ")"); return null; } } @@ -1147,12 +1160,12 @@ namespace Barotrauma Submarine sub = new Submarine(element.GetAttributeString("name", ""), "", false); sub.Load(unloadPrevious, element); - return sub; + return sub; } public static Submarine Load(string fileName, bool unloadPrevious) { - return Load(fileName, SavePath, unloadPrevious); + return Load(fileName, SavePath, unloadPrevious); } public static Submarine Load(string fileName, string folder, bool unloadPrevious) @@ -1285,6 +1298,15 @@ namespace Barotrauma DockedTo.Clear(); } + public void Dispose() + { + savedSubmarines.Remove(this); +#if CLIENT + PreviewImage.Remove(); + PreviewImage = null; +#endif + } + public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null) { msg.Write(ID); diff --git a/Barotrauma/BarotraumaShared/Source/Networking/FileTransfer/FileSender.cs b/Barotrauma/BarotraumaShared/Source/Networking/FileTransfer/FileSender.cs index f119d3f26..3ce6c9153 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/FileTransfer/FileSender.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/FileTransfer/FileSender.cs @@ -271,7 +271,7 @@ namespace Barotrauma.Networking case (byte)FileTransferType.Submarine: string fileName = inc.ReadString(); string fileHash = inc.ReadString(); - var requestedSubmarine = Submarine.SavedSubmarines.Find(s => s.Name == fileName && s.MD5Hash.Hash == fileHash); + var requestedSubmarine = Submarine.SavedSubmarines.FirstOrDefault(s => s.Name == fileName && s.MD5Hash.Hash == fileHash); if (requestedSubmarine != null) { diff --git a/Barotrauma/BarotraumaShared/Source/Networking/Voting.cs b/Barotrauma/BarotraumaShared/Source/Networking/Voting.cs index c78a1bede..300198353 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/Voting.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/Voting.cs @@ -93,7 +93,7 @@ namespace Barotrauma { case VoteType.Sub: string subName = inc.ReadString(); - Submarine sub = Submarine.SavedSubmarines.Find(s => s.Name == subName); + Submarine sub = Submarine.SavedSubmarines.FirstOrDefault(s => s.Name == subName); sender.SetVote(voteType, sub); #if CLIENT UpdateVoteTexts(GameMain.Server.ConnectedClients, voteType); From f25fe6b504d0076cf31ba658a71be65e56829f62 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 18 Jul 2018 14:36:00 +0300 Subject: [PATCH 013/198] Added some error checking & debug logging to Door.PushCharactersAway to diagnose why Math.Sign is throwing ArithmeticExceptions (see #497). --- .../Source/Items/Components/Door.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs index 20e3900d5..3cf5947a8 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs @@ -349,6 +349,12 @@ namespace Barotrauma.Items.Components private void PushCharactersAway() { + if (!MathUtils.IsValid(item.SimPosition)) + { + DebugConsole.ThrowError("Failed to push a character out of a doorway - position of the door is not valid (" + item.SimPosition + ")"); + return; + } + //push characters out of the doorway when the door is closing/opening Vector2 simPos = ConvertUnits.ToSimUnits(new Vector2(item.Rect.X, item.Rect.Y)); @@ -360,6 +366,12 @@ namespace Barotrauma.Items.Components foreach (Character c in Character.CharacterList) { + if (!c.Enabled) continue; + if (!MathUtils.IsValid(c.SimPosition)) + { + DebugConsole.ThrowError("Failed to push a character out of a doorway - position of the character \"" + c.Name + "\" is not valid (" + c.SimPosition + ")"); + continue; + } int dir = isHorizontal ? Math.Sign(c.SimPosition.Y - item.SimPosition.Y) : Math.Sign(c.SimPosition.X - item.SimPosition.X); List bodies = c.AnimController.Limbs.Select(l => l.body).ToList(); @@ -372,13 +384,11 @@ namespace Barotrauma.Items.Components if (isHorizontal) { if (body.SimPosition.X < simPos.X || body.SimPosition.X > simPos.X + simSize.X) continue; - diff = body.SimPosition.Y - item.SimPosition.Y; } else { if (body.SimPosition.Y > simPos.Y || body.SimPosition.Y < simPos.Y - simSize.Y) continue; - diff = body.SimPosition.X - item.SimPosition.X; } From 30a453191f79c7ad3f91c9d8db1416ac17ddb90b Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 18 Jul 2018 15:07:21 +0300 Subject: [PATCH 014/198] Added "VulnerableToEMP" property to Powered. Can be edited in sub editor. Reactors and relays are not affected by EMP by default. Closes #495 --- .../Content/Items/Electricity/signalitems.xml | 2 +- .../BarotraumaShared/Content/Items/Reactor/reactor.xml | 2 +- .../Source/Items/Components/Power/Powered.cs | 10 ++++++++-- Barotrauma/BarotraumaShared/Source/Map/Explosion.cs | 10 ++++------ 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Content/Items/Electricity/signalitems.xml b/Barotrauma/BarotraumaShared/Content/Items/Electricity/signalitems.xml index 0c01875bd..58f1a40c6 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Electricity/signalitems.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Electricity/signalitems.xml @@ -261,7 +261,7 @@ - + diff --git a/Barotrauma/BarotraumaShared/Content/Items/Reactor/reactor.xml b/Barotrauma/BarotraumaShared/Content/Items/Reactor/reactor.xml index 840b56a18..eb7db5017 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Reactor/reactor.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Reactor/reactor.xml @@ -21,7 +21,7 @@ - + diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/Powered.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/Powered.cs index d56c18ff2..02924fb61 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/Powered.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/Powered.cs @@ -33,8 +33,7 @@ namespace Barotrauma.Items.Components get { return powerConsumption; } set { powerConsumption = value; } } - - + [Serialize(false, true)] public override bool IsActive { @@ -60,6 +59,13 @@ namespace Barotrauma.Items.Components set { voltage = Math.Max(0.0f, value); } } + [Editable(ToolTip = "Can the item be damaged by electomagnetic pulses."), Serialize(true, true)] + public bool VulnerableToEMP + { + get; + set; + } + public Powered(Item item, XElement element) : base(item, element) { diff --git a/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs b/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs index a11ce2b95..121fe5417 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs @@ -78,15 +78,13 @@ namespace Barotrauma { float distSqr = Vector2.DistanceSquared(item.WorldPosition, worldPosition); if (distSqr > displayRangeSqr) continue; - - //ignore reactors (don't want to blow them up) - if (item.GetComponent() != null) continue; - + float distFactor = 1.0f - (float)Math.Sqrt(distSqr) / displayRange; //damage repairable power-consuming items - var powerTransfer = item.GetComponent(); - if (powerTransfer != null && item.FixRequirements.Count > 0) + var powered = item.GetComponent(); + if (powered == null || !powered.VulnerableToEMP) continue; + if (item.FixRequirements.Count > 0) { item.Condition -= 100 * empStrength * distFactor; } From c901b75ff166f05dcc1abd637fc05c9ae1633a06 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 19 Jul 2018 10:47:39 +0300 Subject: [PATCH 015/198] Fixed round ending tickbox not being visible client-side if the client is not controlling character (despite servers now allowing votes from players who've spawned at least once during the round). Closes #500 --- .../Source/Characters/CharacterNetworking.cs | 2 ++ .../Source/Networking/GameClient.cs | 26 ++++++++++++++++--- .../Source/Networking/GameServer.cs | 9 +++++++ .../Source/Networking/NetworkMember.cs | 14 ---------- 4 files changed, 34 insertions(+), 17 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs index 7fe1028ac..9db7409d8 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs @@ -188,6 +188,7 @@ namespace Barotrauma controlled = this; IsRemotePlayer = false; + GameMain.Client.HasSpawned = true; GameMain.Client.Character = this; GameMain.LightManager.LosEnabled = true; } @@ -286,6 +287,7 @@ namespace Barotrauma if (GameMain.Client.ID == ownerId) { + GameMain.Client.HasSpawned = true; GameMain.Client.Character = character; Controlled = character; diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs index 5e097dffd..e58636d35 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs @@ -44,6 +44,9 @@ namespace Barotrauma.Networking private FileReceiver fileReceiver; + //has the client been given a character to control this round + public bool HasSpawned; + public byte ID { get { return myID; } @@ -480,7 +483,7 @@ namespace Barotrauma.Networking if (gameStarted && Screen.Selected == GameMain.GameScreen) { - endVoteTickBox.Visible = Voting.AllowEndVoting && myCharacter != null; + endVoteTickBox.Visible = Voting.AllowEndVoting && HasSpawned; if (respawnManager != null) { @@ -666,6 +669,7 @@ namespace Barotrauma.Networking private IEnumerable StartGame(NetIncomingMessage inc) { if (Character != null) Character.Remove(); + HasSpawned = false; GameMain.LightManager.LightingEnabled = true; @@ -1281,6 +1285,23 @@ namespace Barotrauma.Networking { base.Draw(spriteBatch); + if (Screen.Selected == GameMain.GameScreen && !GUI.DisableHUD) + { + if (EndVoteCount > 0) + { + if (!HasSpawned) + { + GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - 180.0f, 40), + "Votes to end the round (y/n): " + EndVoteCount + "/" + (EndVoteMax - EndVoteCount), Color.White, null, 0, GUI.SmallFont); + } + else + { + GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - 140.0f, 40), + "Votes (y/n): " + EndVoteCount + "/" + (EndVoteMax - EndVoteCount), Color.White, null, 0, GUI.SmallFont); + } + } + } + if (fileReceiver != null && fileReceiver.ActiveTransfers.Count > 0) { Vector2 pos = new Vector2(GameMain.NetLobbyScreen.InfoFrame.Rect.X, GameMain.GraphicsHeight - 35); @@ -1564,14 +1585,13 @@ namespace Barotrauma.Networking { if (!gameStarted) return false; - if (!Voting.AllowEndVoting || myCharacter==null) + if (!Voting.AllowEndVoting || !HasSpawned) { tickBox.Visible = false; return false; } Vote(VoteType.EndRound, tickBox.Selected); - return false; } } diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameServer.cs index f146214e8..87919d6b8 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameServer.cs @@ -63,6 +63,15 @@ namespace Barotrauma.Networking log.LogFrame.Draw(spriteBatch); } + if (Screen.Selected == GameMain.GameScreen && !GUI.DisableHUD) + { + if (EndVoteCount > 0) + { + GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - 180.0f, 40), + "Votes to end the round (y/n): " + EndVoteCount + "/" + (EndVoteMax - EndVoteCount), Color.White, null, 0, GUI.SmallFont); + } + } + if (!ShowNetStats) return; GUI.Font.DrawString(spriteBatch, "Unique Events: " + entityEventManager.UniqueEvents.Count, new Vector2(10, 50), Color.White); diff --git a/Barotrauma/BarotraumaClient/Source/Networking/NetworkMember.cs b/Barotrauma/BarotraumaClient/Source/Networking/NetworkMember.cs index cfc4b7f22..1bdd6110b 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/NetworkMember.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/NetworkMember.cs @@ -119,20 +119,6 @@ namespace Barotrauma.Networking inGameHUD.Draw(spriteBatch); - if (EndVoteCount > 0) - { - if (GameMain.NetworkMember.myCharacter == null) - { - GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - 180.0f, 40), - "Votes to end the round (y/n): " + EndVoteCount + "/" + (EndVoteMax - EndVoteCount), Color.White, null, 0, GUI.SmallFont); - } - else - { - GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - 140.0f, 40), - "Votes (y/n): " + EndVoteCount + "/" + (EndVoteMax - EndVoteCount), Color.White, null, 0, GUI.SmallFont); - } - } - if (respawnManager != null) { string respawnInfo = ""; From 8cd79190096a5299cbae93ee5067ba54f4a5426a Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 19 Jul 2018 17:42:03 +0300 Subject: [PATCH 016/198] Removed unused BanClient method --- .../Source/Networking/GameServer.cs | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs index 6dbd84ce9..60b587a89 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs @@ -1583,24 +1583,7 @@ namespace Barotrauma.Networking BanClient(client, reason, range, duration); } - - public void BanClient(NetConnection conn, string reason, bool range = false, TimeSpan? duration = null) - { - Client client = connectedClients.Find(c => c.Connection == conn); - if (client == null) - { - conn.Disconnect("You have been banned from the server"); - if (!banList.IsBanned(conn.RemoteEndPoint.Address.ToString())) - { - banList.BanPlayer("Unnamed", conn.RemoteEndPoint.Address.ToString(), reason, duration); - } - } - else - { - BanClient(client, reason, range); - } - } - + public void BanClient(Client client, string reason, bool range = false, TimeSpan? duration = null) { if (client == null) return; From f1c4bd3c676fa56d521565749387cb1659f74c5f Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 19 Jul 2018 21:13:18 +0300 Subject: [PATCH 017/198] - Some of the non-game-crashing error messages are sent to GameAnalytics. - Changed crash severity from Error to Critical. - Exception handling when loading submarine preview images. - Checking if position is valid in Ragdoll.SetPosition. --- .../Source/Characters/Animation/Ragdoll.cs | 3 +++ .../Source/Networking/GameClient.cs | 25 +++++++++++-------- .../ClientEntityEventManager.cs | 3 +++ Barotrauma/BarotraumaClient/Source/Program.cs | 2 +- .../Source/Screens/NetLobbyScreen.cs | 16 ++++++++++-- .../Source/Sounds/OggStream.cs | 4 +++ .../Source/Characters/AI/AITarget.cs | 6 +++++ .../Source/Characters/Animation/Ragdoll.cs | 15 +++++++++++ .../BarotraumaShared/Source/DebugConsole.cs | 11 +++++++- .../Source/GameAnalyticsManager.cs | 15 +++++++++++ .../Source/Items/Components/Door.cs | 6 +++++ .../BarotraumaShared/Source/Map/Submarine.cs | 14 +++++++++-- .../NetEntityEvent/NetEntityEventManager.cs | 6 +++++ 13 files changed, 109 insertions(+), 17 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs index 309920928..6b9d32c9e 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs @@ -81,6 +81,9 @@ namespace Barotrauma if (Limbs == null) { DebugConsole.ThrowError("Failed to draw a ragdoll, limbs have been removed. Character: \"" + character.Name + "\", removed: " + character.Removed + "\n" + Environment.StackTrace); + GameAnalyticsManager.AddErrorEventOnce("Ragdoll.Draw:LimbsRemoved", + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Failed to draw a ragdoll, limbs have been removed. Character: \"" + character.Name + "\", removed: " + character.Removed + "\n" + Environment.StackTrace); return; } diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs index e58636d35..adbc146eb 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs @@ -1020,29 +1020,32 @@ namespace Barotrauma.Networking ChatMessage.ClientRead(inc); break; default: - DebugConsole.ThrowError("Error while reading update from server (unknown object header \""+objHeader+"\"!)"); - if (prevObjHeader != null) + List errorLines = new List { - DebugConsole.ThrowError("Previous object type: " + prevObjHeader.ToString()); - } - else - { - DebugConsole.ThrowError("Error occurred on the very first object!"); - } - DebugConsole.ThrowError("Previous object was " + (inc.Position - prevBitPos) + " bits long (" + (inc.PositionInBytes - prevBytePos) + " bytes)"); + "Error while reading update from server (unknown object header \"" + objHeader + "\"!)", + prevObjHeader != null ? "Previous object type: " + prevObjHeader.ToString() : "Error occurred on the very first object!", + "Previous object was " + (inc.Position - prevBitPos) + " bits long (" + (inc.PositionInBytes - prevBytePos) + " bytes)" + }; if (prevObjHeader == ServerNetObject.ENTITY_EVENT || prevObjHeader == ServerNetObject.ENTITY_EVENT_INITIAL) { foreach (IServerSerializable ent in entities) { if (ent == null) { - DebugConsole.ThrowError(" - NULL"); + errorLines.Add(" - NULL"); continue; } Entity e = ent as Entity; - DebugConsole.ThrowError(" - "+e.ToString()); + errorLines.Add(" - " + e.ToString()); } } + + foreach (string line in errorLines) + { + DebugConsole.ThrowError(line); + } + GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadInGameUpdate", GameAnalyticsSDK.Net.EGAErrorSeverity.Critical, string.Join("\n", errorLines)); + DebugConsole.ThrowError("Writing object data to \"crashreport_object.bin\", please send this file to us at http://github.com/Regalis11/Barotrauma/issues"); FileStream fl = File.Open("crashreport_object.bin", FileMode.Create); diff --git a/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs b/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs index 2ea912a2f..4beda97e2 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs @@ -191,6 +191,9 @@ namespace Barotrauma.Networking if (GameSettings.VerboseLogging) { DebugConsole.ThrowError("Failed to read event for entity \"" + entity.ToString() + "\"!", e); + GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:ReadFailed" + entity.ToString(), + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Failed to read event for entity \"" + entity.ToString() + "\"!\n" + e.StackTrace); } msg.Position = msgPosition + msgLength * 8; } diff --git a/Barotrauma/BarotraumaClient/Source/Program.cs b/Barotrauma/BarotraumaClient/Source/Program.cs index 4cb2af6c2..2e3f81929 100644 --- a/Barotrauma/BarotraumaClient/Source/Program.cs +++ b/Barotrauma/BarotraumaClient/Source/Program.cs @@ -203,7 +203,7 @@ namespace Barotrauma if (GameSettings.SendUserStatistics) { CrashMessageBox( "A crash report (\"crashreport.log\") was saved in the root folder of the game and sent to the developers."); - GameAnalytics.AddErrorEvent(EGAErrorSeverity.Error, crashReport); + GameAnalytics.AddErrorEvent(EGAErrorSeverity.Critical, crashReport); GameAnalytics.OnStop(); } else diff --git a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs index 287edb042..0f9b9ad1b 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs @@ -737,8 +737,20 @@ namespace Barotrauma //hash will be null if opening the sub file failed -> don't select the sub if (string.IsNullOrWhiteSpace(hash)) { - (component as GUITextBlock).TextColor = Color.DarkRed * 0.8f; - component.CanBeFocused = false; + if (component is GUITextBlock textBlock) + { + textBlock.TextColor = Color.DarkRed * 0.8f; + textBlock.CanBeFocused = false; + } + else + { + DebugConsole.ThrowError("Failed to select submarine. Selected GUIComponent was of the type \"" + (component == null ? "null" : component.GetType().ToString()) + "\"."); + GameAnalyticsManager.AddErrorEventOnce( + "NetLobbyScreen.SelectSub:InvalidComponent", + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Failed to select submarine. Selected GUIComponent was of the type \"" + (component == null ? "null" : component.GetType().ToString()) + "\"."); + } + StartButton.Enabled = false; diff --git a/Barotrauma/BarotraumaClient/Source/Sounds/OggStream.cs b/Barotrauma/BarotraumaClient/Source/Sounds/OggStream.cs index 8f88dfbb8..6f761edd3 100644 --- a/Barotrauma/BarotraumaClient/Source/Sounds/OggStream.cs +++ b/Barotrauma/BarotraumaClient/Source/Sounds/OggStream.cs @@ -55,6 +55,10 @@ namespace Barotrauma.Sounds #else DebugConsole.NewMessage("OpenAL error: " + AL.GetErrorString(error) + "\n" + Environment.StackTrace, Microsoft.Xna.Framework.Color.Red); #endif + GameAnalyticsManager.AddErrorEventOnce( + "OggStream.Check:"+ AL.GetErrorString(error), + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "OpenAL error: " + AL.GetErrorString(error) + "\n" + Environment.StackTrace); } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs index 87f40912c..d857cf3b6 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs @@ -38,6 +38,9 @@ namespace Barotrauma #if DEBUG DebugConsole.ThrowError("Attempted to access a removed AITarget\n" + Environment.StackTrace); #endif + GameAnalyticsManager.AddErrorEventOnce("AITarget.WorldPosition:EntityRemoved", + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Attempted to access a removed AITarget\n" + Environment.StackTrace); return Vector2.Zero; } @@ -54,6 +57,9 @@ namespace Barotrauma #if DEBUG DebugConsole.ThrowError("Attempted to access a removed AITarget\n" + Environment.StackTrace); #endif + GameAnalyticsManager.AddErrorEventOnce("AITarget.WorldPosition:EntityRemoved", + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Attempted to access a removed AITarget\n" + Environment.StackTrace); return Vector2.Zero; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs index c9a371e76..3a5f28887 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs @@ -25,6 +25,11 @@ namespace Barotrauma if (limbs == null) { DebugConsole.ThrowError("Attempted to access a potentially removed ragdoll. Character: " + character.Name + ", id: " + character.ID + ", removed: " + character.Removed + ", ragdoll removed: " + !list.Contains(this)); + GameAnalyticsManager.AddErrorEventOnce( + "Ragdoll.Limbs:AccessRemoved", + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Attempted to access a potentially removed ragdoll. Character: " + character.Name + ", id: " + character.ID + ", removed: " + character.Removed + ", ragdoll removed: " + !list.Contains(this)); + return new Limb[0]; } return limbs; @@ -1165,6 +1170,16 @@ namespace Barotrauma public void SetPosition(Vector2 simPosition, bool lerp = false) { + if (!MathUtils.IsValid(simPosition)) + { + DebugConsole.ThrowError("Attempted to move a ragdoll (" + character.Name + ") to an invalid position (" + simPosition + "). " + Environment.StackTrace); + GameAnalyticsManager.AddErrorEventOnce( + "Ragdoll.SetPosition:InvalidPosition", + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Attempted to move a ragdoll (" + character.Name + ") to an invalid position (" + simPosition + "). " + Environment.StackTrace); + return; + } + Vector2 limbMoveAmount = simPosition - MainLimb.SimPosition; Collider.SetTransform(simPosition, Collider.Rotation); diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs index 2b65dccf9..078ef2755 100644 --- a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs @@ -1898,7 +1898,16 @@ namespace Barotrauma if (string.IsNullOrWhiteSpace(command)) return; string[] splitCommand = SplitCommand(command); - + if (splitCommand.Length == 0) + { + DebugConsole.ThrowError("Failed to execute command \"" + command + "\"!"); + GameAnalyticsManager.AddErrorEventOnce( + "DebugConsole.ExecuteCommand:LengthZero", + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Failed to execute command \"" + command + "\"!"); + return; + } + if (!splitCommand[0].ToLowerInvariant().Equals("admin")) { NewMessage(command, Color.White, true); diff --git a/Barotrauma/BarotraumaShared/Source/GameAnalyticsManager.cs b/Barotrauma/BarotraumaShared/Source/GameAnalyticsManager.cs index 81374dab6..e96489c16 100644 --- a/Barotrauma/BarotraumaShared/Source/GameAnalyticsManager.cs +++ b/Barotrauma/BarotraumaShared/Source/GameAnalyticsManager.cs @@ -1,5 +1,6 @@ using GameAnalyticsSDK.Net; using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; @@ -9,6 +10,8 @@ namespace Barotrauma { public static class GameAnalyticsManager { + private static HashSet sentEventIdentifiers = new HashSet(); + public static void Init() { #if DEBUG @@ -47,5 +50,17 @@ namespace Barotrauma contentPackageName.Replace(":", "").Substring(0, Math.Min(32, contentPackageName.Length))); } } + + /// + /// Adds an error event to GameAnalytics if an event with the same identifier has not been added yet. + /// + public static void AddErrorEventOnce(string identifier, EGAErrorSeverity errorSeverity, string message) + { + if (!GameSettings.SendUserStatistics) return; + if (sentEventIdentifiers.Contains(identifier)) return; + + GameAnalytics.AddErrorEvent(errorSeverity, message); + sentEventIdentifiers.Add(identifier); + } } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs index 3cf5947a8..b3c8a5b03 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs @@ -352,6 +352,8 @@ namespace Barotrauma.Items.Components if (!MathUtils.IsValid(item.SimPosition)) { DebugConsole.ThrowError("Failed to push a character out of a doorway - position of the door is not valid (" + item.SimPosition + ")"); + GameAnalyticsManager.AddErrorEventOnce("PushCharactersAway:DoorPosInvalid", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Failed to push a character out of a doorway - position of the door is not valid (" + item.SimPosition + ")."); return; } @@ -370,6 +372,10 @@ namespace Barotrauma.Items.Components if (!MathUtils.IsValid(c.SimPosition)) { DebugConsole.ThrowError("Failed to push a character out of a doorway - position of the character \"" + c.Name + "\" is not valid (" + c.SimPosition + ")"); + GameAnalyticsManager.AddErrorEventOnce("PushCharactersAway:CharacterPosInvalid", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Failed to push a character out of a doorway - position of the character \"" + c.Name + "\" is not valid (" + c.SimPosition + ")." + + " Removed: " + c.Removed + + " Remoteplayer: " + c.IsRemotePlayer); continue; } int dir = isHorizontal ? Math.Sign(c.SimPosition.Y - item.SimPosition.Y) : Math.Sign(c.SimPosition.X - item.SimPosition.X); diff --git a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs index e5ade2ae9..d291304b5 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs @@ -300,9 +300,19 @@ namespace Barotrauma string previewImageData = doc.Root.GetAttributeString("previewimage", ""); if (!string.IsNullOrEmpty(previewImageData)) { - using (MemoryStream mem = new MemoryStream(Convert.FromBase64String(previewImageData))) + try { - PreviewImage = new Sprite(TextureLoader.FromStream(mem), null, null); + using (MemoryStream mem = new MemoryStream(Convert.FromBase64String(previewImageData))) + { + PreviewImage = new Sprite(TextureLoader.FromStream(mem), null, null); + } + } + catch (Exception e) + { + DebugConsole.ThrowError("Loading the preview image of the submarine \"" + Name + "\" failed. The file may be corrupted.", e); + GameAnalyticsManager.AddErrorEventOnce("Submarine..ctor:PreviewImageLoadingFailed", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Loading the preview image of the submarine \"" + Name + "\" failed. The file may be corrupted."); + PreviewImage = null; } } #endif diff --git a/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEventManager.cs b/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEventManager.cs index fad368b6e..eafb6fb1b 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEventManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEventManager.cs @@ -30,6 +30,9 @@ namespace Barotrauma.Networking catch (Exception exception) { DebugConsole.ThrowError("Failed to write an event for the entity \"" + e.Entity + "\"", exception); + GameAnalyticsManager.AddErrorEventOnce("NetEntityEventManager.Write:WriteFailed" + e.Entity.ToString(), + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Failed to write an event for the entity \"" + e.Entity + "\"\n" + exception.StackTrace); //write an empty event to avoid messing up IDs //(otherwise the clients might read the next event in the message and think its ID @@ -49,6 +52,9 @@ namespace Barotrauma.Networking if (tempEventBuffer.LengthBytes > 255) { DebugConsole.ThrowError("Too much data in network event for entity \"" + e.Entity.ToString() + "\" (" + tempEventBuffer.LengthBytes + " bytes"); + GameAnalyticsManager.AddErrorEventOnce("NetEntityEventManager.Write:TooLong" + e.Entity.ToString(), + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Too much data in network event for entity \"" + e.Entity.ToString() + "\" (" + tempEventBuffer.LengthBytes + " bytes"); } //the ID has been taken by another entity (the original entity has been removed) -> write an empty event From e4e610b35effe6942d3264f79b370ee22366d29e Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 19 Jul 2018 22:13:09 +0300 Subject: [PATCH 018/198] Added exception handling to GameSettings saving --- Barotrauma/BarotraumaShared/Source/GameSettings.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/Source/GameSettings.cs b/Barotrauma/BarotraumaShared/Source/GameSettings.cs index fbc3b2fe8..9acd0b434 100644 --- a/Barotrauma/BarotraumaShared/Source/GameSettings.cs +++ b/Barotrauma/BarotraumaShared/Source/GameSettings.cs @@ -402,7 +402,16 @@ namespace Barotrauma new XAttribute("gender", characterGender)); doc.Root.Add(playerElement); - doc.Save(filePath); + try + { + doc.Save(filePath); + } + catch (Exception e) + { + DebugConsole.ThrowError("Saving game settings failed.", e); + GameAnalyticsManager.AddErrorEventOnce("GameSettings.Save:SaveFailed", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Saving game settings failed.\n" + e.Message + "\n" + e.StackTrace); + } } private IEnumerable ApplyUnsavedChanges() From f822e772418d4ee77f91066f2a69309aa30fb5b8 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 19 Jul 2018 22:21:08 +0300 Subject: [PATCH 019/198] Checking if the position is valid in Ragdoll.FindHull --- .../Source/Characters/Animation/Ragdoll.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs index 3a5f28887..3a9fbcd73 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs @@ -744,7 +744,15 @@ namespace Barotrauma public void FindHull(Vector2? worldPosition = null, bool setSubmarine = true) { - Vector2 findPos = worldPosition==null ? this.WorldPosition : (Vector2)worldPosition; + Vector2 findPos = worldPosition == null ? this.WorldPosition : (Vector2)worldPosition; + if (!MathUtils.IsValid(findPos)) + { + GameAnalyticsManager.AddErrorEventOnce( + "Ragdoll.FindHull:InvalidPosition", + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Attempted to find a hull at an invalid position (" + findPos + ")\n" + Environment.StackTrace); + return; + } Hull newHull = Hull.FindHull(findPos, currentHull); From 9e69216c527f921107aab1c5d783e361a156d2a2 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 20 Jul 2018 01:09:55 +0300 Subject: [PATCH 020/198] Server validates the cursor positions clients send for a console command before using them! --- Barotrauma/BarotraumaShared/Source/DebugConsole.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs index 078ef2755..1ad87c716 100644 --- a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs @@ -1983,6 +1983,13 @@ namespace Barotrauma { GameMain.Server.SendConsoleMessage("Command \"" + splitCommand[0] + "\" not found.", client); return; + } + + if (!MathUtils.IsValid(cursorWorldPos)) + { + GameMain.Server.SendConsoleMessage("Could not execute command \"" + command + "\" - invalid cursor position.", client); + NewMessage(client.Name + " attempted to execute the console command \"" + command + "\" with invalid cursor position.", Color.White); + return; } try From 9b4247a1477028044b39c9304feabfac09ee7738 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 20 Jul 2018 01:10:02 +0300 Subject: [PATCH 021/198] v0.8.1.6 --- Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs | 4 ++-- Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs index 40fceb2b3..2f1006a44 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.5")] -[assembly: AssemblyFileVersion("0.8.1.5")] +[assembly: AssemblyVersion("0.8.1.6")] +[assembly: AssemblyFileVersion("0.8.1.6")] diff --git a/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs index 66089601e..03badd491 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.5")] -[assembly: AssemblyFileVersion("0.8.1.5")] +[assembly: AssemblyVersion("0.8.1.6")] +[assembly: AssemblyFileVersion("0.8.1.6")] From e1539b76cdba8913010305945d79c8485d98be20 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 20 Jul 2018 09:53:14 +0300 Subject: [PATCH 022/198] v0.8.1.6 changelog (forgot from the previous commit) --- Barotrauma/BarotraumaShared/changelog.txt | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 3c33067bd..6dc3f75df 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,23 @@ +--------------------------------------------------------------------------------------------------------- +v0.8.1.6 +--------------------------------------------------------------------------------------------------------- + +- Fixed a memory leak in submarine preview images which caused crashes in the server lobby screen and +submarine editor. +- Fixed clients not seeing the "vote to end the round" tickbox if they don't have a character assigned to +them (despite the server allowing voting if the client has had a character earlier during the round). +- Fixed clients being able to crash servers if they had the permission to use console commands that +use the position of the client's cursor. +- Fixed crashing if a wire is used by a statuseffect (for example if a detonator tries to trigger a wire +contained inside it). +- Fixed GameAnalytics being stopped if the dedicated server is restarted with the "restart" console command.- +- Fixed wiring items outside the submarine. +- Fixed chatbox discarding the second chat message instead of the first one when the maximum number of +chat messages is reached. +- Some error checking and debug logging to diagnose and prevent a crash caused by doors pushing characters away. +- The spawnitem command doesn't require multi-word item names to be surrounded with quotes anymore. +- Added the option to make powered items immune to electromagnetic pulses. + --------------------------------------------------------------------------------------------------------- v0.8.1.5 --------------------------------------------------------------------------------------------------------- From a712ec67566771f401145539117f54cd460df26a Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 20 Jul 2018 09:53:31 +0300 Subject: [PATCH 023/198] Fixed null reference exception when trying to dispose a sub with no preview image. Closes #503 --- Barotrauma/BarotraumaShared/Source/Map/Submarine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs index d291304b5..0568cfcd4 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs @@ -1312,7 +1312,7 @@ namespace Barotrauma { savedSubmarines.Remove(this); #if CLIENT - PreviewImage.Remove(); + PreviewImage?.Remove(); PreviewImage = null; #endif } From 9963836c084cc1b943fddfd0edb37e6a4a79cb3a Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 20 Jul 2018 10:21:22 +0300 Subject: [PATCH 024/198] v0.8.1.7 --- Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs | 4 ++-- Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs | 4 ++-- Barotrauma/BarotraumaShared/changelog.txt | 6 ++++++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs index 2f1006a44..f6e89a9a8 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.6")] -[assembly: AssemblyFileVersion("0.8.1.6")] +[assembly: AssemblyVersion("0.8.1.7")] +[assembly: AssemblyFileVersion("0.8.1.7")] diff --git a/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs index 03badd491..da903abca 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.6")] -[assembly: AssemblyFileVersion("0.8.1.6")] +[assembly: AssemblyVersion("0.8.1.7")] +[assembly: AssemblyFileVersion("0.8.1.7")] diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 6dc3f75df..ab208f888 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,9 @@ +--------------------------------------------------------------------------------------------------------- +v0.8.1.7 +--------------------------------------------------------------------------------------------------------- + +- Fixed crashes when trying to load submarines in the sub editor. + --------------------------------------------------------------------------------------------------------- v0.8.1.6 --------------------------------------------------------------------------------------------------------- From f13b1b4d8445bd2fe671b66df447b8e05d27b55f Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 20 Jul 2018 11:36:22 +0300 Subject: [PATCH 025/198] Fixed camera ScreenToWorld & WorldToScreen returning invalid values when Camera.UpdateTransform has only been called once, because interpolated zoom value is initially 0 which messes up the transformation matrices. Closes #504 --- Barotrauma/BarotraumaClient/Source/Camera.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Camera.cs b/Barotrauma/BarotraumaClient/Source/Camera.cs index 487bb3b87..6b5da39dc 100644 --- a/Barotrauma/BarotraumaClient/Source/Camera.cs +++ b/Barotrauma/BarotraumaClient/Source/Camera.cs @@ -99,7 +99,7 @@ namespace Barotrauma public Camera() { - zoom = 1.0f; + zoom = prevZoom = 1.0f; rotation = 0.0f; position = Vector2.Zero; @@ -112,7 +112,7 @@ namespace Barotrauma viewMatrix = Matrix.CreateTranslation(new Vector3(GameMain.GraphicsWidth / 2.0f, GameMain.GraphicsHeight / 2.0f, 0)); - UpdateTransform(); + UpdateTransform(false); } public Vector2 TargetPos From 8e03ac928444477a5ac1dde2bc84907b8b2a42dc Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 20 Jul 2018 11:36:53 +0300 Subject: [PATCH 026/198] Fixed nullref exceptions in CharacterHUD & RuinGenerator --- Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs | 2 +- .../BarotraumaShared/Source/Map/Levels/Ruins/RuinGenerator.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs b/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs index 295d54d8c..bf6677e4c 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs @@ -32,7 +32,7 @@ namespace Barotrauma public static void TakeDamage(float amount) { - healthBar.Flash(); + healthBar?.Flash(); damageOverlayTimer = MathHelper.Clamp(amount * 0.1f, 0.2f, 1.0f); } diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/Ruins/RuinGenerator.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/Ruins/RuinGenerator.cs index 1f7e758dd..ad9c9a4d0 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/Ruins/RuinGenerator.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/Ruins/RuinGenerator.cs @@ -423,6 +423,7 @@ namespace Barotrauma.RuinGeneration Alignment[] alignments = new Alignment[] { Alignment.Top, Alignment.Bottom, Alignment.Right, Alignment.Left, Alignment.Center }; var prop = RuinStructure.GetRandom(RuinStructureType.Prop, alignments[Rand.Int(alignments.Length, Rand.RandSync.Server)]); + if (prop == null) continue; Vector2 size = (prop.Prefab is StructurePrefab) ? ((StructurePrefab)prop.Prefab).Size : Vector2.Zero; From 49c6b177e33e7ec5506de7471d2ca0899dae16ad Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 20 Jul 2018 12:05:13 +0300 Subject: [PATCH 027/198] Added wrapper methods that check if user statistics are enabled to GameAnalyticsManager --- Barotrauma/BarotraumaClient/Source/GUI/GUI.cs | 9 ++---- .../Source/Screens/LobbyScreen.cs | 2 +- .../Source/Screens/MainMenuScreen.cs | 2 +- .../Source/Screens/NetLobbyScreen.cs | 2 +- .../Source/Screens/SubEditorScreen.cs | 2 +- .../Source/Characters/Character.cs | 2 +- .../Source/GameAnalyticsManager.cs | 30 +++++++++++++++++++ .../Source/GameSession/GameSession.cs | 19 +++++------- .../Source/Networking/GameServer.cs | 11 +++---- 9 files changed, 50 insertions(+), 29 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs index 162a109f8..91d742630 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs @@ -214,12 +214,9 @@ namespace Barotrauma if (GameMain.GameSession != null) { - if (GameSettings.SendUserStatistics) - { - Mission mission = GameMain.GameSession.Mission; - GameAnalyticsSDK.Net.GameAnalytics.AddDesignEvent("QuitRound:" + (save ? "Save" : "NoSave")); - GameAnalyticsSDK.Net.GameAnalytics.AddDesignEvent("EndRound:" + (mission == null ? "NoMission" : (mission.Completed ? "MissionCompleted" : "MissionFailed"))); - } + Mission mission = GameMain.GameSession.Mission; + GameAnalyticsManager.AddDesignEvent("QuitRound:" + (save ? "Save" : "NoSave")); + GameAnalyticsManager.AddDesignEvent("EndRound:" + (mission == null ? "NoMission" : (mission.Completed ? "MissionCompleted" : "MissionFailed"))); GameMain.GameSession = null; } diff --git a/Barotrauma/BarotraumaClient/Source/Screens/LobbyScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/LobbyScreen.cs index 41ce03dfd..a8df21efa 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/LobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/LobbyScreen.cs @@ -88,7 +88,7 @@ namespace Barotrauma campaignUI.OnLocationSelected = SelectLocation; campaignUI.UpdateCharacterLists(); - if (GameSettings.SendUserStatistics) GameAnalyticsSDK.Net.GameAnalytics.SetCustomDimension01("singleplayer"); + GameAnalyticsManager.SetCustomDimension01("singleplayer"); } public override void AddToGUIUpdateList() diff --git a/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs index 6785b2c01..f375ee30d 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs @@ -143,7 +143,7 @@ namespace Barotrauma SelectTab(null, 0); - if (GameSettings.SendUserStatistics) GameAnalyticsSDK.Net.GameAnalytics.SetCustomDimension01(""); + GameAnalyticsManager.SetCustomDimension01(""); } public bool SelectTab(GUIButton button, object obj) diff --git a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs index 0f9b9ad1b..dbc53fa2f 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs @@ -501,7 +501,7 @@ namespace Barotrauma } } - if (GameSettings.SendUserStatistics) GameAnalyticsSDK.Net.GameAnalytics.SetCustomDimension01("multiplayer"); + GameAnalyticsManager.SetCustomDimension01("multiplayer"); if (GameModePreset.list.Count > 0 && modeList.Selected == null) modeList.Select(0); diff --git a/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs index 12659034f..c5d6d6bff 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs @@ -349,7 +349,7 @@ namespace Barotrauma cam.UpdateTransform(); - if (GameSettings.SendUserStatistics) GameAnalyticsSDK.Net.GameAnalytics.SetCustomDimension01("editor"); + GameAnalyticsManager.SetCustomDimension01("editor"); } public override void Deselect() diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs index dbf3e7e0c..1242b7aa6 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs @@ -1960,7 +1960,7 @@ namespace Barotrauma characterType = "Enemy"; else if (AIController is HumanAIController) characterType = "AICrew"; - GameAnalyticsSDK.Net.GameAnalytics.AddDesignEvent("Kill:" + characterType + ":" + SpeciesName + ":" + causeOfDeath); + GameAnalyticsManager.AddDesignEvent("Kill:" + characterType + ":" + SpeciesName + ":" + causeOfDeath); } if (OnDeath != null) OnDeath(this, causeOfDeath); diff --git a/Barotrauma/BarotraumaShared/Source/GameAnalyticsManager.cs b/Barotrauma/BarotraumaShared/Source/GameAnalyticsManager.cs index e96489c16..28eb2d335 100644 --- a/Barotrauma/BarotraumaShared/Source/GameAnalyticsManager.cs +++ b/Barotrauma/BarotraumaShared/Source/GameAnalyticsManager.cs @@ -62,5 +62,35 @@ namespace Barotrauma GameAnalytics.AddErrorEvent(errorSeverity, message); sentEventIdentifiers.Add(identifier); } + + public static void AddDesignEvent(string eventID) + { + if (!GameSettings.SendUserStatistics) return; + GameAnalytics.AddDesignEvent(eventID); + } + + public static void AddDesignEvent(string eventID, double value) + { + if (!GameSettings.SendUserStatistics) return; + GameAnalytics.AddDesignEvent(eventID, value); + } + + public static void AddProgressionEvent(EGAProgressionStatus progressionStatus, string progression01) + { + if (!GameSettings.SendUserStatistics) return; + GameAnalytics.AddProgressionEvent(progressionStatus, progression01); + } + + public static void AddProgressionEvent(EGAProgressionStatus progressionStatus, string progression01, string progression02) + { + if (!GameSettings.SendUserStatistics) return; + GameAnalytics.AddProgressionEvent(progressionStatus, progression01, progression02); + } + + public static void SetCustomDimension01(string dimension) + { + if (!GameSettings.SendUserStatistics) return; + GameAnalytics.SetCustomDimension01(dimension); + } } } diff --git a/Barotrauma/BarotraumaShared/Source/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/Source/GameSession/GameSession.cs index ef9c4f3fb..61394b4c6 100644 --- a/Barotrauma/BarotraumaShared/Source/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/Source/GameSession/GameSession.cs @@ -234,13 +234,11 @@ namespace Barotrauma campaign.CargoManager.CreateItems(); } } - - if (GameSettings.SendUserStatistics) - { - GameAnalyticsSDK.Net.GameAnalytics.AddDesignEvent("Submarine:" + submarine.Name); - GameAnalyticsSDK.Net.GameAnalytics.AddProgressionEvent(GameAnalyticsSDK.Net.EGAProgressionStatus.Start, + + GameAnalyticsManager.AddDesignEvent("Submarine:" + submarine.Name); + GameAnalyticsManager.AddProgressionEvent(GameAnalyticsSDK.Net.EGAProgressionStatus.Start, GameMode.Name, (Mission == null ? "None" : Mission.GetType().ToString())); - } + #if CLIENT roundSummary = new RoundSummary(this); @@ -253,11 +251,10 @@ namespace Barotrauma public void EndRound(string endMessage) { if (Mission != null) Mission.End(); - if (GameSettings.SendUserStatistics) - { - GameAnalyticsSDK.Net.GameAnalytics.AddProgressionEvent((Mission == null || Mission.Completed) ? GameAnalyticsSDK.Net.EGAProgressionStatus.Complete : GameAnalyticsSDK.Net.EGAProgressionStatus.Fail, - GameMode.Name, (Mission == null ? "None" : Mission.GetType().ToString())); - } + GameAnalyticsManager.AddProgressionEvent( + (Mission == null || Mission.Completed) ? GameAnalyticsSDK.Net.EGAProgressionStatus.Complete : GameAnalyticsSDK.Net.EGAProgressionStatus.Fail, + GameMode.Name, + (Mission == null ? "None" : Mission.GetType().ToString())); #if CLIENT if (roundSummary != null) diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs index 60b587a89..a0da30a20 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs @@ -209,7 +209,7 @@ namespace Barotrauma.Networking GameMain.NetLobbyScreen.RandomizeSettings(); started = true; - if (GameSettings.SendUserStatistics) GameAnalyticsSDK.Net.GameAnalytics.AddDesignEvent("GameServer:Start"); + GameAnalyticsManager.AddDesignEvent("GameServer:Start"); yield return CoroutineStatus.Success; } @@ -1370,7 +1370,7 @@ namespace Barotrauma.Networking } } - if (GameSettings.SendUserStatistics) GameAnalyticsSDK.Net.GameAnalytics.AddDesignEvent("Traitors:" + (TraitorManager == null ? "Disabled" : "Enabled")); + GameAnalyticsManager.AddDesignEvent("Traitors:" + (TraitorManager == null ? "Disabled" : "Enabled")); SendStartMessage(roundStartSeed, Submarine.MainSub, GameMain.GameSession.GameMode.Preset, connectedClients); @@ -2249,11 +2249,8 @@ namespace Barotrauma.Networking Log("Shutting down the server...", ServerLog.MessageType.ServerMessage); log.Save(); } - - if (GameSettings.SendUserStatistics) - { - GameAnalyticsSDK.Net.GameAnalytics.AddDesignEvent("GameServer:ShutDown"); - } + + GameAnalyticsManager.AddDesignEvent("GameServer:ShutDown"); server.Shutdown("The server has been shut down"); } } From 15331c58bb0193fd4d5cca8a934885d2cfe1fdfe Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 2 Jul 2018 16:04:55 +0300 Subject: [PATCH 028/198] Cherry-picked fa84c5b (ragdoll optimization) --- .../Source/Characters/Animation/Ragdoll.cs | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs index 3a9fbcd73..869180621 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs @@ -92,7 +92,9 @@ namespace Barotrauma protected List collider; protected int colliderIndex = 0; - + + private Category prevCollisionCategory = Category.None; + public PhysicsBody Collider { get @@ -274,11 +276,7 @@ namespace Barotrauma get { return ignorePlatforms; } set { - if (ignorePlatforms == value) return; ignorePlatforms = value; - - UpdateCollisionCategories(); - } } @@ -812,10 +810,7 @@ namespace Barotrauma } CurrentHull = newHull; - - character.Submarine = currentHull == null ? null : currentHull.Submarine; - - UpdateCollisionCategories(); + character.Submarine = currentHull?.Submarine; } public void Teleport(Vector2 moveAmount, Vector2 velocityChange) @@ -855,7 +850,10 @@ namespace Barotrauma Category collisionCategory = (ignorePlatforms) ? wall | Physics.CollisionProjectile | Physics.CollisionStairs : wall | Physics.CollisionProjectile | Physics.CollisionPlatform | Physics.CollisionStairs; - + + if (collisionCategory == prevCollisionCategory) return; + prevCollisionCategory = collisionCategory; + Collider.CollidesWith = collisionCategory; foreach (Limb limb in Limbs) @@ -881,6 +879,7 @@ namespace Barotrauma UpdateNetPlayerPosition(deltaTime); CheckDistFromCollider(); + UpdateCollisionCategories(); Vector2 flowForce = Vector2.Zero; @@ -1268,8 +1267,6 @@ namespace Barotrauma { //set the position of the ragdoll to make sure limbs don't get stuck inside walls when re-enabling collisions SetPosition(Collider.SimPosition, true); - - UpdateCollisionCategories(); collisionsDisabled = false; } } From e7e7d321236155f3a8bd0a7085872709edf67919 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 20 Jun 2018 15:55:23 +0300 Subject: [PATCH 029/198] Cherry-picked 0ff9a3d (more ragdoll optimization) --- .../Source/Characters/Animation/Ragdoll.cs | 38 ++++++++----------- .../BarotraumaShared/Source/Items/Item.cs | 12 +++--- 2 files changed, 21 insertions(+), 29 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs index 869180621..273a83788 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs @@ -1101,22 +1101,19 @@ namespace Barotrauma float tfloorY = rayStart.Y + (rayEnd.Y - rayStart.Y) * closestFraction; float targetY = tfloorY + Collider.height * 0.5f + Collider.radius + colliderHeightFromFloor; - - if (Math.Abs(Collider.SimPosition.Y - targetY) > 0.01f && Collider.SimPosition.Y 0.01f) { - Vector2 newSpeed = Collider.LinearVelocity; - newSpeed.Y = (targetY - Collider.SimPosition.Y)*5.0f; - Collider.LinearVelocity = newSpeed; - } - else - { - Vector2 newSpeed = Collider.LinearVelocity; - newSpeed.Y = 0.0f; - Collider.LinearVelocity = newSpeed; - Vector2 newPos = Collider.SimPosition; - newPos.Y = targetY; - Collider.SetTransform(newPos, Collider.Rotation); - } + if (forceImmediate) + { + Collider.LinearVelocity = new Vector2(Collider.LinearVelocity.X, 0); + Collider.SetTransform(new Vector2(Collider.SimPosition.X, targetY), Collider.Rotation); + } + else + { + Collider.LinearVelocity = new Vector2(Collider.LinearVelocity.X, (targetY - Collider.SimPosition.Y) * 5.0f); + } + } } } } @@ -1465,22 +1462,17 @@ namespace Barotrauma private Vector2 GetFlowForce() { - Vector2 limbPos = ConvertUnits.ToDisplayUnits(Limbs[0].SimPosition); + Vector2 limbPos = Limbs[0].Position; Vector2 force = Vector2.Zero; - foreach (MapEntity e in MapEntity.mapEntityList) + foreach (Gap gap in Gap.GapList) { - Gap gap = e as Gap; - if (gap == null || gap.FlowTargetHull != currentHull || gap.LerpedFlowForce == Vector2.Zero) continue; + if (gap.Open <= 0.0f || gap.FlowTargetHull != currentHull || gap.LerpedFlowForce == Vector2.Zero) continue; Vector2 gapPos = gap.SimPosition; - float dist = Vector2.Distance(limbPos, gapPos); - force += Vector2.Normalize(gap.LerpedFlowForce) * (Math.Max(gap.LerpedFlowForce.Length() - dist, 0.0f) / 500.0f); } - - if (force.Length() > 20.0f) return force; return force; } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index 32da8efeb..5fe179479 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -783,12 +783,6 @@ namespace Barotrauma public override void Update(float deltaTime, Camera cam) { - if (Level.Loaded != null && WorldPosition.Y < Level.MaxEntityDepth) - { - Spawner.AddToRemoveQueue(this); - return; - } - ApplyStatusEffects(ActionType.Always, deltaTime, null); foreach (ItemComponent ic in components) @@ -851,6 +845,12 @@ namespace Barotrauma MathHelper.Clamp(body.LinearVelocity.X, -MaxVel, MaxVel), MathHelper.Clamp(body.LinearVelocity.Y, -MaxVel, MaxVel)); } + + if (CurrentHull == null && body.SimPosition.Y < ConvertUnits.ToSimUnits(Level.MaxEntityDepth)) + { + Spawner.AddToRemoveQueue(this); + return; + } } UpdateNetPosition(); From bcd9fd7e5f0c50a7a2814c6524f03270a9ae624d Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 20 Jul 2018 12:23:07 +0300 Subject: [PATCH 030/198] Cherry-picked de2136c (level physicsbody optimization) --- .../Source/Map/Levels/CaveGenerator.cs | 38 ++++++++----------- .../Source/Map/Levels/LevelWall.cs | 4 +- .../Source/Map/SubmarineBody.cs | 24 +++++------- 3 files changed, 25 insertions(+), 41 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/CaveGenerator.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/CaveGenerator.cs index 790e08eff..9cccfc97f 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/CaveGenerator.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/CaveGenerator.cs @@ -323,18 +323,9 @@ namespace Barotrauma //choose random edge (ignoring ones where the adjacent cell is outside limits) else { - - - //if (allowedEdges.Count==0) - //{ - // edgeIndex = Rand.Int(currentCell.edges.Count, false); - //} - //else - //{ edgeIndex = Rand.Int(allowedEdges.Count, Rand.RandSync.Server); if (mirror && edgeIndex > 0) edgeIndex = allowedEdges.Count - edgeIndex; edgeIndex = currentCell.edges.IndexOf(allowedEdges[edgeIndex]); - //} } currentCell = currentCell.edges[edgeIndex].AdjacentCell(currentCell); @@ -357,8 +348,8 @@ namespace Barotrauma return pathCells; } - - public static List GeneratePolygons(List cells, out List renderTriangles, bool setSolid=true) + + public static List GeneratePolygons(List cells, out List renderTriangles, bool setSolid = true) { renderTriangles = new List(); var bodies = new List(); @@ -366,6 +357,14 @@ namespace Barotrauma List tempVertices = new List(); List bodyPoints = new List(); + Body cellBody = new Body(GameMain.World) + { + SleepingAllowed = false, + BodyType = BodyType.Static, + CollisionCategories = Physics.CollisionLevel + }; + bodies.Add(cellBody); + for (int n = cells.Count - 1; n >= 0; n-- ) { VoronoiCell cell = cells[n]; @@ -412,14 +411,12 @@ namespace Barotrauma cell.bodyVertices.Add(bodyPoints[i]); bodyPoints[i] = ConvertUnits.ToSimUnits(bodyPoints[i]); } - - + if (cell.CellType == CellType.Empty) continue; + cellBody.UserData = cell; var triangles = MathUtils.TriangulateConvexHull(bodyPoints, ConvertUnits.ToSimUnits(cell.Center)); - - Body cellBody = new Body(GameMain.World); - + for (int i = 0; i < triangles.Count; i++) { //don't create a triangle if any of the vertices are too close to each other @@ -429,16 +426,11 @@ namespace Barotrauma Vector2.Distance(triangles[i][1], triangles[i][2]) < 0.05f) continue; Vertices bodyVertices = new Vertices(triangles[i]); - FixtureFactory.AttachPolygon(bodyVertices, 5.0f, cellBody); + var newFixture = FixtureFactory.AttachPolygon(bodyVertices, 5.0f, cellBody); + newFixture.UserData = cell; } - cellBody.UserData = cell; - cellBody.SleepingAllowed = false; - cellBody.BodyType = BodyType.Kinematic; - cellBody.CollisionCategories = Physics.CollisionLevel; - cell.body = cellBody; - bodies.Add(cellBody); } return bodies; diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelWall.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelWall.cs index 059301862..a0445de16 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelWall.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelWall.cs @@ -21,7 +21,6 @@ namespace Barotrauma public LevelWall(List edgePositions, Vector2 extendAmount, Color color) { cells = new List(); - for (int i = 0; i < edgePositions.Count - 1; i++) { Vector2[] vertices = new Vector2[4]; @@ -47,8 +46,7 @@ namespace Barotrauma cells.Add(wallCell); } - List triangles; - bodies = CaveGenerator.GeneratePolygons(cells, out triangles, false); + bodies = CaveGenerator.GeneratePolygons(cells, out List triangles, false); #if CLIENT List bodyVertices = CaveGenerator.GenerateRenderVerticeList(triangles); diff --git a/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs b/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs index 6d623c73c..bdf43b42e 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs @@ -370,27 +370,22 @@ namespace Barotrauma public bool OnCollision(Fixture f1, Fixture f2, Contact contact) { - Limb limb = f2.Body.UserData as Limb; - if (limb != null) + if (f2.Body.UserData is Limb limb) { bool collision = CheckLimbCollision(contact, limb); if (collision) HandleLimbCollision(contact, limb); return collision; } - VoronoiCell cell = f2.Body.UserData as VoronoiCell; - if (cell != null) + if (f2.UserData is VoronoiCell cell) { HandleLevelCollision(contact, Vector2.Normalize(ConvertUnits.ToDisplayUnits(Body.SimPosition) - cell.Center)); return true; } - Structure structure = f2.Body.UserData as Structure; - if (structure != null) + if (f2.Body.UserData is Structure structure) { - Vector2 normal; - FixedArray2 points; - contact.GetWorldManifold(out normal, out points); + contact.GetWorldManifold(out Vector2 normal, out FixedArray2 points); if (contact.FixtureA.Body == f1.Body) { normal = -normal; @@ -400,8 +395,7 @@ namespace Barotrauma return true; } - Submarine otherSub = f2.Body.UserData as Submarine; - if (otherSub != null) + if (f2.Body.UserData is Submarine otherSub) { HandleSubCollision(contact, otherSub); return true; @@ -486,8 +480,8 @@ namespace Barotrauma 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); + VoronoiCell cell = levelContact.FixtureB.UserData is VoronoiCell ? + ((VoronoiCell)levelContact.FixtureB.UserData) : ((VoronoiCell)levelContact.FixtureA.UserData); var cellDiff = ConvertUnits.ToDisplayUnits(limb.body.SimPosition) - cell.Center; if (Vector2.Dot(contactNormal, cellDiff) < 0) @@ -603,8 +597,8 @@ namespace Barotrauma 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); + VoronoiCell cell = levelContact.FixtureB.UserData is VoronoiCell ? + ((VoronoiCell)levelContact.FixtureB.UserData) : ((VoronoiCell)levelContact.FixtureA.UserData); var cellDiff = ConvertUnits.ToDisplayUnits(Body.SimPosition) - cell.Center; if (Vector2.Dot(contactNormal, cellDiff) < 0) From 20ed61a87937d35ad001359de94395586a2d2306 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 20 Jul 2018 12:24:05 +0300 Subject: [PATCH 031/198] Version string sent to GameAnalytics includes the name and hash of the exe (makes it easier to identify events/errors from modded games), added stack trace to a ragdoll error message --- .../Source/Characters/Animation/Ragdoll.cs | 2 +- Barotrauma/BarotraumaShared/Source/GameAnalyticsManager.cs | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs index 273a83788..3456f0ccf 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs @@ -28,7 +28,7 @@ namespace Barotrauma GameAnalyticsManager.AddErrorEventOnce( "Ragdoll.Limbs:AccessRemoved", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, - "Attempted to access a potentially removed ragdoll. Character: " + character.Name + ", id: " + character.ID + ", removed: " + character.Removed + ", ragdoll removed: " + !list.Contains(this)); + "Attempted to access a potentially removed ragdoll. Character: " + character.Name + ", id: " + character.ID + ", removed: " + character.Removed + ", ragdoll removed: " + !list.Contains(this) + "\n" + Environment.StackTrace); return new Limb[0]; } diff --git a/Barotrauma/BarotraumaShared/Source/GameAnalyticsManager.cs b/Barotrauma/BarotraumaShared/Source/GameAnalyticsManager.cs index 28eb2d335..36cc8fbe1 100644 --- a/Barotrauma/BarotraumaShared/Source/GameAnalyticsManager.cs +++ b/Barotrauma/BarotraumaShared/Source/GameAnalyticsManager.cs @@ -17,7 +17,6 @@ namespace Barotrauma #if DEBUG GameAnalytics.SetEnabledInfoLog(true); #endif - GameAnalytics.ConfigureBuild(GameMain.Version.ToString()); string exePath = Assembly.GetEntryAssembly().Location; string exeName = null; @@ -36,9 +35,13 @@ namespace Barotrauma DebugConsole.ThrowError("Error while calculating MD5 hash for the executable \"" + exePath + "\"", e); } + GameAnalytics.ConfigureBuild(GameMain.Version.ToString() + + (string.IsNullOrEmpty(exeName) ? "Unknown" : exeName) + ":" + + ((exeHash?.ShortHash == null) ? "Unknown" : exeHash.ShortHash)); + GameAnalytics.AddDesignEvent("Executable:" + (string.IsNullOrEmpty(exeName) ? "Unknown" : exeName) + ":" - + ((exeHash == null) ? "Unknown" : exeHash.ShortHash)); + + ((exeHash?.ShortHash == null) ? "Unknown" : exeHash.ShortHash)); GameAnalytics.ConfigureAvailableCustomDimensions01("singleplayer", "multiplayer", "editor"); GameAnalytics.Initialize("a3a073c20982de7c15d21e840e149122", "9010ad9a671233b8d9610d76cec8c897d9ff3ba7"); From 07de2b2a0fe3ff43051022045f2ad822abba91d0 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 20 Jul 2018 13:50:04 +0300 Subject: [PATCH 032/198] Fixed previous items not being cleared from multiplayer campaign UI after a round ends. Closes #505 --- Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs | 3 +++ Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs | 1 + 2 files changed, 4 insertions(+) diff --git a/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs b/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs index 4751c8ac2..1e821b151 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs @@ -149,6 +149,8 @@ namespace Barotrauma null, null, Alignment.TopRight, "", frame); } + + RefreshItemTab(); } public void Update(float deltaTime) @@ -293,6 +295,7 @@ namespace Barotrauma { CreateItemFrame(ip, selectedItemList, selectedItemList.Rect.Width); } + selectedItemList.UpdateScrollBarSize(); } public void SelectTab(Tab tab) diff --git a/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs b/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs index e07c7e124..580321792 100644 --- a/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs +++ b/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs @@ -57,6 +57,7 @@ namespace Barotrauma public void CreateItems() { CreateItems(purchasedItems); + OnItemsChanged?.Invoke(); } public static void CreateItems(List itemsToSpawn) From a61ac1716d95ba1c4dc464d0ae1fa252560c090b Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 20 Jul 2018 14:17:59 +0300 Subject: [PATCH 033/198] Sending level seed & level gen errors to GameAnalytics --- .../Source/Map/Levels/CaveGenerator.cs | 10 +++++++++- .../Source/GameSession/GameSession.cs | 1 + .../Source/Map/Levels/CaveGenerator.cs | 11 ++++++++++- .../BarotraumaShared/Source/Map/Levels/Level.cs | 6 +++--- .../BarotraumaShared/Source/Map/Levels/LevelWall.cs | 6 +++--- 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Map/Levels/CaveGenerator.cs b/Barotrauma/BarotraumaClient/Source/Map/Levels/CaveGenerator.cs index 6bc67482c..942925c27 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Levels/CaveGenerator.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Levels/CaveGenerator.cs @@ -27,7 +27,7 @@ namespace Barotrauma return verticeList; } - public static VertexPositionTexture[] GenerateWallShapes(List cells) + public static VertexPositionTexture[] GenerateWallShapes(List cells, Level level) { float inwardThickness = 500.0f, outWardThickness = 30.0f; @@ -80,6 +80,10 @@ namespace Barotrauma #if DEBUG DebugConsole.ThrowError("Invalid left normal"); #endif + GameAnalyticsManager.AddErrorEventOnce("CaveGenerator.GenerateWallShapes:InvalidLeftNormal:" + level.Seed, + GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, + "Invalid left normal (leftedge: " + leftEdge + ", rightedge: " + rightEdge + ", normal: " + leftNormal + ", seed: " + level.Seed + ")"); + if (cell.body != null) { GameMain.World.RemoveBody(cell.body); @@ -106,6 +110,10 @@ namespace Barotrauma #if DEBUG DebugConsole.ThrowError("Invalid right normal"); #endif + GameAnalyticsManager.AddErrorEventOnce("CaveGenerator.GenerateWallShapes:InvalidRightNormal:" + level.Seed, + GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, + "Invalid right normal (leftedge: " + leftEdge + ", rightedge: " + rightEdge + ", normal: " + rightNormal + ", seed: " + level.Seed + ")"); + if (cell.body != null) { GameMain.World.RemoveBody(cell.body); diff --git a/Barotrauma/BarotraumaShared/Source/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/Source/GameSession/GameSession.cs index 61394b4c6..91ded070f 100644 --- a/Barotrauma/BarotraumaShared/Source/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/Source/GameSession/GameSession.cs @@ -236,6 +236,7 @@ namespace Barotrauma } GameAnalyticsManager.AddDesignEvent("Submarine:" + submarine.Name); + GameAnalyticsManager.AddDesignEvent("Level", ToolBox.StringToInt(level.Seed)); GameAnalyticsManager.AddProgressionEvent(GameAnalyticsSDK.Net.EGAProgressionStatus.Start, GameMode.Name, (Mission == null ? "None" : Mission.GetType().ToString())); diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/CaveGenerator.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/CaveGenerator.cs index 9cccfc97f..1e08742a2 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/CaveGenerator.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/CaveGenerator.cs @@ -349,7 +349,7 @@ namespace Barotrauma return pathCells; } - public static List GeneratePolygons(List cells, out List renderTriangles, bool setSolid = true) + public static List GeneratePolygons(List cells, Level level, out List renderTriangles, bool setSolid = true) { renderTriangles = new List(); var bodies = new List(); @@ -428,6 +428,15 @@ namespace Barotrauma Vertices bodyVertices = new Vertices(triangles[i]); var newFixture = FixtureFactory.AttachPolygon(bodyVertices, 5.0f, cellBody); newFixture.UserData = cell; + + if (newFixture.Shape.MassData.Area < FarseerPhysics.Settings.Epsilon) + { + DebugConsole.ThrowError("Invalid triangle created by CaveGenerator (" + triangles[i][0] + ", " + triangles[i][1] + ", " + triangles[i][2] + ")"); + GameAnalyticsManager.AddErrorEventOnce( + "CaveGenerator.GeneratePolygons:InvalidTriangle", + GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, + "Invalid triangle created by CaveGenerator (" + triangles[i][0] + ", " + triangles[i][1] + ", " + triangles[i][2] + "). Seed: " + level.Seed); + } } cell.body = cellBody; diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs index 1ab021186..7d7ce68e1 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs @@ -449,11 +449,11 @@ namespace Barotrauma List cellsWithBody = new List(cells); List triangles; - bodies = CaveGenerator.GeneratePolygons(cellsWithBody, out triangles); + bodies = CaveGenerator.GeneratePolygons(cellsWithBody, this, out triangles); #if CLIENT renderer.SetBodyVertices(CaveGenerator.GenerateRenderVerticeList(triangles).ToArray(), generationParams.WallColor); - renderer.SetWallVertices(CaveGenerator.GenerateWallShapes(cells), generationParams.WallColor); + renderer.SetWallVertices(CaveGenerator.GenerateWallShapes(cells, this), generationParams.WallColor); #endif TopBarrier = BodyFactory.CreateEdge(GameMain.World, @@ -700,7 +700,7 @@ namespace Barotrauma SeaFloorTopPos = bottomPositions.Max(p => p.Y); - extraWalls = new LevelWall[] { new LevelWall(bottomPositions, new Vector2(0.0f, -2000.0f), backgroundColor) }; + extraWalls = new LevelWall[] { new LevelWall(bottomPositions, new Vector2(0.0f, -2000.0f), backgroundColor, this) }; BottomBarrier = BodyFactory.CreateEdge(GameMain.World, ConvertUnits.ToSimUnits(new Vector2(borders.X, 0)), diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelWall.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelWall.cs index a0445de16..b79ba05fc 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelWall.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelWall.cs @@ -18,7 +18,7 @@ namespace Barotrauma private List bodies; - public LevelWall(List edgePositions, Vector2 extendAmount, Color color) + public LevelWall(List edgePositions, Vector2 extendAmount, Color color, Level level) { cells = new List(); for (int i = 0; i < edgePositions.Count - 1; i++) @@ -46,13 +46,13 @@ namespace Barotrauma cells.Add(wallCell); } - bodies = CaveGenerator.GeneratePolygons(cells, out List triangles, false); + bodies = CaveGenerator.GeneratePolygons(cells, level, out List triangles, false); #if CLIENT List bodyVertices = CaveGenerator.GenerateRenderVerticeList(triangles); SetBodyVertices(bodyVertices.ToArray(), color); - SetWallVertices(CaveGenerator.GenerateWallShapes(cells), color); + SetWallVertices(CaveGenerator.GenerateWallShapes(cells, level), color); #endif } From 0abdcb969ded4a15ead0d26e14b085501b6b670f Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 20 Jul 2018 15:18:46 +0300 Subject: [PATCH 034/198] Fixed "failed to write event" errors when a client or server tries to change an enum value of an item (e.g. the output type of an oscillator component) --- .../BarotraumaShared/Source/Items/Item.cs | 22 +++++++++++++++++++ .../Serialization/SerializableProperty.cs | 1 + 2 files changed, 23 insertions(+) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index 5fe179479..4e3d6f8e4 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -1430,6 +1430,10 @@ namespace Barotrauma msg.Write(((Rectangle)value).Width); msg.Write(((Rectangle)value).Height); } + else if (value is Enum) + { + msg.Write((int)value); + } else { throw new System.NotImplementedException("Serializing item properties of the type \"" + value.GetType() + "\" not supported"); @@ -1487,6 +1491,24 @@ namespace Barotrauma { property.TrySetValue(new Vector4(msg.ReadInt32(), msg.ReadInt32(), msg.ReadInt32(), msg.ReadInt32())); } + else if (typeof(Enum).IsAssignableFrom(type)) + { + int intVal = msg.ReadInt32(); + try + { + property.TrySetValue(Enum.ToObject(type, intVal)); + } + catch (Exception e) + { +#if DEBUG + DebugConsole.ThrowError("Failed to convert the int value \"" + intVal + "\" to " + type, e); +#endif + GameAnalyticsManager.AddErrorEventOnce( + "Item.ReadPropertyChange:" + Name + ":" + type, + GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, + "Failed to convert the int value \"" + intVal + "\" to " + type + " (item " + Name + ")"); + } + } else { return; diff --git a/Barotrauma/BarotraumaShared/Source/Serialization/SerializableProperty.cs b/Barotrauma/BarotraumaShared/Source/Serialization/SerializableProperty.cs index 6ab463699..7e1d34860 100644 --- a/Barotrauma/BarotraumaShared/Source/Serialization/SerializableProperty.cs +++ b/Barotrauma/BarotraumaShared/Source/Serialization/SerializableProperty.cs @@ -223,6 +223,7 @@ namespace Barotrauma return false; } propertyInfo.SetValue(obj, enumVal); + return true; } else { From f0a663bab0d63afa6ffceffca76e8cf0ef16e991 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 20 Jul 2018 16:10:49 +0300 Subject: [PATCH 035/198] Added some extra information to OpenAL error messages --- .../Source/Sounds/OggSound.cs | 12 ++-- .../Source/Sounds/OggStream.cs | 58 +++++++++++-------- .../BarotraumaClient/Source/Sounds/Sound.cs | 6 +- .../Source/Sounds/SoundManager.cs | 20 +++---- 4 files changed, 54 insertions(+), 42 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Sounds/OggSound.cs b/Barotrauma/BarotraumaClient/Source/Sounds/OggSound.cs index 37aa0a313..172ce99b2 100644 --- a/Barotrauma/BarotraumaClient/Source/Sounds/OggSound.cs +++ b/Barotrauma/BarotraumaClient/Source/Sounds/OggSound.cs @@ -56,7 +56,7 @@ namespace Barotrauma.Sounds AL.BufferData(sound.alBufferId, reader.Channels == 1 ? ALFormat.Mono16 : ALFormat.Stereo16, sound.castBuffer, readSamples * sizeof(short), reader.SampleRate); - ALHelper.Check(); + ALHelper.Check(oggFile); } //AL.Source(alSourceId, ALSourcei.Buffer, alBufferId); @@ -100,12 +100,12 @@ namespace Barotrauma.Sounds public void Dispose() { - //var state = AL.GetSourceState(alSourceId); - //if (state == ALSourceState.Playing || state == ALSourceState.Paused) - // Stop(); System.Diagnostics.Debug.WriteLine(alBufferId); - //AL.DeleteSource(alSourceId); - AL.DeleteBuffer(alBufferId); + if (alBufferId > 0) + { + AL.DeleteBuffer(alBufferId); + alBufferId = 0; + } //if (ALHelper.Efx.IsInitialized) // ALHelper.Efx.DeleteFilter(alFilterId); diff --git a/Barotrauma/BarotraumaClient/Source/Sounds/OggStream.cs b/Barotrauma/BarotraumaClient/Source/Sounds/OggStream.cs index 6f761edd3..945cd0173 100644 --- a/Barotrauma/BarotraumaClient/Source/Sounds/OggStream.cs +++ b/Barotrauma/BarotraumaClient/Source/Sounds/OggStream.cs @@ -45,20 +45,24 @@ namespace Barotrauma.Sounds //logHandler(String.Format("Total memory : {0:0.###} {1} ", usedHeap, sizes[order]), 0, 6); } - public static void Check() + public static void Check(string extraErrorMsg = "") { ALError error; if ((error = AL.GetError()) != ALError.NoError) { + string errorMsg = "OpenAL error: " + AL.GetErrorString(error); + if (!string.IsNullOrEmpty(extraErrorMsg)) errorMsg += " {" + extraErrorMsg + "} "; + errorMsg += "\n" + Environment.StackTrace; + #if DEBUG - DebugConsole.ThrowError("OpenAL error: " + AL.GetErrorString(error) + "\n" + Environment.StackTrace); + DebugConsole.ThrowError(errorMsg); #else - DebugConsole.NewMessage("OpenAL error: " + AL.GetErrorString(error) + "\n" + Environment.StackTrace, Microsoft.Xna.Framework.Color.Red); + DebugConsole.NewMessage(errorMsg, Microsoft.Xna.Framework.Color.Red); #endif GameAnalyticsManager.AddErrorEventOnce( - "OggStream.Check:"+ AL.GetErrorString(error), + "OggStream.Check:" + AL.GetErrorString(error) + extraErrorMsg, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, - "OpenAL error: " + AL.GetErrorString(error) + "\n" + Environment.StackTrace); + errorMsg); } } } @@ -87,9 +91,17 @@ namespace Barotrauma.Sounds public Action LogHandler; #endif - public OggStream(string filename, int bufferCount = DefaultBufferCount) : this(File.OpenRead(filename), bufferCount) { } - public OggStream(Stream stream, int bufferCount = DefaultBufferCount) + public string FileName { + get; + private set; + } + + public OggStream(string filename, int bufferCount = DefaultBufferCount) : this(File.OpenRead(filename), filename, bufferCount) { } + public OggStream(Stream stream, string fileName, int bufferCount = DefaultBufferCount) + { + this.FileName = fileName; + BufferCount = bufferCount; alBufferIds = AL.GenBuffers(bufferCount); @@ -98,7 +110,7 @@ namespace Barotrauma.Sounds if (ALHelper.XRam.IsInitialized) { ALHelper.XRam.SetBufferMode(BufferCount, ref alBufferIds[0], XRamExtension.XRamStorage.Hardware); - ALHelper.Check(); + ALHelper.Check(fileName); } if (ALHelper.Efx.IsInitialized) @@ -167,7 +179,7 @@ namespace Barotrauma.Sounds AL.SourcePlay(alSourceId); this.Volume = volume; - ALHelper.Check(); + ALHelper.Check(FileName); Preparing = false; @@ -181,7 +193,7 @@ namespace Barotrauma.Sounds OggStreamer.Instance.RemoveStream(this); AL.SourcePause(alSourceId); - ALHelper.Check(); + ALHelper.Check(FileName); } public void Resume() @@ -191,7 +203,7 @@ namespace Barotrauma.Sounds OggStreamer.Instance.AddStream(this); AL.SourcePlay(alSourceId); - ALHelper.Check(); + ALHelper.Check(FileName); } public void Stop() @@ -230,7 +242,7 @@ namespace Barotrauma.Sounds set { AL.Source(alSourceId, ALSourcef.Gain, volume = value); - ALHelper.Check(); + ALHelper.Check(FileName); } } @@ -260,20 +272,20 @@ namespace Barotrauma.Sounds /*if (ALHelper.Efx.IsInitialized) ALHelper.Efx.DeleteFilter(alFilterId);*/ - ALHelper.Check(); + ALHelper.Check(FileName); } void StopPlayback() { AL.SourceStop(alSourceId); - ALHelper.Check(); + ALHelper.Check(FileName); } void Empty() { int queued; AL.GetSource(alSourceId, ALGetSourcei.BuffersQueued, out queued); - ALHelper.Check(); + ALHelper.Check(FileName); if (queued > 0) { @@ -296,12 +308,12 @@ namespace Barotrauma.Sounds if (processed > 0) { AL.SourceUnqueueBuffers(alSourceId, processed, salvaged); - ALHelper.Check(); + ALHelper.Check(FileName); } // Try turning it off again? AL.SourceStop(alSourceId); - ALHelper.Check(); + ALHelper.Check(FileName); Empty(); } @@ -318,7 +330,7 @@ namespace Barotrauma.Sounds // Fill first buffer synchronously OggStreamer.Instance.FillBuffer(this, alBufferIds[0]); AL.SourceQueueBuffer(alSourceId, alBufferIds[0]); - ALHelper.Check(); + ALHelper.Check(FileName); // Schedule the others asynchronously OggStreamer.Instance.AddStream(this); @@ -433,7 +445,7 @@ namespace Barotrauma.Sounds } AL.BufferData(bufferId, stream.Reader.Channels == 1 ? ALFormat.Mono16 : ALFormat.Stereo16, castBuffer, readSamples * sizeof(short), stream.Reader.SampleRate); - ALHelper.Check(); + ALHelper.Check(stream.FileName); return readSamples != BufferSize; } @@ -470,10 +482,10 @@ namespace Barotrauma.Sounds int queued; AL.GetSource(stream.alSourceId, ALGetSourcei.BuffersQueued, out queued); - ALHelper.Check(); + ALHelper.Check(stream.FileName); int processed; AL.GetSource(stream.alSourceId, ALGetSourcei.BuffersProcessed, out processed); - ALHelper.Check(); + ALHelper.Check(stream.FileName); if (processed == 0 && queued == stream.BufferCount) continue; @@ -500,7 +512,7 @@ namespace Barotrauma.Sounds } AL.SourceQueueBuffers(stream.alSourceId, tempBuffers.Length, tempBuffers); - ALHelper.Check(); + ALHelper.Check(stream.FileName); if (finished && !stream.IsLooped) continue; @@ -518,7 +530,7 @@ namespace Barotrauma.Sounds if (state == ALSourceState.Stopped) { AL.SourcePlay(stream.alSourceId); - ALHelper.Check(); + ALHelper.Check(stream.FileName); } } } diff --git a/Barotrauma/BarotraumaClient/Source/Sounds/Sound.cs b/Barotrauma/BarotraumaClient/Source/Sounds/Sound.cs index c1dd80996..db4563b2e 100644 --- a/Barotrauma/BarotraumaClient/Source/Sounds/Sound.cs +++ b/Barotrauma/BarotraumaClient/Source/Sounds/Sound.cs @@ -54,7 +54,7 @@ namespace Barotrauma { DebugConsole.ThrowError("Failed to load sound "+file+"!", e); } - ALHelper.Check(); + ALHelper.Check(file); } baseVolume = 1.0f; @@ -219,7 +219,7 @@ namespace Barotrauma (SoundManager.IsPlaying(alSourceId) || SoundManager.IsPaused(alSourceId))) { SoundManager.Stop(alSourceId); - ALHelper.Check(); + ALHelper.Check(filePath); } foreach (Sound s in loadedSounds) @@ -228,7 +228,7 @@ namespace Barotrauma } SoundManager.ClearAlSource(AlBufferId); - ALHelper.Check(); + ALHelper.Check(filePath); if (oggSound != null) { diff --git a/Barotrauma/BarotraumaClient/Source/Sounds/SoundManager.cs b/Barotrauma/BarotraumaClient/Source/Sounds/SoundManager.cs index 301583f3c..cd1b4db7a 100644 --- a/Barotrauma/BarotraumaClient/Source/Sounds/SoundManager.cs +++ b/Barotrauma/BarotraumaClient/Source/Sounds/SoundManager.cs @@ -143,7 +143,7 @@ namespace Barotrauma.Sounds volume = 0.0f; } - if (sourceIndex<1 || soundsPlaying[sourceIndex] != sound) + if (sourceIndex < 1 || soundsPlaying[sourceIndex] != sound) { sourceIndex = Play(sound, position, volume, 0.0f, true); } @@ -153,7 +153,7 @@ namespace Barotrauma.Sounds AL.Source(alSources[sourceIndex], ALSourceb.Looping, true); } - ALHelper.Check(); + ALHelper.Check(sound?.FilePath); return sourceIndex; } @@ -165,7 +165,7 @@ namespace Barotrauma.Sounds return; AL.SourcePause(alSources[sourceIndex]); - ALHelper.Check(); + ALHelper.Check(soundsPlaying[sourceIndex]?.FilePath); } public static void Resume(int sourceIndex) @@ -176,7 +176,7 @@ namespace Barotrauma.Sounds return; AL.SourcePlay(alSources[sourceIndex]); - ALHelper.Check(); + ALHelper.Check(soundsPlaying[sourceIndex]?.FilePath); } public static void Stop(int sourceIndex) @@ -291,7 +291,7 @@ namespace Barotrauma.Sounds ALHelper.Efx.Filter(lowpassFilterId, OpenTK.Audio.OpenAL.EfxFilterf.LowpassGainHF, lowPassHfGain = value); ALHelper.Efx.BindFilterToSource(alSources[i], lowpassFilterId); - ALHelper.Check(); + ALHelper.Check(soundsPlaying[i]?.FilePath); } } } @@ -316,7 +316,7 @@ namespace Barotrauma.Sounds ALHelper.Efx.Filter(lowpassFilterId, OpenTK.Audio.OpenAL.EfxFilterf.LowpassGainHF, lowPassGain); ALHelper.Efx.BindFilterToSource(alSources[sourceIndex], lowpassFilterId); - ALHelper.Check(); + ALHelper.Check(soundsPlaying[sourceIndex]?.FilePath); } public static OggStream StartStream(string file, float volume = 1.0f) @@ -331,7 +331,7 @@ namespace Barotrauma.Sounds oggStream.Play(volume); - ALHelper.Check(); + ALHelper.Check(file); return oggStream; } @@ -361,15 +361,15 @@ namespace Barotrauma.Sounds for (int i = 0; i < DefaultSourceCount; i++) { + string soundPath = soundsPlaying[i]?.FilePath; var state = OpenTK.Audio.OpenAL.AL.GetSourceState(alSources[i]); if (state == OpenTK.Audio.OpenAL.ALSourceState.Playing || state == OpenTK.Audio.OpenAL.ALSourceState.Paused) { Stop(i); } - OpenTK.Audio.OpenAL.AL.DeleteSource(alSources[i]); - - ALHelper.Check(); + OpenTK.Audio.OpenAL.AL.DeleteSource(alSources[i]); + ALHelper.Check(soundPath); } if (oggStream != null) From faf0a87cfa562499d7dd72b3c1c50a08fd768142 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 20 Jul 2018 16:33:12 +0300 Subject: [PATCH 036/198] Added a bunch of error checks to physicsbody-manipulating code --- .../Source/Characters/Animation/Ragdoll.cs | 6 +- .../Source/Items/Components/DockingPort.cs | 20 ++++- .../Source/Items/Components/Door.cs | 9 ++ .../Source/Physics/PhysicsBody.cs | 84 ++++++++++++++++--- 4 files changed, 100 insertions(+), 19 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs index 3456f0ccf..7d55f8c61 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs @@ -368,8 +368,8 @@ namespace Barotrauma foreach (var joint in LimbJoints) { - joint.BodyB.SetTransform( - joint.BodyA.Position + (joint.LocalAnchorA - joint.LocalAnchorB)*0.1f, + joint.LimbB?.body?.SetTransform( + joint.BodyA.Position + (joint.LocalAnchorA - joint.LocalAnchorB) * 0.1f, (joint.LowerLimit + joint.UpperLimit) / 2.0f); } @@ -394,7 +394,7 @@ namespace Barotrauma Limb torso = GetLimb(LimbType.Torso); Limb head = GetLimb(LimbType.Head); - MainLimb = torso == null ? head : torso; + MainLimb = torso ?? head; } public void AddJoint(XElement subElement, float scale = 1.0f) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs index 236a2c85d..63108d2ea 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs @@ -345,17 +345,31 @@ namespace Barotrauma.Items.Components private void CreateDoorBody() { + Vector2 position = ConvertUnits.ToSimUnits(item.Position + (dockingTarget.door.Item.WorldPosition - item.WorldPosition)); + if (!MathUtils.IsValid(position)) + { + string errorMsg = + "Attempted to create a door body at an invalid position (item pos: " + item.Position + + ", item world pos: " + item.WorldPosition + + ", docking target world pos: " + DockingTarget.door.Item.WorldPosition + ")\n" + Environment.StackTrace; + + DebugConsole.ThrowError(errorMsg); + GameAnalyticsManager.AddErrorEventOnce( + "DockingPort.CreateDoorBody:InvalidPosition", + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + errorMsg); + position = Vector2.Zero; + } + doorBody = BodyFactory.CreateRectangle(GameMain.World, dockingTarget.door.Body.width, dockingTarget.door.Body.height, 1.0f, + position, dockingTarget.door); doorBody.CollisionCategories = Physics.CollisionWall; doorBody.BodyType = BodyType.Static; - doorBody.SetTransform( - ConvertUnits.ToSimUnits(item.Position + (dockingTarget.door.Item.WorldPosition - item.WorldPosition)), - 0.0f); } private void CreateHull() diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs index b3c8a5b03..c86ce633a 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs @@ -386,6 +386,15 @@ namespace Barotrauma.Items.Components foreach (PhysicsBody body in bodies) { float diff = 0.0f; + if (!MathUtils.IsValid(body.SimPosition)) + { + DebugConsole.ThrowError("Failed to push a limb out of a doorway - position of the body (character \"" + c.Name + "\") is not valid (" + body.SimPosition + ")"); + GameAnalyticsManager.AddErrorEventOnce("PushCharactersAway:LimbPosInvalid", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Failed to push a character out of a doorway - position of the character \"" + c.Name + "\" is not valid (" + body.SimPosition + ")." + + " Removed: " + c.Removed + + " Remoteplayer: " + c.IsRemotePlayer); + continue; + } if (isHorizontal) { diff --git a/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs b/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs index 7ce46113a..4972e4f70 100644 --- a/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs +++ b/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs @@ -360,6 +360,44 @@ namespace Barotrauma this.radius = radius; } + private bool IsValidValue(float value, string valueName) + { + if (!MathUtils.IsValid(value)) + { + string errorMsg = + "Attempted to apply invalid " + valueName + + " to a physics body (userdata: " + UserData == null ? "null" : UserData.ToString() + + "), value: " + value + "\n" + Environment.StackTrace; + + DebugConsole.ThrowError(errorMsg); + GameAnalyticsManager.AddErrorEventOnce( + "PhysicsBody.SetPosition:InvalidPosition", + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + errorMsg); + return false; + } + return true; + } + + private bool IsValidValue(Vector2 value, string valueName) + { + if (!MathUtils.IsValid(value)) + { + string errorMsg = + "Attempted to apply invalid " + valueName + + " to a physics body (userdata: " + UserData == null ? "null" : UserData.ToString() + + "), value: " + value + "\n" + Environment.StackTrace; + + DebugConsole.ThrowError(errorMsg); + GameAnalyticsManager.AddErrorEventOnce( + "PhysicsBody.SetPosition:InvalidPosition", + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + errorMsg); + return false; + } + return true; + } + public void ResetDynamics() { body.ResetDynamics(); @@ -367,6 +405,7 @@ namespace Barotrauma public void ApplyLinearImpulse(Vector2 impulse) { + if (!IsValidValue(impulse, "impulse")) return; body.ApplyLinearImpulse(impulse); } @@ -375,6 +414,9 @@ namespace Barotrauma /// public void ApplyLinearImpulse(Vector2 impulse, float maxVelocity) { + if (!IsValidValue(impulse, "impulse")) return; + if (!IsValidValue(maxVelocity, "max velocity")) return; + float currSpeed = body.LinearVelocity.Length(); Vector2 velocityAddition = impulse / Mass; Vector2 newVelocity = body.LinearVelocity + velocityAddition; @@ -385,21 +427,26 @@ namespace Barotrauma public void ApplyLinearImpulse(Vector2 impulse, Vector2 point) { + if (!IsValidValue(impulse, "impulse")) return; body.ApplyLinearImpulse(impulse, point); } public void ApplyForce(Vector2 force) { + if (!IsValidValue(force, "force")) return; body.ApplyForce(force); } public void ApplyForce(Vector2 force, Vector2 point) { + if (!IsValidValue(force, "force")) return; + if (!IsValidValue(point, "point")) return; body.ApplyForce(force, point); } public void ApplyTorque(float torque) { + if (!IsValidValue(torque, "torque")) return; body.ApplyTorque(torque); } @@ -408,7 +455,10 @@ namespace Barotrauma System.Diagnostics.Debug.Assert(MathUtils.IsValid(simPosition)); System.Diagnostics.Debug.Assert(Math.Abs(simPosition.X) < 1000000.0f); System.Diagnostics.Debug.Assert(Math.Abs(simPosition.Y) < 1000000.0f); - + + if (!IsValidValue(simPosition, "position")) return; + if (!IsValidValue(rotation, "rotation")) return; + body.SetTransform(simPosition, rotation); SetPrevTransform(simPosition, rotation); } @@ -418,14 +468,20 @@ namespace Barotrauma System.Diagnostics.Debug.Assert(MathUtils.IsValid(simPosition)); System.Diagnostics.Debug.Assert(Math.Abs(simPosition.X) < 1000000.0f); System.Diagnostics.Debug.Assert(Math.Abs(simPosition.Y) < 1000000.0f); - + + if (!IsValidValue(simPosition, "position")) return; + if (!IsValidValue(rotation, "rotation")) return; + body.SetTransformIgnoreContacts(ref simPosition, rotation); SetPrevTransform(simPosition, rotation); } - public void SetPrevTransform(Vector2 position, float rotation) + public void SetPrevTransform(Vector2 simPosition, float rotation) { - prevPosition = position; + if (!IsValidValue(simPosition, "position")) return; + if (!IsValidValue(rotation, "rotation")) return; + + prevPosition = simPosition; prevRotation = rotation; } @@ -440,16 +496,19 @@ namespace Barotrauma prevPosition = (Vector2)targetPosition; } - body.SetTransform((Vector2)targetPosition, targetRotation == null ? body.Rotation : (float)targetRotation); + SetTransform((Vector2)targetPosition, targetRotation == null ? body.Rotation : (float)targetRotation); targetPosition = null; } - public void MoveToPos(Vector2 pos, float force, Vector2? pullPos = null) + public void MoveToPos(Vector2 simPosition, float force, Vector2? pullPos = null) { if (pullPos == null) pullPos = body.Position; + if (!IsValidValue(simPosition, "position")) return; + if (!IsValidValue(force, "force")) return; + Vector2 vel = body.LinearVelocity; - Vector2 deltaPos = pos - (Vector2)pullPos; + Vector2 deltaPos = simPosition - (Vector2)pullPos; deltaPos *= force; body.ApplyLinearImpulse((deltaPos - vel * 0.5f) * body.Mass, (Vector2)pullPos); } @@ -474,8 +533,8 @@ namespace Barotrauma dragForce = Math.Min(drag, Mass * 500.0f) * -velDir; } - body.ApplyForce(dragForce + buoyancy); - body.ApplyTorque(body.AngularVelocity * body.Mass * -0.08f); + ApplyForce(dragForce + buoyancy); + ApplyTorque(body.AngularVelocity * body.Mass * -0.08f); } @@ -559,18 +618,17 @@ namespace Barotrauma public void SmoothRotate(float targetRotation, float force = 10.0f) { float nextAngle = body.Rotation + body.AngularVelocity * (float)Timing.Step; - float angle = MathUtils.GetShortestAngle(nextAngle, targetRotation); - - float torque = angle * 60.0f * (force/100.0f); + float torque = angle * 60.0f * (force / 100.0f); if (body.IsKinematic) { + if (!IsValidValue(torque, "torque")) return; body.AngularVelocity = torque; } else { - body.ApplyTorque(body.Mass * torque); + ApplyTorque(body.Mass * torque); } } From 9880a74218edd2699f7bb3dc5487514dee4d81cf Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 20 Jul 2018 16:37:41 +0300 Subject: [PATCH 037/198] Added check for removed AITarget to EnemyAIController.UpdateEscape --- .../Source/Characters/AI/EnemyAIController.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs index 495add3f1..72b956137 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs @@ -274,6 +274,12 @@ namespace Barotrauma private void UpdateEscape(float deltaTime) { + if (selectedAiTarget == null || selectedAiTarget.Entity == null || selectedAiTarget.Entity.Removed) + { + state = AIState.None; + return; + } + SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(SimPosition - selectedAiTarget.SimPosition) * 5); SteeringManager.SteeringWander(1.0f); SteeringManager.SteeringAvoid(deltaTime, 2f); From cac1c48aa37fde5c0b41def5f87b191acbe24e3b Mon Sep 17 00:00:00 2001 From: Nilanth Animosus Date: Fri, 20 Jul 2018 18:01:38 +0100 Subject: [PATCH 038/198] Redesigned campaign buymenu to use a quantity field Additionally sorts the bought items by name then type to attempt to mirror the tabs (Not perfect) --- .../GameModes/MultiPlayerCampaign.cs | 10 +- .../Source/Screens/CampaignUI.cs | 90 ++++++++---- .../Source/GameSession/CargoManager.cs | 136 +++++++++++------- .../GameModes/MultiPlayerCampaign.cs | 20 +-- .../Source/Networking/GameServer.cs | 7 +- 5 files changed, 167 insertions(+), 96 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/MultiPlayerCampaign.cs index 66b46f085..30e711662 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/MultiPlayerCampaign.cs @@ -92,9 +92,10 @@ namespace Barotrauma msg.Write(map.SelectedLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.SelectedLocationIndex); msg.Write((UInt16)CargoManager.PurchasedItems.Count); - foreach (ItemPrefab ip in CargoManager.PurchasedItems) + foreach (PurchasedItem pi in CargoManager.PurchasedItems) { - msg.Write((UInt16)MapEntityPrefab.List.IndexOf(ip)); + msg.Write((UInt16)MapEntityPrefab.List.IndexOf(pi.itemPrefab)); + msg.Write((UInt16)pi.quantity); } } @@ -111,11 +112,12 @@ namespace Barotrauma int money = msg.ReadInt32(); UInt16 purchasedItemCount = msg.ReadUInt16(); - List purchasedItems = new List(); + List purchasedItems = new List(); for (int i = 0; i < purchasedItemCount; i++) { UInt16 itemPrefabIndex = msg.ReadUInt16(); - purchasedItems.Add(MapEntityPrefab.List[itemPrefabIndex] as ItemPrefab); + UInt16 itemQuantity = msg.ReadUInt16(); + purchasedItems.Add(new PurchasedItem(MapEntityPrefab.List[itemPrefabIndex] as ItemPrefab, itemQuantity)); } MultiPlayerCampaign campaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign; diff --git a/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs b/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs index 1e821b151..6ac7b4783 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs @@ -85,7 +85,7 @@ namespace Barotrauma int sellColumnWidth = (tabs[(int)Tab.Store].Rect.Width - 40) / 2 - 20; selectedItemList = new GUIListBox(new Rectangle(0, 30, sellColumnWidth, tabs[(int)Tab.Store].Rect.Height - 80), Color.White * 0.7f, "", tabs[(int)Tab.Store]); - selectedItemList.OnSelected = SellItem; + //selectedItemList.OnSelected = SellItem; storeItemList = new GUIListBox(new Rectangle(0, 30, sellColumnWidth, tabs[(int)Tab.Store].Rect.Height - 80), Color.White * 0.7f, Alignment.TopRight, "", tabs[(int)Tab.Store]); storeItemList.OnSelected = BuyItem; @@ -218,55 +218,91 @@ namespace Barotrauma OnLocationSelected?.Invoke(location, connection); } - private void CreateItemFrame(MapEntityPrefab ep, GUIListBox listBox, int width) + private void CreateItemFrame(PurchasedItem pi, GUIListBox listBox, int width) { GUIFrame frame = new GUIFrame(new Rectangle(0, 0, 0, 50), "ListBoxElement", listBox); - frame.UserData = ep; + frame.UserData = pi; frame.Padding = new Vector4(5.0f, 5.0f, 5.0f, 5.0f); - frame.ToolTip = ep.Description; + frame.ToolTip = pi.itemPrefab.Description; ScalableFont font = listBox.Rect.Width < 280 ? GUI.SmallFont : GUI.Font; GUITextBlock textBlock = new GUITextBlock( new Rectangle(50, 0, 0, 25), - ep.Name, + pi.itemPrefab.Name, null, null, Alignment.Left, Alignment.CenterX | Alignment.Left, "", frame); textBlock.Font = font; textBlock.Padding = new Vector4(5.0f, 0.0f, 5.0f, 0.0f); - textBlock.ToolTip = ep.Description; + textBlock.ToolTip = pi.itemPrefab.Description; - if (ep.sprite != null) + if (pi.itemPrefab.sprite != null) { - GUIImage img = new GUIImage(new Rectangle(0, 0, 40, 40), ep.sprite, Alignment.CenterLeft, frame); - img.Color = ep.SpriteColor; + GUIImage img = new GUIImage(new Rectangle(0, 0, 40, 40), pi.itemPrefab.sprite, Alignment.CenterLeft, frame); + img.Color = pi.itemPrefab.SpriteColor; img.Scale = Math.Min(Math.Min(40.0f / img.SourceRect.Width, 40.0f / img.SourceRect.Height), 1.0f); } textBlock = new GUITextBlock( - new Rectangle(width - 80, 0, 80, 25), - ep.Price.ToString(), + new Rectangle(width - 160, 0, 80, 25), + pi.itemPrefab.Price.ToString(), null, null, Alignment.TopLeft, Alignment.TopLeft, "", frame); textBlock.Font = font; - textBlock.ToolTip = ep.Description; + textBlock.ToolTip = pi.itemPrefab.Description; + + //If its the store menu, quantity will always be 0 + if (pi.quantity > 0) + { + var amountInput = new GUINumberInput(new Rectangle(width - 80, 0, 50, 40), "", GUINumberInput.NumberType.Int, frame); + amountInput.MinValueInt = 0; + amountInput.MaxValueInt = 1000; + amountInput.UserData = pi; + amountInput.IntValue = pi.quantity; + amountInput.OnValueChanged += (numberInput) => + { + PurchasedItem purchasedItem = numberInput.UserData as PurchasedItem; + + //Attempting to buy + if (numberInput.IntValue > purchasedItem.quantity) + { + int quantity = numberInput.IntValue - purchasedItem.quantity; + //Cap the numberbox based on the amount we can afford. + quantity = Math.Max((quantity * (purchasedItem.itemPrefab.Price / Campaign.Money)), quantity); + for (int i = 0; i < quantity; i++) + { + BuyItem(numberInput, purchasedItem); + } + numberInput.IntValue = purchasedItem.quantity; + } + //Attempting to sell + else + { + int quantity = purchasedItem.quantity - numberInput.IntValue; + for (int i = 0; i < quantity; i++) + { + SellItem(numberInput, purchasedItem); + } + } + }; + } } private bool BuyItem(GUIComponent component, object obj) { - ItemPrefab prefab = obj as ItemPrefab; - if (prefab == null) return false; + PurchasedItem pi = obj as PurchasedItem; + if (pi == null || pi.itemPrefab == null) return false; if (GameMain.Client != null && !GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign)) { return false; } - if (prefab.Price > campaign.Money) return false; + if (pi.itemPrefab.Price > campaign.Money) return false; - campaign.CargoManager.PurchaseItem(prefab); + campaign.CargoManager.PurchaseItem(pi.itemPrefab, 1); GameMain.Client?.SendCampaignState(); return false; @@ -274,15 +310,15 @@ namespace Barotrauma private bool SellItem(GUIComponent component, object obj) { - ItemPrefab prefab = obj as ItemPrefab; - if (prefab == null) return false; + PurchasedItem pi = obj as PurchasedItem; + if (pi == null || pi.itemPrefab == null) return false; if (GameMain.Client != null && !GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign)) { return false; } - campaign.CargoManager.SellItem(prefab); + campaign.CargoManager.SellItem(pi.itemPrefab,1); GameMain.Client?.SendCampaignState(); return false; @@ -291,10 +327,12 @@ namespace Barotrauma private void RefreshItemTab() { selectedItemList.ClearChildren(); - foreach (ItemPrefab ip in campaign.CargoManager.PurchasedItems) + foreach (PurchasedItem pi in campaign.CargoManager.PurchasedItems) { - CreateItemFrame(ip, selectedItemList, selectedItemList.Rect.Width); - } + CreateItemFrame(pi, selectedItemList, selectedItemList.Rect.Width); + } + selectedItemList.children.Sort((x, y) => (x.UserData as PurchasedItem).itemPrefab.Name.CompareTo((y.UserData as PurchasedItem).itemPrefab.Name)); + selectedItemList.children.Sort((x, y) => (x.UserData as PurchasedItem).itemPrefab.Category.CompareTo((y.UserData as PurchasedItem).itemPrefab.Category)); selectedItemList.UpdateScrollBarSize(); } @@ -314,16 +352,16 @@ namespace Barotrauma storeItemList.ClearChildren(); MapEntityCategory category = (MapEntityCategory)selection; - var items = MapEntityPrefab.List.FindAll(ep => ep.Price > 0.0f && ep.Category.HasFlag(category)); + var items = MapEntityPrefab.List.FindAll(ep => ep.Price > 0.0f && ep.Category.HasFlag(category) && ep is ItemPrefab); int width = storeItemList.Rect.Width; - foreach (MapEntityPrefab ep in items) + foreach (ItemPrefab ep in items) { - CreateItemFrame(ep, storeItemList, width); + CreateItemFrame(new PurchasedItem((ItemPrefab)ep,0), storeItemList, width); } - storeItemList.children.Sort((x, y) => (x.UserData as MapEntityPrefab).Name.CompareTo((y.UserData as MapEntityPrefab).Name)); + storeItemList.children.Sort((x, y) => (x.UserData as PurchasedItem).itemPrefab.Name.CompareTo((y.UserData as PurchasedItem).itemPrefab.Name)); foreach (GUIComponent child in button.Parent.children) { diff --git a/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs b/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs index 580321792..24b1c9c03 100644 --- a/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs +++ b/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs @@ -6,26 +6,38 @@ using System.Linq; namespace Barotrauma { + class PurchasedItem + { + public ItemPrefab itemPrefab; + public int quantity; + + public PurchasedItem(ItemPrefab itemPrefab, int quantity) + { + this.itemPrefab = itemPrefab; + this.quantity = quantity; + } + } + class CargoManager { - private readonly List purchasedItems; + private readonly List purchasedItems; private readonly CampaignMode campaign; public Action OnItemsChanged; - public List PurchasedItems + public List PurchasedItems { get { return purchasedItems; } } public CargoManager(CampaignMode campaign) { - purchasedItems = new List(); + purchasedItems = new List(); this.campaign = campaign; } - public void SetPurchasedItems(List items) + public void SetPurchasedItems(List items) { purchasedItems.Clear(); purchasedItems.AddRange(items); @@ -33,25 +45,44 @@ namespace Barotrauma OnItemsChanged?.Invoke(); } - public void PurchaseItem(ItemPrefab item) + public void PurchaseItem(ItemPrefab item, int Quantity = 1) { - campaign.Money -= item.Price; - purchasedItems.Add(item); + PurchasedItem purchasedItem = PurchasedItems.Find(pi => pi.itemPrefab == item); + + if(purchasedItem != null && Quantity == 1) + { + campaign.Money -= item.Price; + purchasedItem.quantity += 1; + } + else + { + campaign.Money -= (item.Price * Quantity); + purchasedItem = new PurchasedItem(item, Quantity); + purchasedItems.Add(purchasedItem); + } OnItemsChanged?.Invoke(); } - public void SellItem(ItemPrefab item) + public void SellItem(ItemPrefab item, int quantity = 1) { - campaign.Money += item.Price; - purchasedItems.Remove(item); + campaign.Money += (item.Price * quantity); + PurchasedItem purchasedItem = PurchasedItems.Find(pi => pi.itemPrefab == item); + if (purchasedItem != null && purchasedItem.quantity - quantity > 0) + { + purchasedItem.quantity -= quantity; + } + else + { + PurchasedItems.Remove(purchasedItem); + } OnItemsChanged?.Invoke(); } public int GetTotalItemCost() { - return purchasedItems.Sum(i => i.Price); + return purchasedItems.Sum(i => (i.itemPrefab.Price * i.quantity)); } public void CreateItems() @@ -60,7 +91,7 @@ namespace Barotrauma OnItemsChanged?.Invoke(); } - public static void CreateItems(List itemsToSpawn) + public static void CreateItems(List itemsToSpawn) { WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub); @@ -79,24 +110,24 @@ namespace Barotrauma } Dictionary availableContainers = new Dictionary(); - foreach (ItemPrefab prefab in itemsToSpawn) + foreach (PurchasedItem Pi in itemsToSpawn) { Vector2 position = new Vector2( Rand.Range(cargoRoom.Rect.X + 20, cargoRoom.Rect.Right - 20), - cargoRoom.Rect.Y - cargoRoom.Rect.Height + prefab.Size.Y / 2); + cargoRoom.Rect.Y - cargoRoom.Rect.Height + Pi.itemPrefab.Size.Y / 2); ItemContainer itemContainer = null; - if (!string.IsNullOrEmpty(prefab.CargoContainerName)) + if (!string.IsNullOrEmpty(Pi.itemPrefab.CargoContainerName)) { itemContainer = availableContainers.Keys.ToList().Find(ac => - ac.Item.Prefab.NameMatches(prefab.CargoContainerName) || - ac.Item.Prefab.Tags.Contains(prefab.CargoContainerName.ToLowerInvariant())); + ac.Item.Prefab.NameMatches(Pi.itemPrefab.CargoContainerName) || + ac.Item.Prefab.Tags.Contains(Pi.itemPrefab.CargoContainerName.ToLowerInvariant())); if (itemContainer == null) { var containerPrefab = MapEntityPrefab.List.Find(ep => - ep.NameMatches(prefab.CargoContainerName) || - (ep.Tags != null && ep.Tags.Contains(prefab.CargoContainerName.ToLowerInvariant()))) as ItemPrefab; + ep.NameMatches(Pi.itemPrefab.CargoContainerName) || + (ep.Tags != null && ep.Tags.Contains(Pi.itemPrefab.CargoContainerName.ToLowerInvariant()))) as ItemPrefab; if (containerPrefab == null) { @@ -118,41 +149,42 @@ namespace Barotrauma } } } - - if (itemContainer == null) - { - //no container, place at the waypoint - if (GameMain.Server != null) - { - Entity.Spawner.AddToSpawnQueue(prefab, position, wp.Submarine); - } - else - { - new Item(prefab, position, wp.Submarine); - } - } - else - { - //place in the container - if (GameMain.Server != null) - { - Entity.Spawner.AddToSpawnQueue(prefab, itemContainer.Inventory); - } - else - { - var item = new Item(prefab, position, wp.Submarine); - itemContainer.Inventory.TryPutItem(item, null); - } - - //reduce the number of available slots in the container - availableContainers[itemContainer]--; - if (availableContainers[itemContainer] <= 0) - { - availableContainers.Remove(itemContainer); - } + for (int i = 0; i < Pi.quantity; i++) + { + if (itemContainer == null) + { + //no container, place at the waypoint + if (GameMain.Server != null) + { + Entity.Spawner.AddToSpawnQueue(Pi.itemPrefab, position, wp.Submarine); + } + else + { + new Item(Pi.itemPrefab, position, wp.Submarine); + } + } + else + { + //place in the container + if (GameMain.Server != null) + { + Entity.Spawner.AddToSpawnQueue(Pi.itemPrefab, itemContainer.Inventory); + } + else + { + var item = new Item(Pi.itemPrefab, position, wp.Submarine); + itemContainer.Inventory.TryPutItem(item, null); + } + + //reduce the number of available slots in the container + availableContainers[itemContainer]--; + if (availableContainers[itemContainer] <= 0) + { + availableContainers.Remove(itemContainer); + } + } } } - itemsToSpawn.Clear(); } } diff --git a/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/MultiPlayerCampaign.cs index 54155e44a..35cfdea93 100644 --- a/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/MultiPlayerCampaign.cs @@ -194,9 +194,10 @@ namespace Barotrauma msg.Write(Money); msg.Write((UInt16)CargoManager.PurchasedItems.Count); - foreach (ItemPrefab ip in CargoManager.PurchasedItems) + foreach (PurchasedItem pi in CargoManager.PurchasedItems) { - msg.Write((UInt16)MapEntityPrefab.List.IndexOf(ip)); + msg.Write((UInt16)MapEntityPrefab.List.IndexOf(pi.itemPrefab)); + msg.Write((UInt16)pi.quantity); } } @@ -205,11 +206,12 @@ namespace Barotrauma UInt16 selectedLocIndex = msg.ReadUInt16(); UInt16 purchasedItemCount = msg.ReadUInt16(); - List purchasedItems = new List(); + List purchasedItems = new List(); for (int i = 0; i < purchasedItemCount; i++) { UInt16 itemPrefabIndex = msg.ReadUInt16(); - purchasedItems.Add(MapEntityPrefab.List[itemPrefabIndex] as ItemPrefab); + UInt16 itemQuantity = msg.ReadUInt16(); + purchasedItems.Add(new PurchasedItem(MapEntityPrefab.List[itemPrefabIndex] as ItemPrefab, itemQuantity)); } if (!sender.HasPermission(ClientPermissions.ManageCampaign)) @@ -220,15 +222,15 @@ namespace Barotrauma Map.SelectLocation(selectedLocIndex == UInt16.MaxValue ? -1 : selectedLocIndex); - List currentItems = new List(CargoManager.PurchasedItems); - foreach (ItemPrefab ip in currentItems) + List currentItems = new List(CargoManager.PurchasedItems); + foreach (PurchasedItem pi in currentItems) { - CargoManager.SellItem(ip); + CargoManager.SellItem(pi.itemPrefab, pi.quantity); } - foreach (ItemPrefab ip in purchasedItems) + foreach (PurchasedItem pi in purchasedItems) { - CargoManager.PurchaseItem(ip); + CargoManager.PurchaseItem(pi.itemPrefab, pi.quantity); } } } diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs index a0da30a20..6cd095e1a 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs @@ -1334,13 +1334,10 @@ namespace Barotrauma.Networking { if (sub == null) continue; - List spawnList = new List(); + List spawnList = new List(); foreach (KeyValuePair kvp in extraCargo) { - for (int i = 0; i < kvp.Value; i++) - { - spawnList.Add(kvp.Key); - } + spawnList.Add(new PurchasedItem(kvp.Key, kvp.Value)); } CargoManager.CreateItems(spawnList); From 5177c5b1ebec070aa568588f19a525ad2e99598a Mon Sep 17 00:00:00 2001 From: Nilanth Animosus Date: Sat, 21 Jul 2018 18:38:27 +0100 Subject: [PATCH 039/198] Fixed clients being unable to see vote counts The clients would check their saved submarines instead of the submarines the server had when receiving the vote count. --- Barotrauma/BarotraumaClient/Source/Networking/Voting.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaClient/Source/Networking/Voting.cs b/Barotrauma/BarotraumaClient/Source/Networking/Voting.cs index 6b41a5dac..0b30e3d84 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/Voting.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/Voting.cs @@ -152,7 +152,12 @@ namespace Barotrauma { int votes = inc.ReadByte(); string subName = inc.ReadString(); - Submarine sub = Submarine.SavedSubmarines.FirstOrDefault(sm => sm.Name == subName); + List serversubs = new List(); + foreach (GUIComponent item in GameMain.NetLobbyScreen?.SubList?.children) + { + if (item.UserData != null && item.UserData is Submarine) serversubs.Add(item.UserData as Submarine); + } + Submarine sub = serversubs.FirstOrDefault(sm => sm.Name == subName); SetVoteText(GameMain.NetLobbyScreen.SubList, sub, votes); } } From 5ebfde72de94be9f459e3a09210c5589e5a4cf27 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Sun, 22 Jul 2018 16:00:32 +0300 Subject: [PATCH 040/198] Fixed ragdoll collision categories not being updated when CheckDistFromCollider re-enables limb collisions. Fixes #508 --- .../BarotraumaShared/Source/Characters/Animation/Ragdoll.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs index 7d55f8c61..011f19a5b 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs @@ -1265,6 +1265,8 @@ namespace Barotrauma //set the position of the ragdoll to make sure limbs don't get stuck inside walls when re-enabling collisions SetPosition(Collider.SimPosition, true); collisionsDisabled = false; + //force collision categories to be updated + prevCollisionCategory = Category.None; } } From bd845488d78e265af78da2272f980c9ff2a9ff9a Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Sun, 22 Jul 2018 17:40:17 +0300 Subject: [PATCH 041/198] Added exception handling to MainMenuScreen.StartGame --- .../Source/Screens/MainMenuScreen.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs index f375ee30d..8804d17a6 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs @@ -368,7 +368,19 @@ namespace Barotrauma Directory.CreateDirectory(SaveUtil.TempPath); } - File.Copy(selectedSub.FilePath, Path.Combine(SaveUtil.TempPath, selectedSub.Name + ".sub"), true); + try + { + File.Copy(selectedSub.FilePath, Path.Combine(SaveUtil.TempPath, selectedSub.Name + ".sub"), true); + } + catch (IOException e) + { + DebugConsole.ThrowError("Copying the file \"" + selectedSub.FilePath + "\" failed. The file may have been deleted or in use by another process. Try again or select another submarine.", e); + GameAnalyticsManager.AddErrorEventOnce( + "MainMenuScreen.StartGame:IOException" + selectedSub.Name, + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Copying the file \"" + selectedSub.FilePath + "\" failed.\n" + e.Message + "\n" + Environment.StackTrace); + return; + } selectedSub = new Submarine(Path.Combine(SaveUtil.TempPath, selectedSub.Name + ".sub"), ""); From 0728784b8bd31b2c7a510ee40a9fca7d05649aff Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Sun, 22 Jul 2018 17:50:29 +0300 Subject: [PATCH 042/198] Added invalid value checks to PhysicsBody property setters & item.SetTransform, checking for excessively large values --- .../BarotraumaShared/Source/Items/Item.cs | 14 +++++ .../Source/Physics/PhysicsBody.cs | 57 ++++++++++++------- 2 files changed, 49 insertions(+), 22 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index 4e3d6f8e4..42fb6979a 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -532,6 +532,20 @@ namespace Barotrauma public void SetTransform(Vector2 simPosition, float rotation, bool findNewHull = true) { + if (!MathUtils.IsValid(simPosition)) + { + string errorMsg = + "Attempted to move the item " + Name + + " to an invalid position (" + simPosition + ")\n" + Environment.StackTrace; + + DebugConsole.ThrowError(errorMsg); + GameAnalyticsManager.AddErrorEventOnce( + "Item.SetPosition:InvalidPosition" + ID, + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + errorMsg); + return; + } + if (body != null) { try diff --git a/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs b/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs index 4972e4f70..fd71f11a2 100644 --- a/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs +++ b/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs @@ -125,8 +125,7 @@ namespace Barotrauma } else { - if (!MathUtils.IsValid((Vector2)value)) return; - + if (!IsValidValue(value.Value, "target position", -1e5f, 1e5f)) return; targetPosition = new Vector2( MathHelper.Clamp(((Vector2)value).X, -10000.0f, 10000.0f), MathHelper.Clamp(((Vector2)value).Y, -10000.0f, 10000.0f)); @@ -145,7 +144,7 @@ namespace Barotrauma } else { - if (!MathUtils.IsValid((float)value)) return; + if (!IsValidValue(value.Value, "target rotation")) return; targetRotation = value; } @@ -223,13 +222,21 @@ namespace Barotrauma public Vector2 LinearVelocity { get { return body.LinearVelocity; } - set { body.LinearVelocity = value; } + set + { + if (!IsValidValue(value, "velocity", -1000.0f, 1000.0f)) return; + body.LinearVelocity = value; + } } public float AngularVelocity { get { return body.AngularVelocity; } - set { body.AngularVelocity = value; } + set + { + if (!IsValidValue(value, "angular velocity")) return; + body.AngularVelocity = value; + } } public float Mass @@ -360,18 +367,21 @@ namespace Barotrauma this.radius = radius; } - private bool IsValidValue(float value, string valueName) + public bool IsValidValue(float value, string valueName, float? minValue = null, float? maxValue = null) { - if (!MathUtils.IsValid(value)) + if (!MathUtils.IsValid(value) || + (minValue.HasValue && value < minValue.Value) || + (maxValue.HasValue && value > maxValue.Value)) { + string userData = UserData == null ? "null" : UserData.ToString(); string errorMsg = "Attempted to apply invalid " + valueName + - " to a physics body (userdata: " + UserData == null ? "null" : UserData.ToString() + + " to a physics body (userdata: " + userData + "), value: " + value + "\n" + Environment.StackTrace; DebugConsole.ThrowError(errorMsg); GameAnalyticsManager.AddErrorEventOnce( - "PhysicsBody.SetPosition:InvalidPosition", + "PhysicsBody.SetPosition:InvalidPosition" + userData, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); return false; @@ -379,18 +389,21 @@ namespace Barotrauma return true; } - private bool IsValidValue(Vector2 value, string valueName) + private bool IsValidValue(Vector2 value, string valueName, float? minValue = null, float? maxValue = null) { - if (!MathUtils.IsValid(value)) + if (!MathUtils.IsValid(value) || + (minValue.HasValue && (value.X < minValue.Value || value.Y < minValue.Value)) || + (maxValue.HasValue && (value.X > maxValue.Value || value.Y > maxValue))) { + string userData = UserData == null ? "null" : UserData.ToString(); string errorMsg = "Attempted to apply invalid " + valueName + - " to a physics body (userdata: " + UserData == null ? "null" : UserData.ToString() + + " to a physics body (userdata: " + userData + "), value: " + value + "\n" + Environment.StackTrace; DebugConsole.ThrowError(errorMsg); GameAnalyticsManager.AddErrorEventOnce( - "PhysicsBody.SetPosition:InvalidPosition", + "PhysicsBody.SetPosition:InvalidPosition" + userData, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); return false; @@ -405,7 +418,7 @@ namespace Barotrauma public void ApplyLinearImpulse(Vector2 impulse) { - if (!IsValidValue(impulse, "impulse")) return; + if (!IsValidValue(impulse, "impulse", -1e10f, 1e10f)) return; body.ApplyLinearImpulse(impulse); } @@ -414,7 +427,7 @@ namespace Barotrauma /// public void ApplyLinearImpulse(Vector2 impulse, float maxVelocity) { - if (!IsValidValue(impulse, "impulse")) return; + if (!IsValidValue(impulse, "impulse", -1e10f, 1e10f)) return; if (!IsValidValue(maxVelocity, "max velocity")) return; float currSpeed = body.LinearVelocity.Length(); @@ -427,19 +440,19 @@ namespace Barotrauma public void ApplyLinearImpulse(Vector2 impulse, Vector2 point) { - if (!IsValidValue(impulse, "impulse")) return; + if (!IsValidValue(impulse, "impulse", -1e10f, 1e10f)) return; body.ApplyLinearImpulse(impulse, point); } public void ApplyForce(Vector2 force) { - if (!IsValidValue(force, "force")) return; + if (!IsValidValue(force, "force", -1e10f, 1e10f)) return; body.ApplyForce(force); } public void ApplyForce(Vector2 force, Vector2 point) { - if (!IsValidValue(force, "force")) return; + if (!IsValidValue(force, "force", -1e10f, 1e10f)) return; if (!IsValidValue(point, "point")) return; body.ApplyForce(force, point); } @@ -456,7 +469,7 @@ namespace Barotrauma System.Diagnostics.Debug.Assert(Math.Abs(simPosition.X) < 1000000.0f); System.Diagnostics.Debug.Assert(Math.Abs(simPosition.Y) < 1000000.0f); - if (!IsValidValue(simPosition, "position")) return; + if (!IsValidValue(simPosition, "position", -1e10f, 1e10f)) return; if (!IsValidValue(rotation, "rotation")) return; body.SetTransform(simPosition, rotation); @@ -469,7 +482,7 @@ namespace Barotrauma System.Diagnostics.Debug.Assert(Math.Abs(simPosition.X) < 1000000.0f); System.Diagnostics.Debug.Assert(Math.Abs(simPosition.Y) < 1000000.0f); - if (!IsValidValue(simPosition, "position")) return; + if (!IsValidValue(simPosition, "position", -1e10f, 1e10f)) return; if (!IsValidValue(rotation, "rotation")) return; body.SetTransformIgnoreContacts(ref simPosition, rotation); @@ -478,7 +491,7 @@ namespace Barotrauma public void SetPrevTransform(Vector2 simPosition, float rotation) { - if (!IsValidValue(simPosition, "position")) return; + if (!IsValidValue(simPosition, "position", -1e10f, 1e10f)) return; if (!IsValidValue(rotation, "rotation")) return; prevPosition = simPosition; @@ -504,7 +517,7 @@ namespace Barotrauma { if (pullPos == null) pullPos = body.Position; - if (!IsValidValue(simPosition, "position")) return; + if (!IsValidValue(simPosition, "position", -1e10f, 1e10f)) return; if (!IsValidValue(force, "force")) return; Vector2 vel = body.LinearVelocity; From 78a5dcbfe91b9b1254e11977deccbd6d21d71c6d Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Sun, 22 Jul 2018 17:58:52 +0300 Subject: [PATCH 043/198] Fixed nullref exception in CharacterInvetory.UseItemOnSelf if the OnUse statuseffect removes the item. Closes #513 --- .../BarotraumaShared/Source/Items/CharacterInventory.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Barotrauma/BarotraumaShared/Source/Items/CharacterInventory.cs b/Barotrauma/BarotraumaShared/Source/Items/CharacterInventory.cs index ab3dedaec..4ee83f7f4 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/CharacterInventory.cs @@ -50,6 +50,10 @@ namespace Barotrauma } Items[slotIndex].ApplyStatusEffects(ActionType.OnUse, 1.0f, character); + + //item may have been removed by a status effect + if (Items[slotIndex] == null) return true; + foreach (ItemComponent ic in Items[slotIndex].components) { if (ic.DeleteOnUse) From 41cae8d3c5a5b7b2b8e605c137033e613838071b Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Sun, 22 Jul 2018 17:59:13 +0300 Subject: [PATCH 044/198] Added null check to Sound.StreamVolume --- Barotrauma/BarotraumaClient/Source/Sounds/Sound.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaClient/Source/Sounds/Sound.cs b/Barotrauma/BarotraumaClient/Source/Sounds/Sound.cs index db4563b2e..e9bfdcfb8 100644 --- a/Barotrauma/BarotraumaClient/Source/Sounds/Sound.cs +++ b/Barotrauma/BarotraumaClient/Source/Sounds/Sound.cs @@ -246,7 +246,7 @@ namespace Barotrauma public static void StreamVolume(float volume = 1.0f) { - if (SoundManager.Disabled) return; + if (SoundManager.Disabled || stream == null) return; stream.Volume = volume; } From 7b5ab2f7959a68b2e3e6a25ede621cad962e0bf4 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Sun, 22 Jul 2018 20:48:56 +0300 Subject: [PATCH 045/198] v0.8.1.8 --- .../BarotraumaClient/Properties/AssemblyInfo.cs | 4 ++-- .../BarotraumaServer/Properties/AssemblyInfo.cs | 4 ++-- Barotrauma/BarotraumaShared/changelog.txt | 16 ++++++++++++++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs index f6e89a9a8..d6964bf4f 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.7")] -[assembly: AssemblyFileVersion("0.8.1.7")] +[assembly: AssemblyVersion("0.8.1.8")] +[assembly: AssemblyFileVersion("0.8.1.8")] diff --git a/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs index da903abca..8467bf18d 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.7")] -[assembly: AssemblyFileVersion("0.8.1.7")] +[assembly: AssemblyVersion("0.8.1.8")] +[assembly: AssemblyFileVersion("0.8.1.8")] diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index ab208f888..ebd918174 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,19 @@ +--------------------------------------------------------------------------------------------------------- +v0.8.1.8 +--------------------------------------------------------------------------------------------------------- + +- Fixed "invalid cursor position" errors when a client attempts to use console commands in the server lobby. +- Fixed crashing if a character takes damage during the first frame of a round. +- Fixed crashing if suitable prop items are not found when generating alien ruins. +- Some ragdoll and physics optimization. +- More automatic error logging to help with debugging. +- Fixed errors when attempting to change the output type of an oscillator in multiplayer. +- Campaign store menu includes a quantity field which lets the player select how many items to buy instead +of showing each item individually. +- Fixed clients not seeing vote counts on submarines that the client doesn't have. +- Fixed crashing if a player attempts to use an item on themselves and the item has a status effect that +causes the item to be removed. + --------------------------------------------------------------------------------------------------------- v0.8.1.7 --------------------------------------------------------------------------------------------------------- From c8cdc5d58cefac4b56476919a277da6c747cb099 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 23 Jul 2018 16:59:09 +0300 Subject: [PATCH 046/198] Fixed console errors when entering '\' or '\n' --- Barotrauma/BarotraumaShared/Source/DebugConsole.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs index 1ad87c716..7ada53b2d 100644 --- a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs @@ -1895,12 +1895,12 @@ namespace Barotrauma return; } - if (string.IsNullOrWhiteSpace(command)) return; + if (string.IsNullOrWhiteSpace(command) || command == "\\" || command == "\n") return; string[] splitCommand = SplitCommand(command); if (splitCommand.Length == 0) { - DebugConsole.ThrowError("Failed to execute command \"" + command + "\"!"); + ThrowError("Failed to execute command \"" + command + "\"!"); GameAnalyticsManager.AddErrorEventOnce( "DebugConsole.ExecuteCommand:LengthZero", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, From ee13ea397241dd0c58b116e60406fa35555d5b3d Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 23 Jul 2018 17:40:40 +0300 Subject: [PATCH 047/198] Fixed clients not syncing the position of their controlled character when dead/unconscious --- .../BarotraumaClient/Source/Characters/CharacterNetworking.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs index 9db7409d8..877bf6414 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs @@ -150,7 +150,7 @@ namespace Barotrauma msg.ReadPadBits(); int index = 0; - if (GameMain.NetworkMember.Character == this) + if (GameMain.NetworkMember.Character == this && AllowInput) { var posInfo = new CharacterStateInfo(pos, rotation, networkUpdateID, facingRight ? Direction.Right : Direction.Left, selectedEntity, animation); while (index < memState.Count && NetIdUtils.IdMoreRecent(posInfo.ID, memState[index].ID)) From a373c589d1b4351aa92c1a755fdadff82f3420f3 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 23 Jul 2018 17:44:32 +0300 Subject: [PATCH 048/198] Fixed ragdoll dropping away from the collider if it's freezed while swimming (e.g. if a client disconnects when in water) --- .../BarotraumaShared/Source/Characters/Animation/Ragdoll.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs index 011f19a5b..e69c30f19 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs @@ -47,6 +47,7 @@ namespace Barotrauma frozen = value; Collider.PhysEnabled = !frozen; + if (frozen && MainLimb != null) MainLimb.pullJoint.WorldAnchorB = MainLimb.SimPosition; } } From 25a62b3a511684b8198ffd5fbd096193e2fbdadf Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 23 Jul 2018 17:58:02 +0300 Subject: [PATCH 049/198] More physics error checks, ragdoll clamps the velocity returned by PhysicsBody.CorrectPosition to prevent applying excessively high velocities to the collider if the position has changed significantly between frames --- .../BarotraumaClient/Source/Items/Item.cs | 28 +++++++++++---- .../Source/Characters/Animation/Ragdoll.cs | 34 ++++++++++++++++--- .../Source/Physics/PhysicsBody.cs | 14 ++++---- 3 files changed, 58 insertions(+), 18 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Item.cs b/Barotrauma/BarotraumaClient/Source/Items/Item.cs index 978540201..5544e8c3a 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Item.cs @@ -453,7 +453,7 @@ namespace Barotrauma float newRotation = msg.ReadRangedSingle(0.0f, MathHelper.TwoPi, 7); bool awake = msg.ReadBoolean(); Vector2 newVelocity = Vector2.Zero; - + if (awake) { newVelocity = new Vector2( @@ -461,6 +461,19 @@ namespace Barotrauma msg.ReadRangedSingle(-MaxVel, MaxVel, 12)); } + if (!MathUtils.IsValid(newPosition) || !MathUtils.IsValid(newRotation) || !MathUtils.IsValid(newVelocity)) + { + string errorMsg = "Received invalid position data for the item \"" + Name + + "\" (position: " + newPosition + ", rotation: " + newRotation + ", velocity: " + newVelocity + ")"; +#if DEBUG + DebugConsole.ThrowError(errorMsg); +#endif + GameAnalyticsManager.AddErrorEventOnce("Item.ClientReadPosition:InvalidData" + ID, + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + errorMsg); + return; + } + if (body == null) { DebugConsole.ThrowError("Received a position update for an item with no physics body (" + Name + ")"); @@ -470,7 +483,7 @@ namespace Barotrauma body.FarseerBody.Awake = awake; if (body.FarseerBody.Awake) { - if ((newVelocity - body.LinearVelocity).Length() > 8.0f) body.LinearVelocity = newVelocity; + if ((newVelocity - body.LinearVelocity).LengthSquared() > 8.0f * 8.0f) body.LinearVelocity = newVelocity; } else { @@ -490,11 +503,12 @@ namespace Barotrauma if ((newPosition - SimPosition).Length() > body.LinearVelocity.Length() * 2.0f) { - body.SetTransform(newPosition, newRotation); - - Vector2 displayPos = ConvertUnits.ToDisplayUnits(body.SimPosition); - rect.X = (int)(displayPos.X - rect.Width / 2.0f); - rect.Y = (int)(displayPos.Y + rect.Height / 2.0f); + if (body.SetTransform(newPosition, newRotation)) + { + Vector2 displayPos = ConvertUnits.ToDisplayUnits(body.SimPosition); + rect.X = (int)(displayPos.X - rect.Width / 2.0f); + rect.Y = (int)(displayPos.Y + rect.Height / 2.0f); + } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs index e69c30f19..587edd342 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs @@ -1332,8 +1332,24 @@ namespace Barotrauma character.AnimController.Anim = AnimController.Animation.None; } - Collider.LinearVelocity = Vector2.Zero; - Collider.CorrectPosition(character.MemState, deltaTime, out overrideTargetMovement); + Vector2 newVelocity = overrideTargetMovement; + Vector2 newPosition = Collider.SimPosition; + Collider.CorrectPosition(character.MemState, deltaTime, out newVelocity, out newPosition); + + newVelocity = newVelocity.ClampLength(100.0f); + if (!MathUtils.IsValid(newVelocity)) newVelocity = Vector2.Zero; + + Collider.LinearVelocity = newVelocity; + float distSqrd = Vector2.DistanceSquared(newPosition, Collider.SimPosition); + if (distSqrd > 10.0f) + { + SetPosition(newPosition); + } + else if (distSqrd > 0.1f) + { + Collider.SetTransform(newPosition, Collider.Rotation); + overrideTargetMovement = newVelocity; + } //unconscious/dead characters can't correct their position using AnimController movement // -> we need to correct it manually @@ -1451,10 +1467,18 @@ namespace Barotrauma } } - Collider.SetTransform(Collider.SimPosition + positionError, Collider.Rotation + rotationError); - foreach (Limb limb in Limbs) + float errorMagnitude = positionError.Length(); + if (errorMagnitude > 0.01f) { - limb.body.SetTransform(limb.body.SimPosition + positionError, limb.body.Rotation); + Collider.SetTransform(Collider.SimPosition + positionError, Collider.Rotation + rotationError); + if (errorMagnitude > 0.5f) + { + character.MemLocalState.Clear(); + foreach (Limb limb in Limbs) + { + limb.body.SetTransform(limb.body.SimPosition + positionError, limb.body.Rotation); + } + } } } diff --git a/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs b/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs index fd71f11a2..f6061e3b4 100644 --- a/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs +++ b/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs @@ -463,30 +463,32 @@ namespace Barotrauma body.ApplyTorque(torque); } - public void SetTransform(Vector2 simPosition, float rotation) + public bool SetTransform(Vector2 simPosition, float rotation) { System.Diagnostics.Debug.Assert(MathUtils.IsValid(simPosition)); System.Diagnostics.Debug.Assert(Math.Abs(simPosition.X) < 1000000.0f); System.Diagnostics.Debug.Assert(Math.Abs(simPosition.Y) < 1000000.0f); - if (!IsValidValue(simPosition, "position", -1e10f, 1e10f)) return; - if (!IsValidValue(rotation, "rotation")) return; + if (!IsValidValue(simPosition, "position", -1e10f, 1e10f)) return false; + if (!IsValidValue(rotation, "rotation")) return false; body.SetTransform(simPosition, rotation); SetPrevTransform(simPosition, rotation); + return true; } - public void SetTransformIgnoreContacts(Vector2 simPosition, float rotation) + public bool SetTransformIgnoreContacts(Vector2 simPosition, float rotation) { System.Diagnostics.Debug.Assert(MathUtils.IsValid(simPosition)); System.Diagnostics.Debug.Assert(Math.Abs(simPosition.X) < 1000000.0f); System.Diagnostics.Debug.Assert(Math.Abs(simPosition.Y) < 1000000.0f); - if (!IsValidValue(simPosition, "position", -1e10f, 1e10f)) return; - if (!IsValidValue(rotation, "rotation")) return; + if (!IsValidValue(simPosition, "position", -1e10f, 1e10f)) return false; + if (!IsValidValue(rotation, "rotation")) return false; body.SetTransformIgnoreContacts(ref simPosition, rotation); SetPrevTransform(simPosition, rotation); + return true; } public void SetPrevTransform(Vector2 simPosition, float rotation) From 9d1c4f3efbfa6a5c6f7a44ae8a4ca4bea119e194 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 23 Jul 2018 17:58:28 +0300 Subject: [PATCH 050/198] Moved Submarine.ClientRead to the client project --- .../BarotraumaClient/Source/Map/Submarine.cs | 29 +++++++++++++++++++ .../BarotraumaShared/Source/Map/Submarine.cs | 28 ------------------ 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Map/Submarine.cs b/Barotrauma/BarotraumaClient/Source/Map/Submarine.cs index c6c4e786e..44e13dd6b 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Submarine.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Submarine.cs @@ -1,5 +1,6 @@ using Barotrauma.Networking; using FarseerPhysics; +using Lidgren.Network; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; @@ -216,6 +217,34 @@ namespace Barotrauma } } } + + public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) + { + var newTargetPosition = new Vector2( + msg.ReadFloat(), + msg.ReadFloat()); + + //already interpolating with more up-to-date data -> ignore + if (subBody.MemPos.Count > 1 && subBody.MemPos[0].Timestamp > sendingTime) + { + return; + } + int index = 0; + while (index < subBody.MemPos.Count && sendingTime > subBody.MemPos[index].Timestamp) + { + index++; + } + + //position with the same timestamp already in the buffer (duplicate packet?) + // -> no need to add again + if (index < subBody.MemPos.Count && sendingTime == subBody.MemPos[index].Timestamp) + { + return; + } + + subBody.LastReceivedPositionUpdate = Math.Max(subBody.LastReceivedPositionUpdate, Timing.TotalTime); + subBody.MemPos.Insert(index, new PosInfo(newTargetPosition, 0.0f, sendingTime)); + } } } diff --git a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs index 0568cfcd4..ad03b26fc 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs @@ -1326,34 +1326,6 @@ namespace Barotrauma msg.Write(PhysicsBody.SimPosition.X); msg.Write(PhysicsBody.SimPosition.Y); } - - public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) - { - var newTargetPosition = new Vector2( - msg.ReadFloat(), - msg.ReadFloat()); - - //already interpolating with more up-to-date data -> ignore - if (subBody.MemPos.Count > 1 && subBody.MemPos[0].Timestamp > sendingTime) - { - return; - } - - int index = 0; - while (index < subBody.MemPos.Count && sendingTime > subBody.MemPos[index].Timestamp) - { - index++; - } - - //position with the same timestamp already in the buffer (duplicate packet?) - // -> no need to add again - if (index < subBody.MemPos.Count && sendingTime == subBody.MemPos[index].Timestamp) - { - return; - } - - subBody.MemPos.Insert(index, new PosInfo(newTargetPosition, 0.0f, sendingTime)); - } } } From 12c6a081a9562fe455ab0f67dcf9a4c7d33cf749 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 23 Jul 2018 18:33:04 +0300 Subject: [PATCH 051/198] Fixed nullref exception in NetLobbyScreen when selecting a level seed that has no background sprite defined --- Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs index dbc53fa2f..79582a0e9 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs @@ -177,7 +177,7 @@ namespace Barotrauma if (levelSeed == value) return; levelSeed = value; - backgroundSprite = LocationType.Random(levelSeed).Background; + backgroundSprite = LocationType.Random(levelSeed)?.Background; seedBox.Text = levelSeed; } } From 4041633eec67cef78e581fe0a634ad6e50b36823 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 23 Jul 2018 18:33:32 +0300 Subject: [PATCH 052/198] Clamping velocity when correcting submarine position --- Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs b/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs index bdf43b42e..09f9e7a30 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs @@ -190,6 +190,11 @@ namespace Barotrauma Body.CorrectPosition(memPos, deltaTime, out newVelocity, out newPosition); Vector2 moveAmount = ConvertUnits.ToDisplayUnits(newPosition - Body.SimPosition); + newVelocity = newVelocity.ClampLength(100.0f); + if (!MathUtils.IsValid(newVelocity)) + { + return; + } List subsToMove = submarine.GetConnectedSubs(); foreach (Submarine dockedSub in subsToMove) From 4b5a4d66e75bf7b8f4a7b3d8548ec2e777c3b7c1 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 23 Jul 2018 18:37:48 +0300 Subject: [PATCH 053/198] Fixed nullref exception if the player clicks yes on the "download sub from the server" prompt after returning to the main menu. --- Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs index 79582a0e9..c75161cb0 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs @@ -1532,7 +1532,7 @@ namespace Barotrauma requestFileBox.Buttons[0].OnClicked += (GUIButton button, object userdata) => { string[] fileInfo = (string[])userdata; - GameMain.Client.RequestFile(FileTransferType.Submarine, fileInfo[0], fileInfo[1]); + GameMain.Client?.RequestFile(FileTransferType.Submarine, fileInfo[0], fileInfo[1]); return true; }; requestFileBox.Buttons[1].OnClicked += requestFileBox.Close; From 7977e473594ff47aebe21b992416c5bfbbe25572 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 23 Jul 2018 19:24:44 +0300 Subject: [PATCH 054/198] Fixes --- Barotrauma/BarotraumaClient/Source/Map/Submarine.cs | 1 - .../Source/Characters/Animation/Ragdoll.cs | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Map/Submarine.cs b/Barotrauma/BarotraumaClient/Source/Map/Submarine.cs index 44e13dd6b..bccc223b4 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Submarine.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Submarine.cs @@ -243,7 +243,6 @@ namespace Barotrauma return; } - subBody.LastReceivedPositionUpdate = Math.Max(subBody.LastReceivedPositionUpdate, Timing.TotalTime); subBody.MemPos.Insert(index, new PosInfo(newTargetPosition, 0.0f, sendingTime)); } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs index 587edd342..d2f06b11a 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs @@ -1332,23 +1332,23 @@ namespace Barotrauma character.AnimController.Anim = AnimController.Animation.None; } - Vector2 newVelocity = overrideTargetMovement; + Vector2 newVelocity = Vector2.Zero; Vector2 newPosition = Collider.SimPosition; Collider.CorrectPosition(character.MemState, deltaTime, out newVelocity, out newPosition); newVelocity = newVelocity.ClampLength(100.0f); if (!MathUtils.IsValid(newVelocity)) newVelocity = Vector2.Zero; - + overrideTargetMovement = newVelocity; Collider.LinearVelocity = newVelocity; + float distSqrd = Vector2.DistanceSquared(newPosition, Collider.SimPosition); if (distSqrd > 10.0f) { SetPosition(newPosition); } - else if (distSqrd > 0.1f) + else if (distSqrd > 0.01f) { Collider.SetTransform(newPosition, Collider.Rotation); - overrideTargetMovement = newVelocity; } //unconscious/dead characters can't correct their position using AnimController movement From be5f168b76bf0ff7ad4634045231eda2c4aab6c9 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 23 Jul 2018 21:16:57 +0300 Subject: [PATCH 055/198] Sending error messages during EntityEvent writing/reading and entity removal to GameAnalytics --- .../ClientEntityEventManager.cs | 6 +-- .../BarotraumaShared/Source/Map/Entity.cs | 48 ++++++++++++++----- .../ServerEntityEventManager.cs | 6 ++- 3 files changed, 43 insertions(+), 17 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs b/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs index 4beda97e2..cd4c92164 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs @@ -191,10 +191,10 @@ namespace Barotrauma.Networking if (GameSettings.VerboseLogging) { DebugConsole.ThrowError("Failed to read event for entity \"" + entity.ToString() + "\"!", e); - GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:ReadFailed" + entity.ToString(), - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, - "Failed to read event for entity \"" + entity.ToString() + "\"!\n" + e.StackTrace); } + GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:ReadFailed" + entity.ToString(), + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Failed to read event for entity \"" + entity.ToString() + "\"!\n" + e.StackTrace); msg.Position = msgPosition + msgLength * 8; } } diff --git a/Barotrauma/BarotraumaShared/Source/Map/Entity.cs b/Barotrauma/BarotraumaShared/Source/Map/Entity.cs index ac0046d75..8546ff782 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Entity.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Entity.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; namespace Barotrauma { @@ -126,22 +127,27 @@ namespace Barotrauma catch (Exception exception) { DebugConsole.ThrowError("Error while removing entity \"" + e.ToString() + "\"", exception); + GameAnalyticsManager.AddErrorEventOnce( + "Entity.RemoveAll:Exception" + e.ToString(), + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Error while removing entity \"" + e.ToString() + "\"" + exception.Message); } } + StringBuilder errorMsg = new StringBuilder(); if (dictionary.Count > 0) { - DebugConsole.ThrowError("Some entities were not removed in Entity.RemoveAll:"); + errorMsg.AppendLine("Some entities were not removed in Entity.RemoveAll:"); foreach (Entity e in dictionary.Values) { - DebugConsole.ThrowError(" - " + e.ToString() + "(ID " + e.id + ")"); + errorMsg.AppendLine(" - " + e.ToString() + "(ID " + e.id + ")"); } } if (Item.ItemList.Count > 0) { - DebugConsole.ThrowError("Some items were not removed in Entity.RemoveAll:"); + errorMsg.AppendLine("Some items were not removed in Entity.RemoveAll:"); foreach (Item item in Item.ItemList) { - DebugConsole.ThrowError(" - " + item.Name + "(ID " + item.id + ")"); + errorMsg.AppendLine(" - " + item.Name + "(ID " + item.id + ")"); } var items = new List(Item.ItemList); @@ -153,17 +159,17 @@ namespace Barotrauma } catch (Exception exception) { - DebugConsole.ThrowError("Error while removing entity \"" + item.ToString() + "\"", exception); + DebugConsole.ThrowError("Error while removing item \"" + item.ToString() + "\"", exception); } } Item.ItemList.Clear(); } if (Character.CharacterList.Count > 0) { - DebugConsole.ThrowError("Some characters were not removed in Entity.RemoveAll:"); + errorMsg.AppendLine("Some characters were not removed in Entity.RemoveAll:"); foreach (Character character in Character.CharacterList) { - DebugConsole.ThrowError(" - " + character.Name + "(ID " + character.id + ")"); + errorMsg.AppendLine(" - " + character.Name + "(ID " + character.id + ")"); } var characters = new List(Character.CharacterList); @@ -175,26 +181,42 @@ namespace Barotrauma } catch (Exception exception) { - DebugConsole.ThrowError("Error while removing entity \"" + character.ToString() + "\"", exception); + DebugConsole.ThrowError("Error while removing character \"" + character.ToString() + "\"", exception); } } Character.CharacterList.Clear(); } + if (!string.IsNullOrEmpty(errorMsg.ToString())) + { + foreach (string errorLine in errorMsg.ToString().Split('\n')) + { + DebugConsole.ThrowError(errorLine); + } + GameAnalyticsManager.AddErrorEventOnce("Entity.RemoveAll", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg.ToString()); + } + dictionary.Clear(); } public virtual void Remove() { - DebugConsole.Log("Removing entity " + this.ToString() + " (" + ID + ") from entity dictionary."); - Entity existingEntity; - if (!dictionary.TryGetValue(ID, out existingEntity)) + DebugConsole.Log("Removing entity " + ToString() + " (" + ID + ") from entity dictionary."); + if (!dictionary.TryGetValue(ID, out Entity existingEntity)) { - DebugConsole.Log("Entity " + this.ToString() + " (" + ID + ") not present in entity dictionary."); + DebugConsole.Log("Entity " + ToString() + " (" + ID + ") not present in entity dictionary."); + GameAnalyticsManager.AddErrorEventOnce( + "Entity.Remove:EntityNotFound" + ID, + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Entity " + ToString() + " (" + ID + ") not present in entity dictionary.\n" + Environment.StackTrace); } else if (existingEntity != this) { - DebugConsole.Log("Entity ID mismatch in entity dictionary. Entity " + existingEntity + " had the ID " + ID); + DebugConsole.Log("Entity ID mismatch in entity dictionary. Entity " + existingEntity + " had the ID " + ID + " (expecting " + ToString() + ")"); + GameAnalyticsManager.AddErrorEventOnce("Entity.Remove:EntityMismatch" + ID, + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Entity ID mismatch in entity dictionary. Entity " + existingEntity + " had the ID " + ID + " (expecting " + ToString() + ")"); + foreach (var keyValuePair in dictionary.Where(kvp => kvp.Value == this).ToList()) { dictionary.Remove(keyValuePair.Key); diff --git a/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/ServerEntityEventManager.cs b/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/ServerEntityEventManager.cs index 5c0986cd4..648b28c43 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/ServerEntityEventManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/ServerEntityEventManager.cs @@ -135,10 +135,14 @@ namespace Barotrauma.Networking catch (Exception e) { + string entityName = bufferedEvent.TargetEntity == null ? "null" : bufferedEvent.TargetEntity.ToString(); if (GameSettings.VerboseLogging) { - DebugConsole.ThrowError("Failed to read event for entity \"" + bufferedEvent.TargetEntity.ToString() + "\"!", e); + DebugConsole.ThrowError("Failed to read server event for entity \"" + entityName + "\"!", e); } + GameAnalyticsManager.AddErrorEventOnce("ServerEntityEventManager.Read:ReadFailed" + entityName, + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Failed to read server event for entity \"" + entityName + "\"!\n" + e.StackTrace); } bufferedEvent.IsProcessed = true; From 002e63f1eb430db72f2ea7482d3a9f3d089784c3 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 23 Jul 2018 21:36:43 +0300 Subject: [PATCH 056/198] Fixed entity IDs occasionally getting messed up due to the gaps and hulls created by docking ports. Clients would simply use the existing hulls and gaps when a network event for an already docked port is received, even though they may have been removed and replaced with ones with different IDs. --- .../Source/Items/Components/DockingPort.cs | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs index 63108d2ea..9ea9e1e61 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs @@ -27,7 +27,7 @@ namespace Barotrauma.Items.Components private Joint joint; - private Hull[] hulls; + private readonly Hull[] hulls = new Hull[2]; private ushort?[] hullIds; private Door door; @@ -238,7 +238,7 @@ namespace Barotrauma.Items.Components { if (GameMain.Client != null && !isNetworkMessage) return; - if (dockingTarget==null) + if (dockingTarget == null) { DebugConsole.ThrowError("Error - attempted to lock a docking port that's not connected to anything"); return; @@ -267,6 +267,9 @@ namespace Barotrauma.Items.Components item.CreateServerEvent(this); } + List removedEntities = item.linkedTo.Where(e => e.Removed).ToList(); + foreach (MapEntity removed in removedEntities) item.linkedTo.Remove(removed); + if (!item.linkedTo.Any(e => e is Hull) && !dockingTarget.item.linkedTo.Any(e => e is Hull)) { CreateHull(); @@ -377,7 +380,6 @@ namespace Barotrauma.Items.Components var hullRects = new Rectangle[] { item.WorldRect, dockingTarget.item.WorldRect }; var subs = new Submarine[] { item.Submarine, dockingTarget.item.Submarine }; - hulls = new Hull[2]; bodies = new Body[4]; if (dockingTarget.door != null) @@ -406,6 +408,7 @@ namespace Barotrauma.Items.Components hullRects[i].Location -= MathUtils.ToPoint((subs[i].WorldPosition - subs[i].HiddenSubPosition)); hulls[i] = new Hull(MapEntityPrefab.Find("Hull"), hullRects[i], subs[i]); hulls[i].AddToGrid(subs[i]); + if (hullIds[i] != null) hulls[i].ID = (ushort)hullIds[i]; for (int j = 0; j < 2; j++) { @@ -436,7 +439,6 @@ namespace Barotrauma.Items.Components hullRects[i].Location -= MathUtils.ToPoint((subs[i].WorldPosition - subs[i].HiddenSubPosition)); hulls[i] = new Hull(MapEntityPrefab.Find("Hull"), hullRects[i], subs[i]); hulls[i].AddToGrid(subs[i]); - if (hullIds[i] != null) hulls[i].ID = (ushort)hullIds[i]; } @@ -560,13 +562,9 @@ namespace Barotrauma.Items.Components GameMain.World.RemoveJoint(joint); joint = null; } - - if (hulls != null) - { - hulls[0].Remove(); - hulls[1].Remove(); - hulls = null; - } + + hulls[0]?.Remove(); hulls[0] = null; + hulls[1]?.Remove(); hulls[1] = null; if (gap != null) { @@ -576,10 +574,9 @@ namespace Barotrauma.Items.Components hullIds[0] = null; hullIds[1] = null; - gapId = null; - - if (bodies!=null) + + if (bodies != null) { foreach (Body body in bodies) { @@ -749,6 +746,21 @@ namespace Barotrauma.Items.Components { bool isDocked = msg.ReadBoolean(); + for (int i = 0; i < 2; i++) + { + if (hulls[i] == null) continue; + item.linkedTo.Remove(hulls[i]); + hulls[i].Remove(); + hulls[i] = null; + } + + if (gap != null) + { + item.linkedTo.Remove(gap); + gap.Remove(); + gap = null; + } + if (isDocked) { ushort dockingTargetID = msg.ReadUInt16(); From 852fc174c2abe34cd37f853a63c83cad517ab22b Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 23 Jul 2018 22:51:52 +0300 Subject: [PATCH 057/198] Fixes to respawn shuttle bugs that may have caused entity ID mismatches: clients don't remove gaps or undock docking ports when a respawn shuttle leaves the level (but instead receive network events that tell them to do so), removing gaps is done with EntitySpawner. --- .../Source/Networking/RespawnManager.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs b/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs index f32d873ce..d32650e6d 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs @@ -277,15 +277,15 @@ namespace Barotrauma.Networking if (door.IsOpen) door.SetState(false,false,true); } + var server = networkMember as GameServer; + if (server == null) return; + var shuttleGaps = Gap.GapList.FindAll(g => g.Submarine == respawnShuttle && g.ConnectedWall != null); - shuttleGaps.ForEach(g => g.Remove()); + shuttleGaps.ForEach(g => Spawner.AddToRemoveQueue(g)); var dockingPorts = Item.ItemList.FindAll(i => i.Submarine == respawnShuttle && i.GetComponent() != null); dockingPorts.ForEach(d => d.GetComponent().Undock()); - var server = networkMember as GameServer; - if (server == null) return; - //shuttle has returned if the path has been traversed or the shuttle is close enough to the exit if (!CoroutineManager.IsCoroutineRunning("forcepos")) @@ -405,7 +405,7 @@ namespace Barotrauma.Networking } var shuttleGaps = Gap.GapList.FindAll(g => g.Submarine == respawnShuttle && g.ConnectedWall != null); - shuttleGaps.ForEach(g => g.Remove()); + shuttleGaps.ForEach(g => Spawner.AddToRemoveQueue(g)); foreach (Hull hull in Hull.hullList) { @@ -427,7 +427,7 @@ namespace Barotrauma.Networking foreach (Item item in c.Inventory.Items) { if (item == null) continue; - Entity.Spawner.AddToRemoveQueue(item); + Spawner.AddToRemoveQueue(item); } } From 2c6535b1f2d277d394bdc6a19dc5abb6c6fe9800 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 23 Jul 2018 22:52:24 +0300 Subject: [PATCH 058/198] PhysicsBody error messages are only shown in the console when verbose logging is on. --- Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs b/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs index f6061e3b4..b1e88218a 100644 --- a/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs +++ b/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs @@ -379,7 +379,7 @@ namespace Barotrauma " to a physics body (userdata: " + userData + "), value: " + value + "\n" + Environment.StackTrace; - DebugConsole.ThrowError(errorMsg); + if (GameSettings.VerboseLogging) DebugConsole.ThrowError(errorMsg); GameAnalyticsManager.AddErrorEventOnce( "PhysicsBody.SetPosition:InvalidPosition" + userData, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, @@ -401,7 +401,7 @@ namespace Barotrauma " to a physics body (userdata: " + userData + "), value: " + value + "\n" + Environment.StackTrace; - DebugConsole.ThrowError(errorMsg); + if (GameSettings.VerboseLogging) DebugConsole.ThrowError(errorMsg); GameAnalyticsManager.AddErrorEventOnce( "PhysicsBody.SetPosition:InvalidPosition" + userData, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, From 3b6bedb180b4ef3758c45171b158951c98b05ea7 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 23 Jul 2018 23:27:27 +0300 Subject: [PATCH 059/198] Fixed clients not recreating docking port hulls/gaps when receiving a network event if the docking port is already locked --- .../Source/Items/Components/DockingPort.cs | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs index 9ea9e1e61..076c3307f 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs @@ -243,30 +243,29 @@ namespace Barotrauma.Items.Components DebugConsole.ThrowError("Error - attempted to lock a docking port that's not connected to anything"); return; } - else if (joint is WeldJoint) - { - //DebugConsole.ThrowError("Error - attempted to lock a docking port that's already locked"); - return; - } - dockingDir = IsHorizontal ? - Math.Sign(dockingTarget.item.WorldPosition.X - item.WorldPosition.X) : - Math.Sign(dockingTarget.item.WorldPosition.Y - item.WorldPosition.Y); - dockingTarget.dockingDir = -dockingDir; + if (!(joint is WeldJoint)) + { + + dockingDir = IsHorizontal ? + Math.Sign(dockingTarget.item.WorldPosition.X - item.WorldPosition.X) : + Math.Sign(dockingTarget.item.WorldPosition.Y - item.WorldPosition.Y); + dockingTarget.dockingDir = -dockingDir; #if CLIENT - PlaySound(ActionType.OnSecondaryUse, item.WorldPosition); + PlaySound(ActionType.OnSecondaryUse, item.WorldPosition); #endif - ConnectWireBetweenPorts(); + ConnectWireBetweenPorts(); + CreateJoint(true); - CreateJoint(true); - - if (GameMain.Server != null) - { - item.CreateServerEvent(this); + if (GameMain.Server != null) + { + item.CreateServerEvent(this); + } } + List removedEntities = item.linkedTo.Where(e => e.Removed).ToList(); foreach (MapEntity removed in removedEntities) item.linkedTo.Remove(removed); From a63ee41e5f7ccf237ff88bc0680cb578946ddf17 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 24 Jul 2018 01:06:27 +0300 Subject: [PATCH 060/198] v0.8.1.9 --- .../BarotraumaClient/Properties/AssemblyInfo.cs | 4 ++-- .../BarotraumaServer/Properties/AssemblyInfo.cs | 4 ++-- Barotrauma/BarotraumaShared/changelog.txt | 15 +++++++++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs index d6964bf4f..d39aae256 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.8")] -[assembly: AssemblyFileVersion("0.8.1.8")] +[assembly: AssemblyVersion("0.8.1.9")] +[assembly: AssemblyFileVersion("0.8.1.9")] diff --git a/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs index 8467bf18d..f7493a1eb 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.8")] -[assembly: AssemblyFileVersion("0.8.1.8")] +[assembly: AssemblyVersion("0.8.1.9")] +[assembly: AssemblyFileVersion("0.8.1.9")] diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index ebd918174..0a24d2379 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,18 @@ +--------------------------------------------------------------------------------------------------------- +v0.8.1.9 +--------------------------------------------------------------------------------------------------------- + +- Fixed a bug in docking port syncing that caused entity ID mismatches and "unknown object header" errors. +- Fixed a bug that occasionally caused entity ID mismatches and "unknown object header" errors when +a respawn shuttle got damaged before returning back to the starting location. +- Fixed error messages when attempting to use the console commands "\" or "\n". +- Fixed clients not syncing the position of their controlled character with the server when dead/unconscious. +- Fixed swimming ragdolls "dropping down" if the server freezes them due to a connection error. +- Fixed excessive "attempted to apply invalid velocity to a physics body" console errors. +- Fixed crashing when selecting a level seed that has no background portrait defined. +- Fixed crashing if the player clicks yes on the "download sub from the server" prompt after +returning to the main menu. + --------------------------------------------------------------------------------------------------------- v0.8.1.8 --------------------------------------------------------------------------------------------------------- From 4e3d375ae5d1e987136ab175e62b9ffeb9f3fb78 Mon Sep 17 00:00:00 2001 From: ursinewalrus Date: Mon, 23 Jul 2018 18:57:29 -0500 Subject: [PATCH 061/198] game will now launch on overbuy and extra items will be present on floor, however a GUI error will be generated on match start --- .../BarotraumaShared/Source/GameSession/CargoManager.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs b/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs index 24b1c9c03..941d21b7a 100644 --- a/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs +++ b/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs @@ -177,8 +177,11 @@ namespace Barotrauma } //reduce the number of available slots in the container - availableContainers[itemContainer]--; - if (availableContainers[itemContainer] <= 0) + if (availableContainers.Count() > 0) + { + availableContainers[itemContainer]--; + } + if (availableContainers.ContainsKey(itemContainer) && availableContainers[itemContainer] <= 0) { availableContainers.Remove(itemContainer); } From c28fd336c9bdbe3790c1e529f385a8a5afcffe33 Mon Sep 17 00:00:00 2001 From: ursinewalrus Date: Mon, 23 Jul 2018 21:32:01 -0500 Subject: [PATCH 062/198] If you buy more than a full box worth of stuff at the store instead of not letting you start the mission it will put the extra stuff in extra boxes for you --- .../Source/GameSession/CargoManager.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs b/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs index 941d21b7a..31aa45682 100644 --- a/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs +++ b/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs @@ -110,6 +110,7 @@ namespace Barotrauma } Dictionary availableContainers = new Dictionary(); + ItemPrefab containerPrefab = null; foreach (PurchasedItem Pi in itemsToSpawn) { Vector2 position = new Vector2( @@ -125,7 +126,7 @@ namespace Barotrauma if (itemContainer == null) { - var containerPrefab = MapEntityPrefab.List.Find(ep => + containerPrefab = MapEntityPrefab.List.Find(ep => ep.NameMatches(Pi.itemPrefab.CargoContainerName) || (ep.Tags != null && ep.Tags.Contains(Pi.itemPrefab.CargoContainerName.ToLowerInvariant()))) as ItemPrefab; @@ -151,6 +152,13 @@ namespace Barotrauma } for (int i = 0; i < Pi.quantity; i++) { + if (!availableContainers.ContainsKey(itemContainer)) + { + Item containerItemOverFlow = new Item(containerPrefab, position, wp.Submarine); + itemContainer = containerItemOverFlow.GetComponent(); + availableContainers.Add(itemContainer, itemContainer.Capacity); + } + if (itemContainer == null) { //no container, place at the waypoint @@ -177,7 +185,8 @@ namespace Barotrauma } //reduce the number of available slots in the container - if (availableContainers.Count() > 0) + //if there is a container + if (availableContainers.ContainsKey(itemContainer)) { availableContainers[itemContainer]--; } From 65b8d2d36efdfbe288fe843a3a0f93e039fa1b71 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 24 Jul 2018 13:19:07 +0300 Subject: [PATCH 063/198] Fixed door gaps being removed twice when unloading a sub (in door.Remove and Entity.RemoveAll). Probably didn't cause any issues other than unnecessary console warnings. --- .../BarotraumaShared/Source/Items/Components/Door.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs index c86ce633a..9a97d444a 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs @@ -334,9 +334,13 @@ namespace Barotrauma.Items.Components body.Remove(); body = null; } - - - if (linkedGap != null) linkedGap.Remove(); + + //no need to remove the gap if we're unloading the whole submarine + //otherwise the gap will be removed twice and cause console warnings + if (!Submarine.Unloading) + { + if (linkedGap != null) linkedGap.Remove(); + } doorSprite.Remove(); if (weldedSprite != null) weldedSprite.Remove(); From 5e6215a37ccc36d95f6ade68059373ff08a2672c Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 24 Jul 2018 13:21:02 +0300 Subject: [PATCH 064/198] Fixed crashing when attempting to generate hulls with the "autohull" command when there are no walls or doors in the sub. --- .../BarotraumaClient/Source/Screens/SubEditorScreen.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs index c5d6d6bff..b1bbb0131 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs @@ -1082,6 +1082,12 @@ namespace Barotrauma } } + if (wallPoints.Count < 4) + { + DebugConsole.ThrowError("Generating hulls for the submarine failed. Not enough wall structures to generate hulls."); + return; + } + min = wallPoints[0]; max = wallPoints[0]; for (int i = 0; i < wallPoints.Count; i++) From 4a8845c6040379473b208be9d7749f2a18f5ad38 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 24 Jul 2018 13:21:36 +0300 Subject: [PATCH 065/198] Fixed docking ports creating duplicate hulls and gaps during loading --- .../Source/Items/Components/DockingPort.cs | 15 +++++++-------- .../BarotraumaShared/Source/Map/MapEntity.cs | 12 ++++++------ .../BarotraumaShared/Source/Map/Submarine.cs | 11 ++++++++++- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs index 076c3307f..f3137e284 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs @@ -418,9 +418,7 @@ namespace Barotrauma.Items.Components } gap = new Gap(new Rectangle(hullRects[0].Right - 2, hullRects[0].Y, 4, hullRects[0].Height), true, subs[0]); - if (gapId != null) gap.ID = (ushort)gapId; - - LinkHullsToGap(); + if (gapId != null) gap.ID = (ushort)gapId; } else { @@ -443,19 +441,20 @@ namespace Barotrauma.Items.Components gap = new Gap(new Rectangle(hullRects[0].X, hullRects[0].Y+2, hullRects[0].Width, 4), false, subs[0]); if (gapId != null) gap.ID = (ushort)gapId; - - LinkHullsToGap(); } - item.linkedTo.Add(hulls[0]); - item.linkedTo.Add(hulls[1]); + LinkHullsToGap(); hullIds[0] = hulls[0].ID; hullIds[1] = hulls[1].ID; + hulls[0].ShouldBeSaved = false; + hulls[1].ShouldBeSaved = false; + item.linkedTo.Add(hulls[0]); + item.linkedTo.Add(hulls[1]); gap.DisableHullRechecks = true; + gap.ShouldBeSaved = false; gapId = gap.ID; - item.linkedTo.Add(gap); foreach (Body body in bodies) diff --git a/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs index 5f620dd21..ab868b2d1 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs @@ -29,6 +29,8 @@ namespace Barotrauma get; set; } + + public bool ShouldBeSaved = true; //the position and dimensions of the entity protected Rectangle rect; @@ -45,7 +47,8 @@ namespace Barotrauma private static bool resizing; private int resizeDirX, resizeDirY; - public virtual Rectangle Rect { + public virtual Rectangle Rect + { get { return rect; } set { rect = value; } } @@ -353,15 +356,12 @@ namespace Barotrauma foreach (ushort i in e.linkedToID) { - MapEntity linked = FindEntityByID(i) as MapEntity; - - if (linked != null) e.linkedTo.Add(linked); + if (FindEntityByID(i) is MapEntity linked) e.linkedTo.Add(linked); } } List linkedSubs = new List(); - - for (int i = 0; i= 0; i--) + { + if (!e.linkedTo[i].ShouldBeSaved) e.linkedTo.RemoveAt(i); + } + } + + foreach (MapEntity e in MapEntity.mapEntityList) + { + if (e.MoveWithLevel || e.Submarine != this || !e.ShouldBeSaved) continue; e.Save(element); } } From bf0c12ce5260eca289d3e0a02aa492a1443619ee Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 24 Jul 2018 14:34:27 +0300 Subject: [PATCH 066/198] The number of completed missions in a level connection is saved (-> reloading doesn't reset the mission in the connection to the initial one). Fixes #517 --- .../BarotraumaShared/Source/Map/Map/Map.cs | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Map/Map/Map.cs b/Barotrauma/BarotraumaShared/Source/Map/Map/Map.cs index eb7eb91af..607f06e6d 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Map/Map.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Map/Map.cs @@ -418,12 +418,13 @@ namespace Barotrauma if (int.TryParse(discoveredStrs[i], out index)) locations[index].Discovered = true; } - string passedStr = element.GetAttributeString("passed", ""); - string[] passedStrs = passedStr.Split(','); - for (int i = 0; i < passedStrs.Length; i++) + foreach (XElement subElement in element.Elements()) { - int index = -1; - if (int.TryParse(passedStrs[i], out index)) connections[index].Passed = true; + if (subElement.Name.ToString() != "connection") continue; + int connectionIndex = subElement.GetAttributeInt("i", -1); + if (connectionIndex < 0 || connectionIndex >= connections.Count) continue; + connections[connectionIndex].Passed = true; + connections[connectionIndex].MissionsCompleted = subElement.GetAttributeInt("m", 0); } } @@ -442,12 +443,15 @@ namespace Barotrauma } mapElement.Add(new XAttribute("discovered", string.Join(",", discoveredLocations))); - List passedConnections = new List(); for (int i = 0; i < connections.Count; i++) { - if (connections[i].Passed) passedConnections.Add(i); + if (!connections[i].Passed) continue; + + var connectionElement = new XElement("connection", new XAttribute("i", i)); + if (connections[i].MissionsCompleted > 0) connectionElement.Add(new XAttribute("m", connections[i].MissionsCompleted)); + mapElement.Add(connectionElement); + } - mapElement.Add(new XAttribute("passed", string.Join(",", passedConnections))); element.Add(mapElement); } @@ -467,7 +471,7 @@ namespace Barotrauma public bool Passed; - private int missionsCompleted; + public int MissionsCompleted; private Mission mission; public Mission Mission @@ -476,12 +480,12 @@ namespace Barotrauma { if (mission == null || mission.Completed) { - if (mission != null && mission.Completed) missionsCompleted++; + if (mission != null && mission.Completed) MissionsCompleted++; long seed = (long)locations[0].MapPosition.X + (long)locations[0].MapPosition.Y * 100; seed += (long)locations[1].MapPosition.X * 10000 + (long)locations[1].MapPosition.Y * 1000000; - MTRandom rand = new MTRandom((int)((seed + missionsCompleted) % int.MaxValue)); + MTRandom rand = new MTRandom((int)((seed + MissionsCompleted) % int.MaxValue)); if (rand.NextDouble() < 0.3f) return null; @@ -518,8 +522,7 @@ namespace Barotrauma public LocationConnection(Location location1, Location location2) { locations = new Location[] { location1, location2 }; - - missionsCompleted = 0; + MissionsCompleted = 0; } public Location OtherLocation(Location location) From e21b756f29ae356b069871f9046d4ece66468c0f Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 24 Jul 2018 15:00:35 +0300 Subject: [PATCH 067/198] Fixed invalid left/right normal errors during level generation (I think). The voronoi cell generation logic ignored zero-length edges, but it was possible for an edge to be so short that the distance from the adjacent edge to the center of the short edge rounded down to zero in GenerateWallShapes. --- .../Source/Map/Levels/CaveGenerator.cs | 2 +- .../Source/Map/Levels/VoronoiElements.cs | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/CaveGenerator.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/CaveGenerator.cs index 1e08742a2..7325965ff 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/CaveGenerator.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/CaveGenerator.cs @@ -183,7 +183,7 @@ namespace Barotrauma foreach (GraphEdge ge in graphEdges) { - if (ge.point1 == ge.point2) continue; + if (Vector2.DistanceSquared(ge.point1, ge.point2) < 0.001f) continue; for (int i = 0; i < 2; i++) { diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/VoronoiElements.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/VoronoiElements.cs index 22641dfb4..27ee2e4a7 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/VoronoiElements.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/VoronoiElements.cs @@ -211,16 +211,16 @@ namespace Voronoi2 public VoronoiCell AdjacentCell(VoronoiCell cell) { - if (cell1==cell) + if (cell1 == cell) { return cell2; } - else if (cell2==cell) + else if (cell2 == cell) { return cell1; } - return null; + return null; } /// @@ -239,6 +239,11 @@ namespace Voronoi2 return normal; } + + public override string ToString() + { + return "GraphEdge (" + point1.ToString() + ", " + point2.ToString() + ")"; + } } // للترتيب From 756278bb815da62d54907e7470cbee72c16717a0 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 24 Jul 2018 15:43:26 +0300 Subject: [PATCH 068/198] Increased the minimum distance between verts in level collider generation code. Should make the "invalid triangle created by CaveGenerator" errors less frequent. (See #311) --- .../BarotraumaShared/Source/Map/Levels/CaveGenerator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/CaveGenerator.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/CaveGenerator.cs index 7325965ff..3b2086d0c 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/CaveGenerator.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/CaveGenerator.cs @@ -421,9 +421,9 @@ namespace Barotrauma { //don't create a triangle if any of the vertices are too close to each other //(apparently Farseer doesn't like polygons with a very small area, see Shape.ComputeProperties) - if (Vector2.Distance(triangles[i][0], triangles[i][1]) < 0.05f || - Vector2.Distance(triangles[i][0], triangles[i][2]) < 0.05f || - Vector2.Distance(triangles[i][1], triangles[i][2]) < 0.05f) continue; + if (Vector2.DistanceSquared(triangles[i][0], triangles[i][1]) < 0.006f || + Vector2.DistanceSquared(triangles[i][0], triangles[i][2]) < 0.006f || + Vector2.DistanceSquared(triangles[i][1], triangles[i][2]) < 0.006f) continue; Vertices bodyVertices = new Vertices(triangles[i]); var newFixture = FixtureFactory.AttachPolygon(bodyVertices, 5.0f, cellBody); From 9edbbaa170480c66ebda178c6699296bd01c9ea9 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 24 Jul 2018 16:29:25 +0300 Subject: [PATCH 069/198] Revert changes to the revolver sprite because jaggies are ugly --- .../Content/Items/Weapons/weapons.png | Bin 16235 -> 16957 bytes .../Content/Items/Weapons/weapons.xml | 13 ++++++------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Content/Items/Weapons/weapons.png b/Barotrauma/BarotraumaShared/Content/Items/Weapons/weapons.png index b42d17f9ab7adc52a1a9b5eef4af21136f19ef7a..2bd2a36f06f51013ee0f9469c45c76760b69a051 100644 GIT binary patch literal 16957 zcmV*hKu*7jP)004R= z004l4008;_004mK002@5009+P0026d000+n*IFNt00009a7bBm000XR000XR0j(pw zbpQYW8FWQhbW?9;ba!ELWdLwtX>N2bZe?^JG%heMF*(%MvSa`NL7quOK~#8N?VSgF z7DfL5SFj@;q=!(_d+)vXLV6*A6p|1~LI}NtUPJG_7ZFiZ6ckZG0TC1w`*C*f{2iX& z{ZG&O`F}r?Y<~uXHn_Z1d=i9XYq3#_XADQQMW^7Tw z$>~)wCl`!OIys{<{A7KZ-^p=hp(pDy98WHC)=n;T&`!>=6C_;Zdeg}obM0h82koTn z=9F*(fcC#cUs|)-^!QIy#;ql>;ZF-wu z0+$}a4!10yQs#%%(=)JlQ67%&uEX*Dlkn({8XVeIg?$@quxmydj-_y6ah)Xw2?Vc!;w7>b(7)fhi#92Rd}gqh1`puS-$D#unzG1J@r61aS^ zc1{jLy)1C*Kn+4&dm<*t5ot+5$jOL9PF5P+U9E6rWhwqPCmioh560Uwg78j#04`1O z!bg*wktf^ac$!}q0tgL@zZ?>pfXJvph>6RRB-7=tH=>fFzAddO!I&9!ShS%5Qx;Cf z1}@rTprpsFY6z1!4CNA3ky(^=7rj_G)x&+iA75m;n8D{ zqDl_z`5m?RWL_jbS`>p%7RKR|S)q7mk~=<~;()4v{wNI?fSaxZ0R#j@UiR_|L}W}F zqGB>6$#l8vjbw5Aqo>uPsHzawjy+)O+8fSZec>T>>+atl_HKPtdVj0C9ZYTibKx@S z(M=Vv)s{r#%S$U!nIDch^>tVCX6a(f~{fHAj(GZ`^hr2p}*h`f_kcjI{m?#3klRlIe2S8<~Zfs@3=C(*gY~yJCP% zH!*r|cm)lBtB<)DzbB249%db+xaocT9Ju^zVO`9>7ET(1e_mXT+97e+yKxClojQ&q zj~&8^=bsZHEW;Z|CgBqM{z+aUgkbz@K@k2i#}8l3aK)qq8>BjR#qHOL095}k#!pIV zIb(o9`Kg$`WE#5lYOjp%B1R95w?+Q&03>9)A~4DtRt`OsK#aHjC2;v7Ke8w0RR-gM z6$Pjt8HYnV=HS%PEjV^?H6Gf%2)j4Uz_Xi*aK6S7XDjXST9q9hD=^27)Na_H)e~cV z+F^*rP1i*i5F8SFc~JTgBqrx0IkiBNOqaXf$Slmj+?8{bfz2(t!p5m5JOc+HF~?O2 zfnId1A~HNE~jM_iSY-E2l#EKPQL|~ z;da=0Ud30V8QD7SiN->>ZXrIMnSsDH?{rG#bjbjCP)O3o>`0( zasSCFEvE~x5DySnld25dr(Yl3+2&3JMg(HT>zu5&) z04>P_*v2@+Dbq_C_}1HQWp|2p?b~7bre)~drIT8xC~gSgM~IHe_>uyMi_b<>Ou8hQ zE_c0Q6YYqw(ijXJXx_SS|GvuD!O_83vu!mp@-mbMaB{!l0e+N#z?d%wr5C~7JrDsw z(UN4k-1UZQnh#<|rYHk@`gv~I^*1>;O>5dtvpZ`ZzO?}6c+y7U@y=bdfq zEG+D&1qMb=^ACuc5gZaXvuIfDoXp(v`QhOy4Z&fFOL7K}Se}(zzB+$M)xkmOg)h3h z`(5_*@K<Dd}=f zPR=KOeu1C#=+*yoD;wvp23k6N74F>otDZg0FZb%z?{c_H-^;yv_m_D8%W(mgm%DW{ zyWB(aqWrBd_mni|F`Z%e9(^yzdYG&9-n|E0j`Cre>__sVJ*dxr z-+F{~RyC^@OZTo)2G%`5h8yQ}PfQnTXuHatto0 zRC$F%Mj~J0IkJ6jUIntUhapYo{&f9WIc3PmERo+Ch72JlE)$7KDJUo?M1EdAa&z-k zm?sP^EL7Bu9*wBzL`249psaEN1{aRRy$efJH;N z9@;uhr7`dE4HMOM2N{Fl(e-2HH}ljq^pRDya{gIrR`Rf<$I3pret1nC`~&0AtB>X9 zth4p|HtpZ`@GeUl__o__Q++;-P64#IF@UtBQz?TA#2`a4D5F>zB{#nU`GbcmgXM@Z zNnZYNm{df^rpe&05bn~tZET$( z-UQChUa)s?hkrmM?Cjm(=;S5wC^)-#O9e+EA~F?egECNDRDzCb(9!bAkwu(WnYw;pD2aSMRBR@~jK zw;0?HUAp!_cS*OkcSqZH9pU8Sg)k9-lamWPJ-y&1c({AO!=o|u^Y%udpD)tW(hwFN zs{~LZo`}cs@XFE3Q0a0GkFOc03{4O7$8O>FqO-E@Dp-aXh_q znkq{X9NRKYJVT)p3FAqeorpI$wrz@d$5faPu>TJMcm;T={+@Gx3SimBrKp-X5{a3K zEkgip+jhFy+Q#)cUX9~)EFm!qNl7`1goG?v&z5*Dl9O}QI?J%~2Bj6K7VF~d4m&&N zFKh*CYlnY(cm^l~M8{?#Br*dLak+>|Dnxi}E@D%PkT9qO(TN3!N*Jutf+Erp9GNM3 z#Rv>f!_7C}g4V5DqgAWcXiX$eZn^np+wW^zX;o-FyT2w9T zqj2`Y)hew>;^!V&Bi+jg#kqrPl)&try#H-jce((0(T4)yEMU>v26a)07l&Ge0DAQ7 zugM@NAtWqGwS=JHSZQ%GIP(aEr{Lo34O<&$3>;wdMep9`AB2S^eHI#;_<6f_?RUq< zW_)Je*L;-pc>|iT=-9E-F%NP39MyAb$N4=k@-Sp;V&BVtn0Nnp3z*LV@L>=>5yZ#B zWFPfS>=PaojtEZ(PwEUDXmhGyNcp>?Mo;|9h>>Ida_F9oe;H9V=0a7~*b8S5-*=&^ zX559U>aiDIc=phRis7{vq>PG>lzgVYc>JLY)z#xJ3?DK2!V6D7sM4!tn{!Vcyih4= zRg!o1sfQ$9r^-Bk{J{%Di-(^N3W?fnSa-jROTkvw&YFd#Ln|w5m$Q-4X&5lT4!7NQ z`|-Q)?zZ9f+wYhxtuL>2>sA4#w*9U6vBlNZTa$5smY$K@Iw^Tj>m@U%wvLHSXdN4y z*m}vV>8)ep64m;$hMBEnV&a8_)~gmzm;8j*agw)g=`;zGgoM^B=F~T(H^x~$PS%$+ z%xxVj+i{)S%KlopyL(@8oY(wjkg3F9icFKVZr#nbw(Yv)wQbw+b4|NxIxD$fKXV!3 z-m2Zy>L%^hTW>MB?Qg};SxoKcx8|DP4mOqeOKE-c9m1VjtJb%%9_hw!W`qK`{r1}= zz10l?`~W@8I{nuNLVEV?B*jc``*Sdvc!U2PV)#G@SDDF}cqEMJzxpF#rV?-P$H8Rc z4gNT&KSz%q)wXWksx>q;XscJR);4b3sMfb^*`jUQv`JgGY?-!v`Er$Z&pr2Od-v{D z^VNO(_Gu42^pJMozya;P`|eYDOndt2r&ao{UAwgV@4sJLzkWTpw~>9VS-g1hKPxIK zUOjc{)WdJT{r1BjfBf;oAAb1Z!ykO`!NVVY^pOhRdFP#n-+%vo#e46)r(n9i&ir@Z zeYZJ}>kPR+#`RFo0V_e_h21_sx7B8_!*~8D(sgYl-?p0npfs7A?}&tXZRN*swv{ zwr!hA+q`+Rwsh%IZN-WeDvib`J9q9>#(wa@2bJ+D0t(>Xd+*hrdg>`9fZe-yQvkP% z;p;?1pUQr)aN$D4#l=B{hG(CB7B9Z|BF>#Vr#OH9yjnkV<_w;D?l~1c|NQeRJbU)6 zf^j`xhwCCJ1=mlXKCR*>Po7lai4!Mq{P=OT&NzRUA(3NM9653X4?OSyHPR7x8WKpPQ& zN@to>Kvzk3l=U%t_Uw6CJOM;Npa2-}(W3`KLqjoR#tbZ3vIOE4RG$0>AZrxPKye?h3C^~oUd2amO8Z;Lbbm z#9epY)x569Z@sng37nt9%{Sky;684-<(4a8x*qCrmg8~s?%f;F(a|U>D*E3GKn&DT zjQ0P6f`WK(PszbOZf0h7RKlZToTFltqf+rlrDBgtg&pnCp@WJOF@y@G!c{1hdQ?vK zeyN-WA+2@m*0$;C>2I^;R9060M+IIlv})B#iIuHg_X3PtTU#r151@Mk3e(hnekub7 z47kVGDAyA8M^t#Xxc=|tfK~|n*J@3Kph+vyM75e2l*vD95x1ue9z6Ko+i$;Jxk;)% zHZ~T1etxRL{r&w_t2Q?`N8i4ERY;G(7UJOG09RL6czAfg-Q68-Zf+{%x~HdSbGnO* zi*m_y+cX>vMI&*4jB`KCCoC(6bwH>wL_~eAs;WvITS`g_($dmYw=ihXAQewcOuT}C zfB^325##;*45EyUaxGDR#Q1&K56i)n32G7dsY$EWq(y6Dyq_fkF@`21UXC%GMPz@M zK_=KW$RHEt<>ksCy342ciHL|$hK-DjR25HS(#Tw=5lTx-K_gK#G<`!5neR8&;+56KAFO$I#o%Rujv3^?BtqkJqy zz7YN{e9ZolMx>!>aJFW82?~KmqtShQe42$qZ@|_}!8El-b&I!u|Nbh@zMbO$#wjxH zpM8FCa4^&U!*H^l^N=A!YO}JkYDHAFaxBep5txc+XJ;#zCg(Qlrkif+-?X{OML+lJ z8TCh6WFT9Y9J6QRp*Jq#7B#6z)fN040f>uN`~T{zuQZu5scB-yumE2GgXu+?(hKvA&k;2@GMe(2DlN*MGG6beH*R%LJsfP*X!8o3V|o&sPW z&obf(|ZvZ>Vfix}X5 zjHo$Yu#~y_KiJ~wjfl8p8mmkOhKpqS)Z9HaE%$LG&OsyHI*m=lIL#qjI|V`kFwU`n z2)So$X=G-fFz>)C}E}RYg)I}G-*I$3# z>;ZU{aoxIgnixfiN~Vsgn~@f`Tl!a!kt0W{0he?K?{Pn3fKlT9yRfXmQc9z;_0!nA zzCe#aq`WdXTRX=I985Az#tWS31U7Ddj*IRS} z5E%rj%f%u#)qk>E(YqHJY&wbC|5_>;g@uKx^|Q~HT|6UGxN>RXS(5K3Q#&)+hU12K znLq!X(>#tDI7p=1XA38c$H&JjqjQYl+7{?)269Vx`SOD)Un^E@!6V17&Mr# zU@4ReMZ%D#aDp&RnDi5vD3}Q*7h?hFMmH3LapnCUP=93l)Km?>-oky(pFdx@`&Fw} zDR@Vbp-cx=It3vf!9k{2U(x8ph7D7r{HAH$Yr?H+XV+l)Ap!g$JiyNx;9bq~W20m) z-rn9jInAR7C@U*deYm)H3Sd@aiEFb^02~DVA;$o}wg9fi@La@8I7y%V75is>j?WRh z91o{{E{&zG&0;(a)ZP9cq6_%71Rzs2HCxazUN=TY=X+^<_Wi?$4_5;}UbmNBJukFU zJH1AW;WSVefT0)|8du)$0rls%>H>abaS#D~LzmCi&pAJ*bTZYW0LC^Jxps@*1?a6_ zcl(BMT|_W-1YsXKR)&MSTvs>C&1j0Hf9E9>`&`tx%HK=;lI zGlVyd2=5>ohsJ5-uDkB)Aiwz`jZP0hq~g^zecAPe#$UL$i=hB?w{Kiozem)cpDF-O z@py-jmvJcs!nh%NnsGia{K)oRym2Fytf2AP|1&PTCqe3muHkaS16<`o<8#2&y?gg7 z=+>=UGlpq~Wk`=6J$UWEz3gg+uv}OwqF5$gVY%#kT~pc*UaR8Gfx{ebD=hDe7!cMVU@Oc^KNa}*vajaatre#QqmudNKShvG(P#w zh~)GM`NM0v7S)c^M$MR~RZpF*xp@0&se_BOx$Cxdn7ejs{9|X%_cHYxH@J=k5Sx{6 zkw0?u6f!sZF;w2!nFS@9W(iC@#qP|3rE&=o49aU(!_SU}D6AYllIjzfG_&J*QzV`JtlUv-nI3i+dRg?S-~7DsS7N1MX_y zUWHt5-?=k7bm=N|QSpuv@7Pt=N#`z&amnj+cQy= zuLo|-0k-XiAx$`KFP@|gqM#$vL&23cd=}{2-9XaU^?^XEs^adZ5H>juyKD( zo3Q{(mT!cwZz%3;rJ>zzH=*P0tpv>_dPW&BE-C>t#-^gYG#<0&Ek$zX_a9m?G&a{`;xj$dG7Bfgr)8fGjZFA= zKu9bCqm$tu8jBDKONWm_VM!(Oiz-l1JX{gV{IEC_S5#wYSrtkuY80gvqflI4jmny_ z7(IRp#!Z@zxcFpP+t?vKAq7*#ptHm{^Bb09{-Wh*Sh@o%ha!^v|Q4*Ntta}y?zVUNSXDrPS$W;Xk6cpjk2HZI}ac! zZV(3c>;y~mUa;)j16JleU~S$Lw*7m-zJG6pUB6ziHR}%R-d*1d_VozS>#ilB3>z8| zh0((zkeeQi>Z&@VWe-JYWRm1J77_#cq+|?U8XlAKwwHf6yrfllhejhRE)8je@{v|p zDxM<`nS)D|Vauz=plb94OqeV8p0#s2(#3HDe~Dmf`4$7%wfm ze%5@0o+huC&0lSkYwc^_#b2%l6&ay>~zM+;adscHfH~dm7ofXFqn| zvmbl+9Z>l@_w2(i#l0#lD<2L&{{RUqWu4pKhdul5mu>D-<#+GBPquvk_edJ|v2*u+ zJh1_6}blH*fh*{2I^2lRo%z}|4Q=%aA9>I+vZbGTaeg|n>N_v;3S0li?+tK+9x zsR@pH-CgfA3Lq*9l?5Rfk`;{M6_rTIC`3q95@N)KXXFhP*B!sEncr${SJ zhm6vZl~;m_(rT2Cti{Mt zh~tc@%Dx^^0tg9@M0|uBV#3`}S6z#+s1$ewNJ|e0gSUjiQHh9*OGRu-7Lp5!q^~Z) z;Odbmuc^bB>amzGc^W3w&&Iq(%T!-Ib^2Va6?aObu2{W6-0fy871zCd^?EE@wE@dk ztjE$7>##&vynKyv;X8Kk!`2;pFr>H?9-iJPEvvx22Oh$KgO4bqJtD?^?1`sv`0-;Z zeDdjM@brn3&2asRCy%S}spHQ!rym#U#*9T!P_PQQ&irFd<+!eIulA|ZPT|zEr}5mm z3m8#a3^!{tcsW`M1K{mE5Z+D$;3J`qmm`JH7w&duaI-RlRj&?6Pl``5RpHkgN&rEj zQAiH=K(L<^TwFY1@8k|2F+5v*aC8#l#l2@_4?*sbG8B#)jY?tIq_L=-It4SP&X9gu z223l~s{VJjxbX$jUvJ*FTd{TLJ=nH$uY`M5xMSBo37K|}N+Y|(4c~jD3n=K0)H@lhvqwQ0@?9i{Z;kM@qjw z5ji!ZFnY>l_{YX0EG-4=SFgs}wd-X-wGErL?UKIrJ~8q=*t~`D1GrCI_JIc;ZstBQ zvWg$nIiyO|L}NPtA5f1Q!v7B>`a0iG^4Gt9`~&=76OHSa|MoGy{Pr8H zS~v@lzK)2M1`-qGg6KeJ#00y_x+~&BToE1Yf`|YIg!kZA>BLKMxg_u4vNd|&3NKel}-jH%NWvi*1hzT;Nn=yY8RxDnLRqHlkcDox#wTN@n=q9n|J{VfiCs@g_oPSXu$VrEc4pyZ{p3j zF5%Lj-xl7%JMX@a_ul&eAN}R8_`knh#^uYO;L}e(!$1G||M2Jk+eF$A z@$ENX;`6V*#Im_l5gX`&oJc=pMED9mD$I)VLuQmeQY4-b>M4Q{aRi7Me4G*PW(hwT zGZRk|~cLl2g(U7M&{d`C94gCt$2h<)+SFfTi=7tA4(E*&;OT z*^I&^Q!sqRZ0tXDzl_Kq5)P|THT&sfPoGfU;N@3d6+xU+ty_hLoOwZ|pApWUJC7I7 zzog2)^6Kk~*WY*xZ;2q@e&=1h_x=ZX|AP;e7%qSEy%2s&KKtzN_*{grY1Mqh1vnvF z44)n0C-@^f(jPfu{OqUzq(}N9G0YS3!EPb|Hzj}w4=eaNSxEhwuJr2(%|}N1mi;W? z;@AuBUj5qjV1bPCs`R&!2svNhmKXF23?wvsiwJSIKK{G-8ye=NrafeYL5qaPi_r zyz!??m_20_qNSgY71tjZjHaQH;VV6qIl zrq7%W%Fgg4rh1z@0K3 zY`oaRi!=Aj-2Xx4O(-6Pn)4r#vBu+S-rvmO#*kwWhMec?b91Q!z)Ch!l*h$NFM7X!3gieia-p2*uz6>1^DuxfD+jq8rx5GfVTlaylt3%5f z1jd9VBP-es!7{M1wsl62o@N+mX$MOiM>x5P7`y^xS{T-Nxi=(rq zVOYf|lnft*is~`yTK$yz*%&)v8tP}v!!!{F#led|lcvtb6d6<0&ycjH>-IC}G+?d_ z7|Fba%h0f71r{w`iA76RVaf6}Sg~q@V)fchO_zMPV$+tLA}T|6s5G)+(^hO0Z=}|@ z3Nkaqdt@9_nw<+@XL|&@J0jRa!X`pI91!B^AaMt2_15rr zvQ)-*v*`y%3v)QxTDP2OV0c6VQli|Do)Cnj)O^@Gd!k1#GbM!HX8lA64sdbzg{OE0 zU;l9U2Sp()G6_+!X^2hCLVQXNGINWNFN4RyLn}~RR)vZYqfsPN$8rhP_5bo}6c$%v zNJ*vSk3=ai8CQyEM#xyBdaSx1Fk;krj2bghm>}KFWYmqDDnhEq*opO+FnI}66q-Q6F>UH}<}**$MQHUi7f8OqP~g=^6_=24!}UfM%6bDU zTE-3062>QIBY9AsO7jnnLS*b949X}#bbPu>i%rZ#T(WqPLAmNuaaz_83?kV>l#sG= zhcf#`s8~O2M=urZrJgR*h1jL_}Cx zUegTIRi27B-kjo%DGF7kSJYyJ5D^*yXR875uoc&Cgohnl`~Zck6ky*>S z6dW5BiwRYc$j=EuL|hVr!eS5|n{<|WM2vP*dS-s6lZ)p^UAlHhzX8@t2#$iQyPt?5 z5PpFX2oPga458vtf~30$6m0EX;OQHJkf=o2Z=51HJYIyBfY8W9ghwSSfspX%l;(J( zj60&@MNsh~ED??(A)`?|N!dzdamf@?2I7;lBrOXG;)xPu;2JMoR3jNkNR{+drZpkk zu}ovxMiNtVMT~hWG)zko0{wzu+s{ml+ZPT4`%Bn}v*iGV(>X?eS5)57--i0 z)A*Q(mUS~IJ|-Sh>k^SMC=>}Y$V(iQ^@>z}Yh3__6bvo5ic1`{(9Xf-qJ_2HzXw{| z$#}t5#tx2f_3(kar=RL7=pkIilX&@sz{@`b-ZG$c_YP2E@$?N=cuSry#}+{m@Cyo8 zaSF*dAY6rhfsv9Ii2&(x0>slOLLw0u5-kFYK~PA{_wg_Z6^&^so$H~E1gcQVHA6AR zDuQ{*TL@_igTrO4LxF}Tz}>|I{d;wXm02%Xn)Ojw3D$ij6fAm+(R=ko|L)x+ulIY7 z_I54rc5t{<)bXPmF=tkdw2*Z5HEUzT{}6*Ra{OG~y$@R3IeyA6!p6=CwtSjc!u|uT z)cCL>)qej4Y0iA=IAol)z-S@JrH6l@dke!_>Mr0FKWoj%2F?D;LDfwOy>O^DX2&p4R-%aMQ;gZoBO^MVmHlzT?ZM+qP}{O_wfR zF3B;?b#rrTc^-g9q!B_xL*ec1tt!CF%S-Ya3m6)k>taMtDK|-scG|(g;op1_Bj4*o zqx1Fed_yN+&nlu&Az?bpiC7f;EyA)~*A2}ZHWUbr%#ecQ@$fb9p`k9Qt+j@^IgM+D zy?d?j%rlmlF@r+0L}{rt9)8#gCr=K*m^y3qI(NbsPx2UONQPV|dOlxGLyXs1hHovS zX!$OKu3ft}f2o9VzNVH!)oIFq&%(l@WsL!7SQ>||gOyHWur<qu=)!hWZ$agv4t~EBl9H?%!`SMX@-W^jm`b^>C;E;Hzy|t z`T6*t ztDvHyLdA8suETY{vzV`}r2zQeBHhs4yLVUc4dgw0_Ea#g3qr*5shn@inFZ(qpzG&5 z_*qG;dYS8l2^ru79~hH+Xn>xH2qVby8B2dQ~f3C|fS~OM}y`(#X22 zrTf)gG4pg|Q%rmXEceZp$QMY_h$K4NN8G#v&Yrbu!VZ}kj;eLbF^W)}F>-{P+K>KW z2flNdf@B$nT-QGcLAXxzk2~ofhhQ0gbc$Ys24}ZJV>2{{9*a;26oG^-=Q@Bc09GI? zi5`Gm0^>S#@$9c@cy<%)tNDg*-SBL|G(5l7K@m^@Y_&A1{xuK21CVhYt`nBg-w#|| zT&%hdju+T9@YV7BLI@2)!!RBf=ZAalal!fXj)Ifqxgj;h3Dc*0NEvrAexCY5m=3?t zMFH~s7;>HI6DCY(&SyAr;zWg>#t&%Bnl(!u2My1$4Ly?%*L4gbz)t$j4Oc@ z0Q+ykitpF2pK5`uFbadlr(0&Xz*o5HE?)1qX?V6|8j?oW`|P5kA_ZHpAvCmcn(lH5 z_s{SB@HhC4p>gBJDWgo8GNt*GZ`^-bSqK^${IPJMzZfh?443KzGX)&Q?r=vwx+L^*CEG`%)d_b;1@-cT9uRqp-W-*nq!7 zk7D>#4+W=pGdk>AbPU%G^C@J8t5&VLYPN6RuC}Mq^_h&0KJFkC0UM1j0(t=vMa#Gw zNC8m6JOQ2vPk_c{UrhJMmZn)@^;T%udg-odNcN!= z28E!zQ=+?FVwlD<{Br2Zl`FAk%^H49Q+-u)^XAR!OQZYu?^mC6edLix)F)Z_IoOw9 ze)%VJ;lc%z>87CZ`59ZDJB>^+(u?TOLlR!EVE3tuf@0AU+jKXBw8qHuOw76axjIdxI7y03LYL)J2T} zc;GxCo*c!%!Jj_jr_nh*qnBVFJ%>J;=ah}3cn;|F0U$kqp+E@ZI?N|b=O~}kFnST9 zkJ35#n?8NI`W7&SqI(X0j#d}T55aFC@>_uV_lZsA8^-mrgkt3Pj_L7eR2r9K6JuC^ z>=yJf0=ocqqauQq(FJe}z?0xOfb(5WVHoEL@x(YDpcpt4;JAP-f$4-5K#xEVLD=up zJFu&u$Dk3KT(c7|zsIt9pqFAYl(uud2nq8nZxN9lt=5kkuu1k%8C|2!Fb0g8Ygf%95j3=|1H z0|mhA0lFYKLtuA7IOt=yKvA&e=)xep;KOu7!RQx+C>+887w<{vACJC>n~$ zP*ld~!qV4uZ$u2s8Dm&ZhhpUUu~pN^KZK$2^$S8$k1cB&sJnf-b)FDKKzQw&C&hk^ zil-3R^0^Ji4f<6PBX04D;D=SWon#ymuh=({e5ZaUerOvcIP%&<8Ox zh1w?RL*>Li=T|rB+L&%A1`3DU=u<7Cj}I89SU45q3_~Am^0#y^!gBmw<`W9Z5RN$r z#YF)T3g~K~@N{%fMGVvQxM6u+pzH!^SQ_}hp||{|djc)%QZQRHT^$WXFVJ+6=PdJx z80{u8PNt~+qjv4uDV6K3fi93@prLg!P*B_lVe8;{foaUs;X2{(>GO5NxDExwSR z4$*~VC@AA}y{s`TYY2sCDvg6beUMDp2nY>M7-v2W&pU#hJ9qw6>a%6t3}VHzWwF9p z$x=bDNW8T!07JyZTS=LPW@cs=<>ddZR}5PLJ%iyuiQ>=)jI2mK&oE8zQWzS>8R}42 zhUnt@B~XNhab1+g0%iZttE2iYU}N;##Oy95{+*e)r^>saN46n4&0~v;dd0hXspz(PQ{1|nc zHf1SQkA(f+x!T3lw$~%tZFk&`Htp|1U+aD_vyu_5 zm9!`cds#?JV`yoHUIV2yN;=beb6rBFGhg41p(@Kf!P(ap!zzc#ZZ|vK3!gRef^b&WpA$|ACxo+-_LhXN3YX5k|NcwQzx!^cseN7z zrjGyDM7zDs?P%Mn9sH9*;GYpA^A|6W-)Q`yS1z7!=HkmQYR^9Vly>5oC$(cw9M(>rdRjaE+{qsM?%6YJ z=ZmF2eKc`|pGklQB>8-!tF%CT(Ca`}zM zA2JresZ+AWwIEA#XmiDw`(Dh>2ewVef$cMd>DVvvy^HF<+_`k# zshLwJ7qd*00P2KKc@Kg&n|Ogq)^F7Xz&nCZKKZ1&Aw*6JFMRmnY5jbz7TIR!^Dn)G zGiT1=^y$;h@IHgSZ{DOb1dbm&`C`~9`h?rhrz9lCZ@2BgtxKteH;)D6ZxyJjoH zZ{9eiSp@g&oQ30uSE_hsMLNQAqm=P=;V`TiUwMT9UVY`l6+Ch5h~4%bTdNi=UU+oU zlquhi9y=Dbbz_l|9FO?uP{hVWBRVn?aeQVlE)GcviHM1c7Q^QuK7n5cPeMXsih>?s z_N?ja`8%s72jZniOL6{CDPDZE0;l#DuyQ4yICt&?{d}$#+2-9H`}V23oXeLlzbg1Ff*$A1FyRhU z`~Ef1+K2$!ivVauih$?>pizrP<)U(I0p={M!N~DLP(5)d(xipwR^_NP8r*O@?uP;> zt{rxT0M0yjTygICXS64eJ?c?iUG?Sg5tXQ_sX^`N(U>@SiV{FXXdrg(*o31`@ZY~5 z#m-&3v2MdgY}vLQ)m1epEv-OKb{PBPJ=8ZV_!t#;g?3HcC788q*N$IWzs=Nn{2Is@;7+vb++Gdb)WA;$ zgB6^vvODnCcsKC;t%fy9F|5yFwkv(SC*JyKQPtz7HUaT!!zFgb9 zX2pM;+z-(t0B7OvG>`#I*F*rG><*YVq^JnxA{0I`%!gO_d;{+kvfHBPAavt=oQ?U3 zNl8j9IdWdv0{3D3`&Wa0ET-~)O-v^K7Ce9Uly?67^V;zfPxcRwi2P(wT80eflGNS9 z^71k?ELwym%T{3fo_nxt_4>apShD8%*& zokrfqW*S5G)y$)qY5e?x0_)Q9@>}%%nL36aL*Mr2Zhy6yO#Cf4fBu|y@#2MBDk>^Y zczF6^8ejA{d#()NV$_W&-tsLh7>w|+@JnVryPMfqSls00;eK~$SkxCmfx(B9l9J6U zD~CI+ShmD@%Z63AY~8q0BOBH(*OsmLSsuWUHUj(Ku7bJH!_nT}rf;uaPuW;nejOGX zs)WGX%97Xkc$^rUT?jn`AA@7ppu2qGiv629h9ARtA3Wy&h74Xc4-XIRrkiduxxXJm zo87mQVrJh?J*Jp-9zD*i%ZWO(E}se*-N;v?`*eAywojL>)qT2#R`uzs5mP(+Z@l@& zMQz>M)zcjuoTc*erQgj)b?k(EBJzKTyTi5>@0x*eqf?GU2JYJ^&IFKW3@qK&uhMkSg z=RV%vUrD*s;sM@pcXRttJjOS&{vQJPH83`~F{aM*YS0CcD+2h-RI|>QXx8}+5y8^2 zW?f3hn03jKc(t%i1o8KpK3yRq*dqe?$ycXNKJ%1SczDDYzJ5U%GPGDKDG|QDGNAPG z5to|sMM#JYIDPyeE&dxZ_SeF7X-NTE!k$@y|Phz=jHX<)(h*jP!YvjqeKM8JNhxQvNB$Eadk)K zh$;*%DnU$49AqH)*@n$qI&4_KR$DS}O7nEim=J$Y&CkhiVztY zt=xY=K+rcbmv>sVW}UXAVYZf<5U%w%>!F2*25XTK;hGGpw4|i?|CIoC@%ckOfy~yQ zk)Do$=H>|S_m7c0J7JY@q=^TFIl^dxd8YRFi!e60F{aM*YES|YBO^RG0JS5^F=@;w zESxv1T3fenm6jSW0_byH2*5%3O1~e#8%CaPZjeFW`;y*#!{}OK zY;a>to#)k{1P~P9hX7w6EL|`gi|5V4ibeAkizGg>d>E$p?evC-05V4S?82&>{zefP z@94*fjfwuq+b2MM5+*!6N~V56m@#Yi$gSJ9Ya2F+i`lV5%g@cw%zJmc9t5ycQ2+Bt z_s@S)k?A12fHg+tt{KJ#H^$U?UJZ>NAUqUt(UDluFc(Ya&%(x4%dmC*YOG$`fV!HI zkU`=;87!9W$+**6x`PjepWFpZo;YfgkGId?A|fK+kBy0ayI^qM;SC$sns48>S=+jG zleTl`Hf_n`g_@mJe=RH|=zl2y_WfV!gFpUE2 zE6h|S6Bj3^=Xs-wgFm`{Ufzw3iGi(^)yI;rZWaG8FgCa`rq1(f(D>#nPj^>jrKMus z@#U}zT?SGY?W_EGM9>ry1 z!B6Y-V6k$FIf7TFVapK8< zF7EgB%3(zr_9Gk%6%}83D!ycLGr#$jUHozFzgb9HY(E5xuS~fo$OG(neeUh*`64dJ za$;A1MFYdkf0k9dcHh4ja^=Y)=~vqiO}WTx7|I^reDcVB=Id34r9At^%vnBYC+v_| z|9{TWivPNzu(hPXc>*7`dk^}7N~Ql4f;KAIb6xmn-|1DOASLl#3V56YgQu&X%Q~lo FCIAst6$tXLYac zQ(d*st~&dKE69l>!Q;UL001OO2@$2Q*6+Uq7W(Vl&u1|H)esp;D9Hi<9^_y4Kmg$R z^Q%1q0F+<>V3_Iv09PsifNi&|35xr=7tUTn;}-ycfc)P9CeXDj_+`X(5> zYT#%Bus5)_MOM@@i7d#~^hkRMajk_%7s^s!ZiUUmG(JY@rT_Q`?JuTJTy*nLdcEw>d}S2nFUcCI!deg8&;^^8A775>|-aHCcYEXW&0KlG}gO}aho=AD9FoBB4|Lc8H#f^`! zG8M|wk_V9hrzwKLbv3JjpAh4%p{0aqDBIx!d`k}I)!wyenfgIO!kt9JyZjV=x z+WOo4y~07LVZPG{J^W_RJN%1NX{ctw#;-9g?f$R=_n(GuL4`xi&=RB4Lk)vJXUnv# zOuQ%BezCfGM@Lr}>bEE&uhu?@eF3p`1Grq1G1YXUNdbPLl?QvoU(i zg;wx8H@4wnh-F7@9-s!fd3eXAzI?Abi%bGQgqat(}XUgG;T|YhPXPXQ*gU4Xuyn{ns^3b2EvKehI%mia^GVo z;oJV!$+LxMHUFjh3dmC}d8f<-P^Dl;?BN;%BI6Q{^|-Qj zM{fSmY_ttF!B5He4t252%5S0dt3bs~{`dxkik=Z1R?70f(PV&W5gWHhd3U~EuMxY& zn^Xru3Q^}VPE~kVOpUU$;@{fdsP0B!Jq@9Z7GsJZ&CaCy5v65l0s3q9pA&C(T`w^*yRTq`YHhIK+U%rKeDYmJwsO!OxqAj4sDuUS!M z%!dG*p6Z!(u6F^ct~de0iKyQ)0t*-XCB`Ym#8lyw$g;CFqAD}doaeuj*25-~mMii( zk^K1+F~jc*3!NMzw7OF#AvY%K=EmdbC_P)_+d4K5V!&`mn@&0ZYv8go$uh@tE=lDAecq zE2zAYEJhCun;C8A_nzdV)zA`bG2t2je}sjP_kB$MRKdH=_R;TXyC5<`UKY5y zlM%o|vLr$2B~lQYguGJ$mz<&o(fu8oI?yDNQPOrx|F0Atl7_l=a6q3_St$Xd8#oDR zqA_Y}aM}?DB#bC26BTa!Y#CF_cxXj~Ewz<+7`uN#0gUyz_U z)tDm53OM(nsC{fD=eZxNg;fu53DcFC6{0U1p&3Dr-2Ngo^I)FL8rP`{+bolXqlDX{s&tOnn+WCnKlj%Ml*xTM<}tEe@bED>NRP=dk$ z1O%Bo7Z+eCy_1VbwziFVu_Y95-W~BNza!#i%*p+B_rl!>U{s?NLZlLZxp~~qbCCHjbv#3M&uX|^V1d^6 za90_zX}+dzG{4ZC(34Bf#}YtFwO&BPwJ{~3@nv0+mn9~Rt&ZJX_abwXZsb(n*IaRymgvwsl*Y#URA06 zSGn9i)VeQNz^?L9pn5cV`dASbM2U3?EHS{3<{F#jpamTSiQ&r0AtA-2BNKbmGx#^4 zD8%K}wK((bKoy^9X?JfdWMsP;J3CKyT8);S%v@aW8QHuZs!}=2CCK$nHtXM<(VM3g zc}rX}w{J;MgqyxUbe@H}KgKruJre*x(7Wh@UNkZfj72e?^5`(3;n3x6AIlOlx&k?&uQ)-C#-XH(4k+bRXwVidxN}m)) z&0eU7A9Fx#))UXY!eW`e&#l8MFAS>(8+G1?crKn zW1{n9{M8y!f2(wuRQe#lEH@fIPv)%lX+Wf>k=PTqShVX7S?gmTzuVe8bX_vp-%aD{ z2DT$*5}mTR9v2#|7m1rq#+$ZJoMemZ5Y5w|zqvqx5UPw52Mt24uIw+teTLXu5`P&+3tG!aJgNx-tJm+5id@t=q}@V z(R!%-67!AQDVFc0)ONGYd1FiJq0wVDU-XlTn%eVpq3kCN-`iC`gr4{PqW?7C>se1p zj*rLA?KHpIt~kisX(v&hPf@$WO|jR>ChHmG`{v+HxQy%Q8)cQ3GVC0UfK9hhQ005o z3q$p`zWt%97qbp5?ta|zy#Lq0;r_Vkiqi4^uudf_Z8qXK;dxrsto$^^eObz}+m$|T zwBI_;mK-A1nqJC}0f_Fj-xb8e@7FkcpUa1 zV?g}XOSp%ZOk|Fg5&fYk`*p*Ffdn=1|2l{c{k&fv%AAMpzxn~+=G836yNNFe+OI%7 zb}!v5+kb&J+vDU10hg0R@Wf%=m9_{fPm0d}@zODu#fAAAKs&%dI{?uTw^WS^)_~gI z7>8B4H26e2F!P`F%M>SNK@4tA46bl2lL&uAdHnE)@1z$7c%)-}P_)j#9~em&+{0;dZGE$Zu$BsvaI1g2uwaVqj-iOG{5LbMEu<^z<|~ zHrBs8n!S(0VFV`?k5PU#>L@RVFA$4XQdU)6s=0M_*HxNUuQ#1E6)xD2SpX?sWN9_q z@)8ZbYe%_AMmITcq$DMbhH^&aGwBJKoB3nPlD+w zLUksQ!DgeJ8Ahy3iCHaFwm|CmCsyho4XRQGtCeY1O3DMVmYlo$a}8B(-b{nVPjb~J zmq`Q$TMLV_86Lj_BuW{h*XOFcDRXj!GI6woF& z&a`_EPG7U#^Xa1fQq?x0JT@jKBsMm-++I&lZ?W9-;Vhhx*P}F!(4Tf-Y6^L3YRdlC zufU25YHe+8CB*T)wp3JQvU3CB$q9C#68dmlK5K56pms>oI~4B8;zeQQJnytB2W zq@;wbtjea4lvG5-@bIu+NKOvu@iu7Jr#cY!|9m5(kLyFfBLjJW=79G}p~`8Y=;?sl zGC4S7h@4mlfe}Z<$OmztOs!R%;CCr$>108cRQXGEEUaQgd_ux%s_db{_*OYm3~cN` zv3zs$r_4+OQCnL&5#w@sIeGa|Hv$u~$+RfuFD@>kt-WD#SVl{da!(Lz?#=q@=wc96 z&u_+s%jeaS@B8_&F6-^(KyosvlJ3y&QKs3r&vP9S>hpV&kdWj|=A|mpRG77sQ z3e6KcCU6;m$Uz^W*p)!?3H%`T8vKYCE6lU7Z!-0OgZCFJnCsgZNU1$_@ zj%;?l+@hN5%Gw7E{b0Jt@%GWzCz|&$;aHt==~o%!&=92;-WEBw>?a2K~l85ddCBjh)(zX^YyZOc{E8< z7X8!|x2gz@TPi+V2s+)RwV%(j}da#O;{X$|CJS0%Bjnc+WNx3x_2BM)?$oTAp#wOVDh58&AUh&Yfm;MNV>@t zEjz$*PpriaB?QJWRp$qLXtMf{y{7Z_XC(J0#ZP4ueLQXEHXrToy$`BV{x2BvP(Se_ z-KVC@M0#5iMejl3e^a;wb4zwOUu)!Ij6h44`KH27N^4tkS3@KmimU=bcZ^Ku$ZzN; zop^fxuHwz|iL$ zcwEZ0|AJumT6v2@o9;8>rN`hraf|hZ!MY!ku2NcZ051R@6XshZy?C*_ z^P>Ge*2S-jjn=Wv8tqo4UhW7)6Ry&^AF$W3E=+?j*)qsSe)!~o^zyRRfRNobVkWL_(f#^M z5RY3e-qUqk=h+E)UfJ5QtmfU<-GIBno|)R45BO4kUuA6j2tQpf$g1Pb>SS-n^9pV#zSS|hsv zEX{W68or_nIonhKxofx3xKxK9kbNzDn!gSB=-);j2s<1|lPhztS3jBQ+9|+JLT&dP zzcx|!h4z2-EPMs+@uM#$#`|LNVj_`Br{i|s-FQ~6ss5b?s7QlZQ!De(*+h39fo=U! zD%y@w=?-{!4N1ON8!+FqvHceHoeeUd$0A`@ARWhU@XfPEt+ff=dd`P3 z<{B5~2mEG>9`LZK3FdKpF2lVQPU$TiZr4#)W>wp#7VC|LZtU3KZcX+BExpge6NBm9$vF`tp+ii3<0)+Mw#-xYko(nBsyprV89_9n?wOsjM z&JZq!d`?xxmWSt{i@hgj`zHuGCunDf=kS(drfM5p1A?zjR-$5#J+({hci zkJ;iwYx_LgIS-uLZ2#OCUW1c+t}WXzUE4jHJd$s z?s}mlKTM|+J>Qrjw<&`W^~>|3V)Om6f1~$WoRMmvMc@4J7|xR(82`0C&J;;%ayhcF z2-gjO1y`k`t4(s^{;@qe0LeQ+D*)!;NukFN3+tY*5U<2IFJ|c()5+AnKa&7 zq@*dcAN|JSChsKlLO$&$p}yMQN5KH^L+lVHBOz|=1+C2kvGeyLvUC7ZWT1qUj0-Mr zc2tNepg;*VF*ADM3~Xcy1WCV5ba!GDl3Od0gn}P97LU$ocxN1i!wzNiUDar#@HmaZ z8LN2mckFRJwqXdvuLm#X>CDKo`9mp62T9Tiiq+QVtE(H)l%RPt`UD$n!;=Z9w4pc7 zNE1)Vj9%}FnoZDr`Jut}pZlu^-r3CI!CP#W4>cEM&I2?==r2d`+c#LEu zHH=i1{8}fRvC!C|u)0JB(>|7gf$x2DW6*UDl4&&1S;Rx>?fSR}blM?M(yE5X5sm6~ z9=uCDwXNBWR2yL`R_uoNZPDF-t5SLLYD*!znC_`5>5?00l}r&-tJZ@Sl}xiL6Bo6y zEti`OJI~BcA37z%Bn7vt&-J(J@h3c;{6(kfYR^_Nmpa>fo0`M>iq+WFGh9N7B!?sY zw`>J9H(8;R3Y+(Ecm!{(5W>Ma6XsrN;i9EpX8PGN2)?dJjM-tjM9l@F1651y@ zuj^*%FPGp?v^91nr4yLdEb0iCr~-gM%1clXSh_!swrWh_ofo7i8IhE>o5zKK!?^*! z+iPe)J&TyOOG3jO%LwJS^j1+yYQm7YJQW;GPSuS&V;PJ`9)}a_e{zTPnlS?lJj%+^ zgO$(H45FHMERq!Sr)g|Pgr?J4QKHgnMuWeRMn=a&3lfPw^O@`PnNsgCrSJfR6-5Q9D6?dy&q%+|B*8_%KGGj#2nPoYrSx9pqG zk=P|&>o3445YWlNfy1~)|FLUuSa>9H>okOXAsq%sbxa|Wgq>Xq+D&@MY&2-G0aBjt zxU-V!5@&iu-p;@mB>afET~bEN74xVJ4ovjoiGj>uX%#(Q_ciNEpsQ2?o!-4)%X+jr zNww!{xwCrP4H%717qWCp&7h&d?4QN4V7v8J{ETMaFtFf3`(KYD4wATpTkRmvN8Y@s zhajBCGP9W7nR#)Qr?X$v`>p5{r@JrA(}Zffb^G)SB_M3E7`$jdKLJd~tY`7lF$bM> z-SeMCS$$>La{Gh74jBtuQ0Y>NuWH$`UwY>2=y|@q^hgUKp?JdQ!D!pa>>3)tN7~xN zrJ!aQSN7rKNddrmff9($!nv^!nVA~~r#tZUj67Oi`o`dfgPkR2NCH!SXn)mW1X>x) z`^=SPvx81X7w6>1{VbmG7C)yj*!kpNPNTJ4sMzd$VYQkr6YNvro6lEhiRTHGEQ~{2 zc0(U8&)UKC(euUt!E?05{kiJHB2w^0t<|;$m{3Do&P8mz(<1w3{lu2X8k2ismz*mj< zwmvw+4NB_Lh@uYRefs-zP8>8)*&3<%J+L>iL{G`~o=L95&nV!aSU#I85PLeazcqE* zXl%h!M0A+w_rSo28!1*@ox_Rm>)zhp>E5;TLh6|{SYj6y3os)_~y_$U_ zULQ%L^yY1Ml*>+EFzk0P)@_$4UB@-Ba`HzThW0e-n&9) zbmJsh7$o3hGG(Yd3kjQA+CsurJU&WACHhGSBKuxMLdK+VaH}%69NTZ6J|>&Zi6VVK z_w)U&wg{nar#E^;j|VO=b$TE>Fm^1=tJ_{8x_nh0}e(m zLZUt-8JY&uFnp?H0QF*P>~@V{?VE`{5z}tqUlU)1AvpoTS}4JSS~Yrxdu2|R-HO=B zoOGGy*e^|>DPC>u?~jnUDX^a)#YtJdLorHfNPJB*pHT%w+Oh=qSF!;VP{@ej`>t9- zOzw@3`dyDBjaDc8DVufgcicM%+fK!UQ+)Qz9;3PL`tI#vi}eqRy3ZcsvqXh`MoEeZ z&%K#>4jR%oQt_Oh1iUUn(Q}+n-0yoD9ZnDbxbixx-S#J&PX`^zw@B=|wIT(xQ))oI zr&Vo#`ta>K{9XB5{L&MtfIr^78d^Tm0BQp#K+;Vi89yh8E@arlCjscty_a3HZ3r-1 zm*@h79h{zJak00pgIZSxK{fQBKz|r*W1^!Ae8lcJoFEYjY5t68r}Re{GO~VJ4vgx& zyes(FJ`r3e*$mE#2cejWLw8o&V43#=TJ_@U@~Z8IDy;PRUzQ_3K=aG+DApUAS|PXI zzzM8$Xxr3d{qsRW-As4ogO6BhJE3dJh0M>|DAsJ%+Yb-q#Snr(hQB;|8S7RAXedQD zWDBJ_%X8|*$gA7GraS#4lZ8?Lnv&A46Xs<2AwX1jRr zTCRTEnsSLYL&d>N2%C_=h8*h|np*?vwEfPnX+@f-I@{f$F}%7;v@no3|F=~N>C8(+ z%nF0s8a#5r4;wf}rO48PX3{TKW_6$V737W_eliw$AQwk4P$KKTr8BsH|2~>#DAicF zsMDOoX9Z?D?iW9i-i?8PML*m}PF-t%Ek&c-gg36nm#f}rD!fU)J-JeA(HcbI)rnlUXIw`_9V;{^J8kvzqx=GT<08k^xfc_psfq6 zV{>9lR)dH16IUAbe!Kd8K&OU0hq@M9wuQdAIza-7b{rPAU(jEk6|S}dY4)Gwkm&Qe zB#_)3tseb49RbaDlwJ@dsA$=KVMod$!)7FGKk#ump+*vWjk^6!zWBbtY8;Rt7U6Mta=TNb`^pb)yw?XMTN&f9lFHc=S<;7p5d)G3hiyzXN9X zOY}DeETeK*gu~5m7Sc&b;4RpS<*+GP`~!^E{#s0!WmiVd6wBmLJcA4StASLsg}(OD ztVfh={Rd zBM2Jo&f+BPQ@?wWf%hBk9vwhvd8&yFk=cez%ix>aTD;nnOl|6$8bEN1vvY8H_D&`m zqSl~Nu1m1@zJkXy*@h)CurVTp|7fL}WM=^1o&6ypc^Nyh!jg>jEyq7i=K<5x9hkfs7Xbo>v=mohK9=U;!ml zQBZ1(6A?@TG;lb+@<^+`QSNv+z_621ET!tKgclB~-C^L+yz|d!Y;l3uuwL1FrOc#V zxsf$k3}lax!Cx-gISMn6s(=;ObwTYjL_yC>ocah_Iw1V6D zqE1D;f-qIhM*{8p^c31FA0^8O87U4IchRR;+vFV3?ur^fvS-rG7|}DM4%8$F=-&t8 z#g{?~36UnA?C&yNgoR-W4o1|9a747+X8=`FHwXH?&qgeg2W2;%PrO0BSxdLoLA;r z4z#*f%S);VHH=b)yh&(ARmO%68B-4hM~V)tNW}MWJw-&Aw`E{IudYR<@syxUOAniE zcNV>z$|9Too8MU_yWjqefC|(3ZNy{ZH>0+u#P65ijd{B@B2pervGo;)eA4)@(q|-< z)&%;KCE?^}o6ADcAR5SHv6=M=rSRZ?B0N-^LP170r=?95!Pjf&RRf+gf$HlP92GNS zLRy}wZ8Ml`QR~uO-ZotGIBT2?mB9`+n?qaYe^@-~$4jMMlj`C$?3f$u-08U*ZT(-h z8dGYc^R-@M_+8IufV^B=p9Qj@TLiiXx%#XsTKsJY;vuxJRep~v$Gi;?6Vql(pLy%gbW7rTvwcjLdny zIZmO~`dG;m4n=U3sn^lg#>;`p1zH5{?C86Bc$C9mU0ua>tbxA_9kk)h>Ws;v7GyQm z*T2&14U9a#%p7^Wb8pX#F4sp;DodnIIg(&O`9{{#EVByVh9E%+kunz&x$}4-dD+*Z zmzUGb)>A}V`RlaPT)o{MlvWqdOektwheN>7*xwj~qVO!rbPwg8_9O`*kVfN9MaB;+ zEW&oV#;>YOFle_;X1=nIFZ9U_Q_BlVrFhQI*pm|zc*{6`Aw>==T?wyu0{Pz>U;|}a6mzEv}ncJrrwLSbpp4`dH%Nwh8T7-jxJ5i}}%Cm0W zphuExmLj#PPPtI~H8?T<1v}T6wHxeOBzi=mqzNMu^bWT!9G*-0$oR&E>lI71!<0e} z12e>Qmi%j>-=3xn;1f)69CcoaAa}WD`7R2*2?9)>W`B4f;bm?jM^Db6de8_FD?p;L zWuF3m1JwO1$vglqa8Xt>gZ16TyQ{LbH3#@@to`*+^H*=0FrorrwTpA5Ys)-O$O1#G zEpqB&p8WO(26-o39J>0;J!-?Z2*fO~Nd0ayQ7D8SBXBicmh>DF*$4@V1PnI6Gm;!I z_YceIgDkEPbTEjfV~Lm3`D`~@Uc5r~hhx>uE#CZwqHviP9A>Tv)@PV4>~)0oAZgaC zb-0&Q_FK>-z-m9gS3c=>dX!;}z!xx>d$pOkAb15=9PBmvWQ3SW7ZY1JRZY`TQYzF! zoeB%%7hBM!+JC-nq4)-FQN+zE0KM%d9CP5nXg6mD>Tv9_Lp!N@!saANi z$${XL4*TMAtVn6Z@0K-GwGK;;ac+1ljJr~@K?VBn48}UZq4W~CAj4dWIJf;4Yyq&D zs`{eaoz?AGz<=A`Zg?TIVYE3RX2EmRGn;>c>oMnd&h#vdX{S^tx|I9vvucCqzs>L5 zQJ&dyusgjwMsqq*rM*m!DfW*?cd^i{^$E?GY;o z_tO;+ndP{ERstw9$ooFhDS!4C_j!sRGQlnsj0Ylfg!{Z%;@C=0U+)B>;F&B}>%iN*?}mbgLP0*IZLDmt zRgr)7l}_;*S!5xD4D(RQtO=$e6ebBuXKPugVEkBUmRI3LiN*$Rg^{h#t)tJ{8&ylU zJ&vm_Bp&iGpo4Q;f9AL9ZsO^Vk22{U-lFc}s9#T|y&zlH6C;(=@l{*XP z2cygGtY`!va&Wk?aG}))jhTyURNTB4>qxQ8x-@?sv zjvcZ@=?0k>I3a%&%VrdJLCNHqX9wmKEDku5KhcW#ASk@@)zx{ux2UElM7?b>@)AZ% z?K@fWyc9^Xav_Yj5GUo{tz)QQxp%A7Q0M3xw z>sEyOljND@0^E27&>_uW&*)%byw9*G;hP9#ae}bD(yHlk&I3?`g*kTic;U|X71x|0 z&o#kCg@MPBW2my~Yi$S7VLqI~cjFccQ9S%jmUz!nNBDZ}L}d3XEyZ8SrxLA0I7P^y zY&^v8M8&F;N_70+XcdAgQSelmnS_PYrbGHY^0EJQLX_nU{UQ#7z{N{= z=jHWh3yc4;fF%i{qCA%5cnZ*7Os*i`;=J`u9%kaS^dG1vCImLtb<5u7%*b@D7J)a0w|CTq=&9zelR(~I#m=k&U zw?*=prHc=kf8|nf^B<7xMfWSCVE4?gLgzb3}A*$-*f7QMbb=}8pm^~Jd+&<`XsAlNNfPvRMb z`}N=~$oUMPyh;3|t&uPSmMH$1n9~O1Y>6$-HQuZ>t+ipX2dS+c`QHim@l_bD}_p=~#9_k0bme1z{se!L^wKHRrjj+h_TXy?w{=N5+!74&A z7E}J0K!@Cbo!PFIZe?XRWhR;&tQq;aH{3QFUFPfG#AV%=r+q}D z*AA?xZ=ca3uC9^ovww7`pT0x(vhxY-=3qd^Qs%t4={wfKckaLK;O-+lQqqqiB-w!P zVpil}$0CggcqS*bIUhbvLDTfHg5d5|=EeboyCOr80>UHew+cD@Ai zdOo~moo%)^AAE_@KDnHZX4qdJ&R;d>y4JoVhlf_n)$d^vs|aXa{9j1AyzRCh-8A&z zSunHtYn#{H)0bNWB}!aIkYq0+0k}mE9sgX1!&e7;C~w{Kn|0sedQm6B*@|LkHE_IE zb#5wWrF6`3eMURIU3%mk9lA(=rc{YWr&GqK{|V(&aUw@Epi`^Z;Bv{*0PjJN24mvfbjN)Co(t#s@KsQl)Sm^@k{3VjHvVX=`&6(T3pSOVEb89VUa!CAEN&D(N-RWH5+SUFUod(EM z{)qgr-HprqSs)E_b%F&^*V3-gs*|OgTI8m zgag5|mQ3e-UTmz}`BHmP@w}XPUV7gQ)EV*1rgN$A>WV#i1%0RL^Tfc&XLOS>d}9yd zKWhuP`5zVW!Bas8AXR{va_F)h&{D8X?izs}=hoGkwZS$%*lAdE#)Oa=N>Z zj82NK&g9|HLF2QyF+@m7(G@gwX#e|_QF&%PxDx@7vBXQ$FTal`jQE~T5LrK?Yfjrx zzR%a&pi-gwHs|r{e8rB13AzQlYOPrnf}Qm$v6l$On_$8suTC&qKC`=ze)frov7PMl zkKoQC!*_68GJgVYmov3m--CWKYr)^`9jYggR}4r!TGLLC9j>$gK3*T4E@li06o@y$ z=9rk`(vyRY`8sK?+{L#*`h=I53S?xAKO~Dr`RD^5>2XeS5{X%A8I8GP#Mc;Xt=sW~ zFRI%!##w4j&hO-QoMIjN$~KdW-81oiFD0|G$vWM*Jj>1t@Ii?H&4qS)&gP1wpAItY zt{%HT-^?*j|}astQHHjnvGNs4)}v0VW^3(n*RJ<yGQKn3|lFT`rVFFL-T7YfI-{`%(O8zHE|U@d`AO;m*XdSldnT12%f`=X-|6 zmbbaC@KkyZ??haDa~@ck@}XYqFCqy432D8!-fY)h+2~j}5zo9yEk5hii1<8136=XU zJp6^WD!y{D`cK>B6Lr&_}d?HKA9Dg zfT!WBi*LjvZVHfB?KN`Oa70_Qf4@oK{YLRQ!PgZ=2WZh9p8ke?k|+2Q-x{`DR`kL% ztB#5>V1JDM!1aw>hI&d$C`=xyGzf;>(uzPQrd3Coa22J-2@bkijq@k5N5 z-LLe)fv}1ck3x_h<*Ql9$toF)lC6WXoL=wfyv&oP;Ub3j8YUGx`U-8cxTjet^=Me= zz#6Qaw^6=nH3n5wEk5S?Tztw!!#(j4Z`>Xp5m7v~-q5ZV^LMxEKF*$=I%Q1PEJ8(6 zZq;A_aW0NDck5x+Ajmts#}C2XKDX1ZSvhV@r4J$U*8=lp3IIpn5PkP8Yx|;9{(vF( zp?G!0bqi1LUmgxyLFP!my!QPth+&XOBfdkogoGbh`S8IGfo30Aao0s?`{LAnLwy8{ z2E8M)@`4UNbRo%DM#_Ww7?0DUZuULXc6Kv1K;WCOh+m+Jt2g&-$Gfvm&O!e-uZXZ| z$SYkgVLoL+VjSkTMg3-ba+IrX{b1Y*V+a>}FQLo+r5rjPRXt7@e!XqV(R!dNeCVm0 zOd5F7q}vYa!56!rql**Vrby8B?LRWjx3f0F;hwegRura$h1KCqzg>zGe7b4P~`QhwnWbtymj6gl1 zPJUXaqwskvk$0V=Y|J%6r{5^pnh}!Mi^S-Xy)0A(1B)akW6Y64pB6p%%Os-F@dsOt zRu#ByFRBZ09?v!TqkH8Kgw)C-t@tel~|(*1)_6 zW13bbbn@_k)4n{sr++5(X~1al!}wQEP2B<0JwmDvst)-@~40Powob7`50Y%n*I+L zVjdpE&rWT@uOmR8;_-QfzT4vqP8|mB-chteV>|M-~3$sDxO55o~RA*biO29tNL}A(uvjFXGwp0YeIL7#&_5-I^k zVKznroxVtxPE8BW#jV)?|H1rsbXct};t&%%tRbv2<1HX!Sa|q#3@pkcCh%#rE!7Rc zpZ>Ah-rlyQqqcCAk9!i^mo|I<-xv&-9q7hmH)4EfXOCD1Zvi`^4>?_~$GPaP`-*;F ms;U2 + + - - - - + + - - + aimpos="90,10" handle1="-11,-7"/> + From df720b67ba4d910f80acd66ff1179d8b06a4b13b Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 24 Jul 2018 19:02:56 +0300 Subject: [PATCH 070/198] Fixed clients getting desynced if cargomanager spawns extra containers due to running out of space. --- .../BarotraumaShared/Source/GameSession/CargoManager.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs b/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs index 31aa45682..a3cad0948 100644 --- a/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs +++ b/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs @@ -152,11 +152,17 @@ namespace Barotrauma } for (int i = 0; i < Pi.quantity; i++) { + //if the intial container has been removed due to it running out of space, add a new container + //of the same type and begin filling it if (!availableContainers.ContainsKey(itemContainer)) { Item containerItemOverFlow = new Item(containerPrefab, position, wp.Submarine); itemContainer = containerItemOverFlow.GetComponent(); availableContainers.Add(itemContainer, itemContainer.Capacity); + if (GameMain.Server != null) + { + Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false); + } } if (itemContainer == null) From 01e97fe4bdc66466de7317537612bfe577fc7b97 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 24 Jul 2018 19:10:18 +0300 Subject: [PATCH 071/198] Fixed crashing if the server attempts to spawn extra cargo that doesn't spawn in a container. --- .../Source/GameSession/CargoManager.cs | 86 +++++++++---------- 1 file changed, 42 insertions(+), 44 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs b/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs index a3cad0948..d1918e153 100644 --- a/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs +++ b/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs @@ -111,24 +111,24 @@ namespace Barotrauma Dictionary availableContainers = new Dictionary(); ItemPrefab containerPrefab = null; - foreach (PurchasedItem Pi in itemsToSpawn) + foreach (PurchasedItem pi in itemsToSpawn) { Vector2 position = new Vector2( Rand.Range(cargoRoom.Rect.X + 20, cargoRoom.Rect.Right - 20), - cargoRoom.Rect.Y - cargoRoom.Rect.Height + Pi.itemPrefab.Size.Y / 2); + cargoRoom.Rect.Y - cargoRoom.Rect.Height + pi.itemPrefab.Size.Y / 2); ItemContainer itemContainer = null; - if (!string.IsNullOrEmpty(Pi.itemPrefab.CargoContainerName)) + if (!string.IsNullOrEmpty(pi.itemPrefab.CargoContainerName)) { itemContainer = availableContainers.Keys.ToList().Find(ac => - ac.Item.Prefab.NameMatches(Pi.itemPrefab.CargoContainerName) || - ac.Item.Prefab.Tags.Contains(Pi.itemPrefab.CargoContainerName.ToLowerInvariant())); + ac.Item.Prefab.NameMatches(pi.itemPrefab.CargoContainerName) || + ac.Item.Prefab.Tags.Contains(pi.itemPrefab.CargoContainerName.ToLowerInvariant())); if (itemContainer == null) { containerPrefab = MapEntityPrefab.List.Find(ep => - ep.NameMatches(Pi.itemPrefab.CargoContainerName) || - (ep.Tags != null && ep.Tags.Contains(Pi.itemPrefab.CargoContainerName.ToLowerInvariant()))) as ItemPrefab; + ep.NameMatches(pi.itemPrefab.CargoContainerName) || + (ep.Tags != null && ep.Tags.Contains(pi.itemPrefab.CargoContainerName.ToLowerInvariant()))) as ItemPrefab; if (containerPrefab == null) { @@ -150,57 +150,55 @@ namespace Barotrauma } } } - for (int i = 0; i < Pi.quantity; i++) + for (int i = 0; i < pi.quantity; i++) { - //if the intial container has been removed due to it running out of space, add a new container - //of the same type and begin filling it - if (!availableContainers.ContainsKey(itemContainer)) - { - Item containerItemOverFlow = new Item(containerPrefab, position, wp.Submarine); - itemContainer = containerItemOverFlow.GetComponent(); - availableContainers.Add(itemContainer, itemContainer.Capacity); - if (GameMain.Server != null) - { - Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false); - } - } - if (itemContainer == null) { //no container, place at the waypoint if (GameMain.Server != null) { - Entity.Spawner.AddToSpawnQueue(Pi.itemPrefab, position, wp.Submarine); + Entity.Spawner.AddToSpawnQueue(pi.itemPrefab, position, wp.Submarine); } else { - new Item(Pi.itemPrefab, position, wp.Submarine); + new Item(pi.itemPrefab, position, wp.Submarine); } + continue; + } + //if the intial container has been removed due to it running out of space, add a new container + //of the same type and begin filling it + if (!availableContainers.ContainsKey(itemContainer)) + { + Item containerItemOverFlow = new Item(containerPrefab, position, wp.Submarine); + itemContainer = containerItemOverFlow.GetComponent(); + availableContainers.Add(itemContainer, itemContainer.Capacity); + if (GameMain.Server != null) + { + Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false); + } + } + + //place in the container + if (GameMain.Server != null) + { + Entity.Spawner.AddToSpawnQueue(pi.itemPrefab, itemContainer.Inventory); } else { - //place in the container - if (GameMain.Server != null) - { - Entity.Spawner.AddToSpawnQueue(Pi.itemPrefab, itemContainer.Inventory); - } - else - { - var item = new Item(Pi.itemPrefab, position, wp.Submarine); - itemContainer.Inventory.TryPutItem(item, null); - } - - //reduce the number of available slots in the container - //if there is a container - if (availableContainers.ContainsKey(itemContainer)) - { - availableContainers[itemContainer]--; - } - if (availableContainers.ContainsKey(itemContainer) && availableContainers[itemContainer] <= 0) - { - availableContainers.Remove(itemContainer); - } + var item = new Item(pi.itemPrefab, position, wp.Submarine); + itemContainer.Inventory.TryPutItem(item, null); } + + //reduce the number of available slots in the container + //if there is a container + if (availableContainers.ContainsKey(itemContainer)) + { + availableContainers[itemContainer]--; + } + if (availableContainers.ContainsKey(itemContainer) && availableContainers[itemContainer] <= 0) + { + availableContainers.Remove(itemContainer); + } } } itemsToSpawn.Clear(); From d3d842eecd6366c05f3e6843fa2a15cfaef3cb44 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 25 Jul 2018 11:49:10 +0300 Subject: [PATCH 072/198] Added error checking and logging to wire cloning logic in MapEntity.Clone (see #527) --- .../BarotraumaShared/Source/Map/MapEntity.cs | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs index ab868b2d1..2c1516ecf 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs @@ -220,12 +220,29 @@ namespace Barotrauma var connectedItem = originalWire.Connections[n].Item; if (connectedItem == null) continue; - + //index of the item the wire is connected to int itemIndex = entitiesToClone.IndexOf(connectedItem); + if (itemIndex < 0) + { + DebugConsole.ThrowError("Error while cloning wires - item \"" + connectedItem.Name + "\" was not found in entities to clone."); + GameAnalyticsManager.AddErrorEventOnce("MapEntity.Clone:ConnectedNotFound" + connectedItem.ID, + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Error while cloning wires - item \"" + connectedItem.Name + "\" was not found in entities to clone."); + continue; + } + //index of the connection in the connectionpanel of the target item int connectionIndex = connectedItem.Connections.IndexOf(originalWire.Connections[n]); - + if (connectionIndex < 0) + { + DebugConsole.ThrowError("Error while cloning wires - connection \"" + originalWire.Connections[n].Name + "\" was not found in connected item \"" + connectedItem.Name + "\"."); + GameAnalyticsManager.AddErrorEventOnce("MapEntity.Clone:ConnectionNotFound" + connectedItem.ID, + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Error while cloning wires - connection \"" + originalWire.Connections[n].Name + "\" was not found in connected item \"" + connectedItem.Name + "\"."); + continue; + } + (clones[itemIndex] as Item).Connections[connectionIndex].TryAddLink(cloneWire); cloneWire.Connect((clones[itemIndex] as Item).Connections[connectionIndex], false); } From 40f4e946130326240ab560d537b8383cacc3fbb3 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 25 Jul 2018 17:00:31 +0300 Subject: [PATCH 073/198] Fixed incorrect debug info when receiving a message with an invalid object header. The method wrote the bytes/bits read SINCE the previous object (= just one byte, the invalid object header) instead of what was read in the previous object. The error message also ignored empty entity events. --- .../BarotraumaClient/Source/Networking/GameClient.cs | 11 +++++++++-- .../NetEntityEvent/ClientEntityEventManager.cs | 6 ++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs index adbc146eb..c794bd446 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs @@ -986,6 +986,9 @@ namespace Barotrauma.Networking long prevBitPos = 0; long prevBytePos = 0; + long prevBitLength = 0; + long prevByteLength = 0; + ServerNetObject objHeader; while ((objHeader = (ServerNetObject)inc.ReadByte()) != ServerNetObject.END_OF_MESSAGE) { @@ -1023,8 +1026,9 @@ namespace Barotrauma.Networking List errorLines = new List { "Error while reading update from server (unknown object header \"" + objHeader + "\"!)", + "Message length: " + inc.LengthBits + " (" + inc.LengthBytes + " bytes)", prevObjHeader != null ? "Previous object type: " + prevObjHeader.ToString() : "Error occurred on the very first object!", - "Previous object was " + (inc.Position - prevBitPos) + " bits long (" + (inc.PositionInBytes - prevBytePos) + " bytes)" + "Previous object was " + (prevBitLength) + " bits long (" + (prevByteLength) + " bytes)" }; if (prevObjHeader == ServerNetObject.ENTITY_EVENT || prevObjHeader == ServerNetObject.ENTITY_EVENT_INITIAL) { @@ -1051,13 +1055,16 @@ namespace Barotrauma.Networking FileStream fl = File.Open("crashreport_object.bin", FileMode.Create); BinaryWriter sw = new BinaryWriter(fl); - sw.Write(inc.Data, (int)prevBytePos, (int)(inc.LengthBytes - prevBytePos)); + sw.Write(inc.Data, (int)(prevBytePos - prevByteLength), (int)(prevByteLength)); sw.Close(); fl.Close(); throw new Exception("Error while reading update from server: please send us \"crashreport_object.bin\"!"); } + prevBitLength = inc.Position - prevBitPos; + prevByteLength = inc.PositionInBytes - prevByteLength; + prevObjHeader = objHeader; prevBitPos = inc.Position; prevBytePos = inc.PositionInBytes; diff --git a/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs b/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs index cd4c92164..f8cec2fe5 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs @@ -140,7 +140,13 @@ namespace Barotrauma.Networking if (entityID == 0) { + if (GameSettings.VerboseLogging) + { + DebugConsole.NewMessage("received msg " + thisEventID + " (null entity)", + Microsoft.Xna.Framework.Color.Orange); + } msg.ReadPadBits(); + entities.Add(null); if (thisEventID == (UInt16)(lastReceivedID + 1)) lastReceivedID++; continue; } From b309b45246d5b0cbb546d2613ee579788c0dfabb Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 25 Jul 2018 17:34:10 +0300 Subject: [PATCH 074/198] Fixed entity ID mismatches and desync kicks caused by gap creation/removal in Structure.SetDamage. Creating the gaps on damaged walls wasn't guaranteed to happen in the same order client-side as on the server, causing the IDs to get assigned mismatching IDs and in some cases also affecting the IDs of other types of entities (see #528). Now the structure gaps simply don't have IDs. They're never accessed by ID so I don't think there's the need to make the creation/removal go through entityspawner. --- .../BarotraumaShared/Source/Map/Entity.cs | 21 +++++-- .../BarotraumaShared/Source/Map/Structure.cs | 59 ++++++++----------- .../BarotraumaShared/Source/Map/Submarine.cs | 2 +- 3 files changed, 41 insertions(+), 41 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Map/Entity.cs b/Barotrauma/BarotraumaShared/Source/Map/Entity.cs index 8546ff782..def927532 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Entity.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Entity.cs @@ -20,6 +20,8 @@ namespace Barotrauma protected AITarget aiTarget; + private bool idFreed; + public virtual bool Removed { get; @@ -52,7 +54,8 @@ namespace Barotrauma DebugConsole.Log("The id of " + this + " is now " + value); } - id = value; + id = value; + idFreed = false; dictionary.Add(id, this); } } @@ -199,21 +202,24 @@ namespace Barotrauma dictionary.Clear(); } - public virtual void Remove() + /// + /// Removes the entity from the entity dictionary and frees up the ID it was using. + /// + public void FreeID() { DebugConsole.Log("Removing entity " + ToString() + " (" + ID + ") from entity dictionary."); if (!dictionary.TryGetValue(ID, out Entity existingEntity)) { DebugConsole.Log("Entity " + ToString() + " (" + ID + ") not present in entity dictionary."); GameAnalyticsManager.AddErrorEventOnce( - "Entity.Remove:EntityNotFound" + ID, + "Entity.FreeID:EntityNotFound" + ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Entity " + ToString() + " (" + ID + ") not present in entity dictionary.\n" + Environment.StackTrace); } else if (existingEntity != this) { DebugConsole.Log("Entity ID mismatch in entity dictionary. Entity " + existingEntity + " had the ID " + ID + " (expecting " + ToString() + ")"); - GameAnalyticsManager.AddErrorEventOnce("Entity.Remove:EntityMismatch" + ID, + GameAnalyticsManager.AddErrorEventOnce("Entity.FreeID:EntityMismatch" + ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Entity ID mismatch in entity dictionary. Entity " + existingEntity + " had the ID " + ID + " (expecting " + ToString() + ")"); @@ -224,6 +230,13 @@ namespace Barotrauma } dictionary.Remove(ID); + id = 0; + idFreed = true; + } + + public virtual void Remove() + { + if (!idFreed) FreeID(); Removed = true; } diff --git a/Barotrauma/BarotraumaShared/Source/Map/Structure.cs b/Barotrauma/BarotraumaShared/Source/Map/Structure.cs index 7348e0aa5..69974ebb2 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Structure.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Structure.cs @@ -18,11 +18,7 @@ namespace Barotrauma public Rectangle rect; public float damage; public Gap gap; - - public int GapID; - - //public float lastSentDamage; - + public WallSection(Rectangle rect) { System.Diagnostics.Debug.Assert(rect.Width > 0 && rect.Height > 0); @@ -687,7 +683,7 @@ namespace Barotrauma return new AttackResult(damageAmount, 0.0f); } - private void SetDamage(int sectionIndex, float damage, Character attacker = null) + private void SetDamage(int sectionIndex, float damage, Character attacker = null, bool createNetworkEvent = true) { if (Submarine != null && Submarine.GodMode) return; if (!prefab.Body) return; @@ -695,7 +691,7 @@ namespace Barotrauma damage = MathHelper.Clamp(damage, 0.0f, prefab.Health); - if (GameMain.Server != null && damage != sections[sectionIndex].damage) + if (GameMain.Server != null && createNetworkEvent && damage != sections[sectionIndex].damage) { GameMain.Server.CreateEntityEvent(this); } @@ -720,7 +716,9 @@ namespace Barotrauma GameServer.Log((sections[sectionIndex].gap.IsRoomToRoom ? "Inner" : "Outer") + " wall repaired by " + attacker.Name, ServerLog.MessageType.ItemInteraction); } - //remove existing gap if damage is below 50% + DebugConsole.Log("Removing gap (ID " + sections[sectionIndex].gap.ID + ", section: " + sectionIndex + ") from wall " + ID); + //remove existing gap if damage is below leak threshold + sections[sectionIndex].gap.Open = 0.0f; sections[sectionIndex].gap.Remove(); sections[sectionIndex].gap = null; #if CLIENT @@ -730,17 +728,21 @@ namespace Barotrauma } else { - if (sections[sectionIndex].gap == null) { - Rectangle gapRect = sections[sectionIndex].rect; gapRect.X -= 10; gapRect.Y += 10; gapRect.Width += 20; gapRect.Height += 20; sections[sectionIndex].gap = new Gap(gapRect, !isHorizontal, Submarine); + //free the ID, because if we give gaps IDs we have to make sure they always match between the clients and the server and + //that clients create them in the correct order along with every other entity created/removed during the round + //which COULD be done via entityspawner, but it's unnecessary because we never access these gaps by ID + sections[sectionIndex].gap.FreeID(); + sections[sectionIndex].gap.ShouldBeSaved = false; sections[sectionIndex].gap.ConnectedWall = this; + DebugConsole.Log("Created gap (ID " + sections[sectionIndex].gap.ID + ", section: " + sectionIndex + ") on wall " + ID); //AdjustKarma(attacker, 300); //the structure didn't have any other gaps yet, log the breach @@ -856,9 +858,8 @@ namespace Barotrauma { for (int i = 0; i < sections.Length; i++) { - float damage = msg.ReadRangedSingle(0.0f, 1.0f, 8) * Health; - - SetDamage(i, damage); + float damage = msg.ReadRangedSingle(0.0f, 1.0f, 8) * Health; + SetDamage(i, damage); } } public override void FlipX() @@ -900,10 +901,12 @@ namespace Barotrauma } Rectangle rect = element.GetAttributeRect("rect", Rectangle.Empty); - Structure s = new Structure(rect, prefab, submarine); - s.Submarine = submarine; - s.ID = (ushort)int.Parse(element.Attribute("ID").Value); - + Structure s = new Structure(rect, prefab, submarine) + { + Submarine = submarine, + ID = (ushort)int.Parse(element.Attribute("ID").Value) + }; + foreach (XElement subElement in element.Elements()) { switch (subElement.Name.ToString()) @@ -911,12 +914,7 @@ namespace Barotrauma case "section": int index = subElement.GetAttributeInt("i", -1); if (index == -1) continue; - - s.sections[index].damage = - subElement.GetAttributeFloat("damage", 0.0f); - - s.sections[index].GapID = subElement.GetAttributeInt("gap", -1); - + s.sections[index].damage = subElement.GetAttributeFloat("damage", 0.0f); break; } } @@ -938,17 +936,10 @@ namespace Barotrauma for (int i = 0; i < sections.Length; i++) { if (sections[i].damage == 0.0f) continue; - var sectionElement = new XElement("section", new XAttribute("i", i), new XAttribute("damage", sections[i].damage)); - - if (sections[i].gap != null) - { - sectionElement.Add(new XAttribute("gap", sections[i].gap.ID)); - } - element.Add(sectionElement); } @@ -961,14 +952,10 @@ namespace Barotrauma public override void OnMapLoaded() { - foreach (WallSection s in sections) + for (int i = 0; i < sections.Length; i++) { - if (s.GapID == -1) continue; - - s.gap = FindEntityByID((ushort)s.GapID) as Gap; - if (s.gap != null) s.gap.ConnectedWall = this; + SetDamage(i, sections[i].damage, createNetworkEvent: false); } } - } } diff --git a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs index 276bf8177..ddba249ca 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs @@ -322,7 +322,7 @@ namespace Barotrauma DockedTo = new List(); ID = ushort.MaxValue; - base.Remove(); + FreeID(); } public bool HasTag(SubmarineTag tag) From 50603b72f4546bb1ea30c0a5655537aa9a767acb Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 26 Jul 2018 12:41:54 +0300 Subject: [PATCH 075/198] Fixed another bug in DockingPort that caused entity ID mismatches. Even though the server sent the IDs of the CURRENT hulls and gap of the docking port, they are not necessarily created in the correct order during midround syncing and may end up replacing the ID of another entity or another entity spawned after them may cause their IDs to be replaced. Closes #530 --- .../Source/Items/Components/DockingPort.cs | 59 ++++--------------- .../BarotraumaShared/Source/Map/Entity.cs | 6 +- .../BarotraumaShared/Source/Map/Hull.cs | 2 +- .../NetEntityEvent/NetEntityEventManager.cs | 4 +- 4 files changed, 21 insertions(+), 50 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs index f3137e284..b1244bcf8 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs @@ -28,17 +28,13 @@ namespace Barotrauma.Items.Components private Joint joint; private readonly Hull[] hulls = new Hull[2]; - private ushort?[] hullIds; + private Gap gap; private Door door; private Body[] bodies; - private Body doorBody; - private Gap gap; - private ushort? gapId; - private bool docked; public int DockingDir @@ -114,9 +110,7 @@ namespace Barotrauma.Items.Components } IsActive = true; - - hullIds = new ushort?[2]; - + list.Add(this); } @@ -407,7 +401,7 @@ namespace Barotrauma.Items.Components hullRects[i].Location -= MathUtils.ToPoint((subs[i].WorldPosition - subs[i].HiddenSubPosition)); hulls[i] = new Hull(MapEntityPrefab.Find("Hull"), hullRects[i], subs[i]); hulls[i].AddToGrid(subs[i]); - if (hullIds[i] != null) hulls[i].ID = (ushort)hullIds[i]; + hulls[i].FreeID(); for (int j = 0; j < 2; j++) { @@ -418,7 +412,6 @@ namespace Barotrauma.Items.Components } gap = new Gap(new Rectangle(hullRects[0].Right - 2, hullRects[0].Y, 4, hullRects[0].Height), true, subs[0]); - if (gapId != null) gap.ID = (ushort)gapId; } else { @@ -436,25 +429,22 @@ namespace Barotrauma.Items.Components hullRects[i].Location -= MathUtils.ToPoint((subs[i].WorldPosition - subs[i].HiddenSubPosition)); hulls[i] = new Hull(MapEntityPrefab.Find("Hull"), hullRects[i], subs[i]); hulls[i].AddToGrid(subs[i]); - if (hullIds[i] != null) hulls[i].ID = (ushort)hullIds[i]; + hulls[i].FreeID(); } gap = new Gap(new Rectangle(hullRects[0].X, hullRects[0].Y+2, hullRects[0].Width, 4), false, subs[0]); - if (gapId != null) gap.ID = (ushort)gapId; } LinkHullsToGap(); - - hullIds[0] = hulls[0].ID; - hullIds[1] = hulls[1].ID; + hulls[0].ShouldBeSaved = false; hulls[1].ShouldBeSaved = false; item.linkedTo.Add(hulls[0]); item.linkedTo.Add(hulls[1]); + gap.FreeID(); gap.DisableHullRechecks = true; gap.ShouldBeSaved = false; - gapId = gap.ID; item.linkedTo.Add(gap); foreach (Body body in bodies) @@ -569,11 +559,7 @@ namespace Barotrauma.Items.Components gap.Remove(); gap = null; } - - hullIds[0] = null; - hullIds[1] = null; - gapId = null; - + if (bodies != null) { foreach (Body body in bodies) @@ -636,6 +622,9 @@ namespace Barotrauma.Items.Components protected override void RemoveComponentSpecific() { list.Remove(this); + hulls[0]?.Remove(); hulls[0] = null; + hulls[1]?.Remove(); hulls[1] = null; + gap?.Remove(); gap = null; } public override void OnMapLoaded() @@ -724,19 +713,8 @@ namespace Barotrauma.Items.Components if (docked) { - msg.Write(dockingTarget.item.ID); - - if (hulls != null && hulls[0] != null && hulls[1] != null && gap != null) - { - msg.Write(true); - msg.Write(hulls[0].ID); - msg.Write(hulls[1].ID); - msg.Write(gap.ID); - } - else - { - msg.Write(false); - } + msg.Write(dockingTarget.item.ID); + msg.Write(hulls != null && hulls[0] != null && hulls[1] != null && gap != null); } } @@ -764,14 +742,7 @@ namespace Barotrauma.Items.Components ushort dockingTargetID = msg.ReadUInt16(); bool isLocked = msg.ReadBoolean(); - - if (isLocked) - { - hullIds[0] = msg.ReadUInt16(); - hullIds[1] = msg.ReadUInt16(); - gapId = msg.ReadUInt16(); - } - + Entity targetEntity = Entity.FindEntityByID(dockingTargetID); if (targetEntity == null || !(targetEntity is Item)) { @@ -791,10 +762,6 @@ namespace Barotrauma.Items.Components if (isLocked) { Lock(true); - - hulls[0].ID = (ushort)hullIds[0]; - hulls[1].ID = (ushort)hullIds[1]; - gap.ID = (ushort)gapId; } } else diff --git a/Barotrauma/BarotraumaShared/Source/Map/Entity.cs b/Barotrauma/BarotraumaShared/Source/Map/Entity.cs index def927532..8490e6113 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Entity.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Entity.cs @@ -28,6 +28,11 @@ namespace Barotrauma private set; } + public bool IdFreed + { + get { return idFreed; } + } + public ushort ID { get @@ -230,7 +235,6 @@ namespace Barotrauma } dictionary.Remove(ID); - id = 0; idFreed = true; } diff --git a/Barotrauma/BarotraumaShared/Source/Map/Hull.cs b/Barotrauma/BarotraumaShared/Source/Map/Hull.cs index a74c65fd7..4f2fb775f 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Hull.cs @@ -406,7 +406,7 @@ namespace Barotrauma if (Math.Abs(lastSentVolume - waterVolume) > Volume * 0.1f || Math.Abs(lastSentOxygen - OxygenPercentage) > 5f) { - if (GameMain.Server != null) + if (GameMain.Server != null && !IdFreed) { sendUpdateTimer -= deltaTime; if (sendUpdateTimer < 0.0f) diff --git a/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEventManager.cs b/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEventManager.cs index eafb6fb1b..65f820231 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEventManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEventManager.cs @@ -56,9 +56,9 @@ namespace Barotrauma.Networking GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Too much data in network event for entity \"" + e.Entity.ToString() + "\" (" + tempEventBuffer.LengthBytes + " bytes"); } - + //the ID has been taken by another entity (the original entity has been removed) -> write an empty event - if (Entity.FindEntityByID(e.Entity.ID) != e.Entity) + else if (Entity.FindEntityByID(e.Entity.ID) != e.Entity || e.Entity.IdFreed) { //technically the clients don't have any use for these, but removing events and shifting the IDs of all //consecutive ones is so error-prone that I think this is a safer option From 90f6d351e5d78d31c8a7d03936a8307a49aed369 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 26 Jul 2018 13:09:54 +0300 Subject: [PATCH 076/198] Fixed indentation in jobs.xml, oxygengenerator.xml & weapons.xml and removed the oxygenite tank statuseffect that does nothing from oxygen generator --- .../Items/OxygenGenerator/oxygengenerator.xml | 13 +++++-------- .../Content/Items/Weapons/weapons.xml | 3 ++- Barotrauma/BarotraumaShared/Content/Jobs.xml | 10 +++++----- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Content/Items/OxygenGenerator/oxygengenerator.xml b/Barotrauma/BarotraumaShared/Content/Items/OxygenGenerator/oxygengenerator.xml index a3fdf04d8..0237ce837 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/OxygenGenerator/oxygengenerator.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/OxygenGenerator/oxygengenerator.xml @@ -13,16 +13,13 @@ - - - - - - - - + + + + + diff --git a/Barotrauma/BarotraumaShared/Content/Items/Weapons/weapons.xml b/Barotrauma/BarotraumaShared/Content/Items/Weapons/weapons.xml index 4fd034a96..ef19e463e 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Weapons/weapons.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Weapons/weapons.xml @@ -126,7 +126,8 @@ - + + diff --git a/Barotrauma/BarotraumaShared/Content/Jobs.xml b/Barotrauma/BarotraumaShared/Content/Jobs.xml index a0b3caf33..17550563c 100644 --- a/Barotrauma/BarotraumaShared/Content/Jobs.xml +++ b/Barotrauma/BarotraumaShared/Content/Jobs.xml @@ -16,11 +16,11 @@ - - - - - + + + + + From ce18309a7867b2587e7561109c58dac980c2dec6 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 26 Jul 2018 16:42:11 +0300 Subject: [PATCH 077/198] Fixed crashing when attempting to increase buy quantity in campaign UI when money is at zero, fixed capping the number input to the value the player can afford (the previous calculation only clamped it above zero). Closes #531 --- Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs b/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs index 6ac7b4783..99a3b3a45 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs @@ -270,7 +270,8 @@ namespace Barotrauma { int quantity = numberInput.IntValue - purchasedItem.quantity; //Cap the numberbox based on the amount we can afford. - quantity = Math.Max((quantity * (purchasedItem.itemPrefab.Price / Campaign.Money)), quantity); + quantity = campaign.Money <= 0 ? + 0 : Math.Min((int)(Campaign.Money / (float)purchasedItem.itemPrefab.Price), quantity); for (int i = 0; i < quantity; i++) { BuyItem(numberInput, purchasedItem); From 2de1b2a05c3f101320c6e3b0f7cad678355a2d0e Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 27 Jul 2018 02:30:35 +0300 Subject: [PATCH 078/198] v0.8.1.10 --- .../BarotraumaClient/Properties/AssemblyInfo.cs | 4 ++-- .../BarotraumaServer/Properties/AssemblyInfo.cs | 4 ++-- Barotrauma/BarotraumaShared/changelog.txt | 16 ++++++++++++++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs index d39aae256..fe3acb274 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.9")] -[assembly: AssemblyFileVersion("0.8.1.9")] +[assembly: AssemblyVersion("0.8.1.10")] +[assembly: AssemblyFileVersion("0.8.1.10")] diff --git a/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs index f7493a1eb..1cfb0fad4 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.9")] -[assembly: AssemblyFileVersion("0.8.1.9")] +[assembly: AssemblyVersion("0.8.1.10")] +[assembly: AssemblyFileVersion("0.8.1.10")] diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 0a24d2379..2263709ac 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,19 @@ +--------------------------------------------------------------------------------------------------------- +v0.8.1.10 +--------------------------------------------------------------------------------------------------------- + +- Fixed bugs in wall hole creation logic and docking port syncing which caused entity ID mismatches and +"unknown object header" errors. +- Fixed errors when attempting to buy too many items of a given type to fit in one container. +- Fixed crashing when attempting to buy items that don't spawn in a container. +- Fixed crashing when attempting to generate hulls with the "autohull" command when there are no walls +or doors in the sub. +- Fixed docking ports creating duplicate hulls and gaps during loading. +- Fixed missions resetting to the initial ones when loading a campaign. +- Increased revolver ammo capacity. +- Fires can break oxygen generators. +- Oxygenite tanks can be contained in plasma cutters and oxygen generators. + --------------------------------------------------------------------------------------------------------- v0.8.1.9 --------------------------------------------------------------------------------------------------------- From aa5b2a973e3cc4657a231cb6ac633579c8acf03d Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 27 Jul 2018 10:36:52 +0300 Subject: [PATCH 079/198] Mirroring levels. Mirroring the wall geometry works, but ruins and background sprites are not placed at the correct positions yet. --- .../Source/Map/Levels/Level.cs | 11 +- .../Map/Levels/BackgroundSpriteManager.cs | 2 + .../Source/Map/Levels/Level.cs | 122 +++++++++++++++--- .../Source/Map/Levels/Ruins/RuinGenerator.cs | 39 +++++- 4 files changed, 144 insertions(+), 30 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Map/Levels/Level.cs b/Barotrauma/BarotraumaClient/Source/Map/Levels/Level.cs index 0fcdf9623..04fbb87e9 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Levels/Level.cs @@ -27,10 +27,17 @@ namespace Barotrauma { color = Color.LightGray; } - - + GUI.DrawRectangle(spriteBatch, new Vector2(pos.Position.X - 15.0f, -pos.Position.Y - 15.0f), new Vector2(30.0f, 30.0f), color, true); } + + foreach (RuinGeneration.Ruin ruin in ruins) + { + Rectangle ruinArea = ruin.Area; + ruinArea.Y = -ruinArea.Y - ruinArea.Height; + + GUI.DrawRectangle(spriteBatch, ruinArea, Color.DarkSlateBlue, false, 0, 5); + } } } diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/BackgroundSpriteManager.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/BackgroundSpriteManager.cs index a5b52b7c8..565d8e90f 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/BackgroundSpriteManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/BackgroundSpriteManager.cs @@ -231,6 +231,8 @@ namespace Barotrauma Rand.Range(0.0f, level.Size.X, Rand.RandSync.Server), Rand.Range(0.0f, level.Size.Y, Rand.RandSync.Server)); + if (level.Mirrored) randomPos.X = level.Size.X - randomPos.X; + if (prefab.SpawnPos == BackgroundSpritePrefab.SpawnPosType.None) return randomPos; List edges = new List(); diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs index 7d7ce68e1..a7314c6dd 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs @@ -25,12 +25,12 @@ namespace Barotrauma [Flags] public enum PositionType { - MainPath=1, Cave=2, Ruin=4 + MainPath = 1, Cave = 2, Ruin = 4 } - + struct InterestingPosition { - public readonly Vector2 Position; + public Vector2 Position; public readonly PositionType PositionType; public InterestingPosition(Vector2 position, PositionType positionType) @@ -145,7 +145,11 @@ namespace Barotrauma private set; } - + public bool Mirrored + { + get; + private set; + } public LevelGenerationParams GenerationParams { @@ -170,7 +174,9 @@ namespace Barotrauma this.generationParams = generationParams; - borders = new Rectangle(0, 0, (int)generationParams.Width, (int)generationParams.Height); + borders = new Rectangle(0, 0, + (int)(Math.Ceiling(generationParams.Width / GridCellSize) * GridCellSize), + (int)(Math.Ceiling(generationParams.Height / GridCellSize) * GridCellSize)); } public static Level CreateRandom(LocationConnection locationConnection) @@ -194,6 +200,8 @@ namespace Barotrauma public void Generate(bool mirror = false) { + Mirrored = mirror; + if (backgroundSpriteManager == null) { var files = GameMain.SelectedPackage.GetFilesOfType(ContentType.BackgroundSpritePrefabs); @@ -303,9 +311,7 @@ namespace Barotrauma if (y < borders.Height - siteInterval.Y) sites.Add(new Vector2(x, y) + Vector2.UnitY * siteInterval * 0.5f); if (x < borders.Width - siteInterval.X && y < borders.Height - siteInterval.Y) sites.Add(new Vector2(x, y) + Vector2.One * siteInterval * 0.5f); } - - if (mirror) site.X = borders.Width - site.X; - + sites.Add(site); } } @@ -334,7 +340,7 @@ namespace Barotrauma //---------------------------------------------------------------------------------- List mainPath = CaveGenerator.GeneratePath(pathNodes, cells, cellGrid, GridCellSize, - new Rectangle(pathBorders.X, pathBorders.Y, pathBorders.Width, borders.Height), 0.5f, mirror); + new Rectangle(pathBorders.X, pathBorders.Y, pathBorders.Width, borders.Height), 0.5f, false); for (int i = 2; i < mainPath.Count; i += 3) { @@ -348,8 +354,13 @@ namespace Barotrauma foreach (InterestingPosition positionOfInterest in positionsOfInterest) { - WayPoint wayPoint = new WayPoint(positionOfInterest.Position, SpawnType.Enemy, null); - wayPoint.MoveWithLevel = true; + WayPoint wayPoint = new WayPoint( + mirror ? new Vector2(borders.X - positionOfInterest.Position.X, positionOfInterest.Position.Y) : positionOfInterest.Position, + SpawnType.Enemy, + submarine: null) + { + MoveWithLevel = true + }; } startPosition.X = pathCells[0].Center.X; @@ -400,7 +411,7 @@ namespace Barotrauma //---------------------------------------------------------------------------------- // initialize the cells that are still left and insert them into the cell grid //---------------------------------------------------------------------------------- - + foreach (VoronoiCell cell in pathCells) { cell.edges.ForEach(e => e.OutsideLevel = false); @@ -417,6 +428,55 @@ namespace Barotrauma } } + //---------------------------------------------------------------------------------- + // mirror if needed + //---------------------------------------------------------------------------------- + + if (mirror) + { + HashSet mirroredEdges = new HashSet(); + HashSet mirroredSites = new HashSet(); + foreach (VoronoiCell cell in cells) + { + foreach (GraphEdge edge in cell.edges) + { + if (mirroredEdges.Contains(edge)) continue; + edge.point1.X = borders.Width - edge.point1.X; + edge.point2.X = borders.Width - edge.point2.X; + if (!mirroredSites.Contains(edge.site1)) + { + edge.site1.coord.x = borders.Width - edge.site1.coord.x; + mirroredSites.Add(edge.site1); + } + if (!mirroredSites.Contains(edge.site2)) + { + edge.site2.coord.x = borders.Width - edge.site2.coord.x; + mirroredSites.Add(edge.site2); + } + mirroredEdges.Add(edge); + } + } + + + foreach (List smallTunnel in smallTunnels) + { + for (int i = 0; i < smallTunnel.Count; i++) + { + smallTunnel[i] = new Vector2(borders.Width - smallTunnel[i].X, smallTunnel[i].Y); + } + } + + for (int i = 0; i < positionsOfInterest.Count; i++) + { + positionsOfInterest[i] = new InterestingPosition( + new Vector2(borders.Width - positionsOfInterest[i].Position.X, positionsOfInterest[i].Position.Y), + positionsOfInterest[i].PositionType); + } + + startPosition.X = borders.Width - startPosition.X; + endPosition.X = borders.Width - endPosition.X; + } + foreach (VoronoiCell cell in cells) { int x = (int)Math.Floor(cell.Center.X / GridCellSize); @@ -435,9 +495,12 @@ namespace Barotrauma ruins = new List(); for (int i = 0; i < generationParams.RuinCount; i++) { - GenerateRuin(mainPath); + System.Diagnostics.Debug.WriteLine("Generating ruin "+i+" *******************************************"); + GenerateRuin(mainPath, mirror); } + int testSync = Rand.Int(1000, Rand.RandSync.Server); + DebugConsole.NewMessage("TESTSYNC: " + testSync + " ---------------------------------------------",Color.White); //---------------------------------------------------------------------------------- // generate the bodies and rendered triangles of the cells @@ -809,40 +872,56 @@ namespace Barotrauma return tunnelNodes; } - private void GenerateRuin(List mainPath) + private void GenerateRuin(List mainPath, bool mirror) { Vector2 ruinSize = new Vector2(Rand.Range(5000.0f, 8000.0f, Rand.RandSync.Server), Rand.Range(5000.0f, 8000.0f, Rand.RandSync.Server)); float ruinRadius = Math.Max(ruinSize.X, ruinSize.Y) * 0.5f; - Vector2 ruinPos = cells[Rand.Int(cells.Count, Rand.RandSync.Server)].Center; + System.Diagnostics.Debug.WriteLine("Cell count " + cells.Count); + int cellIndex = Rand.Int(cells.Count, Rand.RandSync.Server); + System.Diagnostics.Debug.WriteLine("Cell index " + cellIndex); + Vector2 ruinPos = cells[cellIndex].Center; //50% chance of placing the ruins at a cave if (Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) < 0.5f) { + System.Diagnostics.Debug.WriteLine("Placing at cave"); TryGetInterestingPosition(true, PositionType.Cave, 0.0f, out ruinPos); } ruinPos.Y = Math.Min(ruinPos.Y, borders.Y + borders.Height - ruinSize.Y / 2); ruinPos.Y = Math.Max(ruinPos.Y, SeaFloorTopPos + ruinSize.Y / 2.0f); - + + float minDist = ruinRadius * 2.0f; + float minDistSqr = minDist * minDist; + + System.Diagnostics.Debug.WriteLine("Initial ruin pos "+ruinPos); + int iter = 0; - while (mainPath.Any(p => Vector2.Distance(ruinPos, p.Center) < ruinRadius * 2.0f)) + while (mainPath.Any(p => Vector2.DistanceSquared(ruinPos, p.Center) < minDistSqr)) { Vector2 weighedPathPos = ruinPos; iter++; foreach (VoronoiCell pathCell in mainPath) { - float dist = Vector2.Distance(pathCell.Center, ruinPos); - if (dist > 10000.0f) continue; + Vector2 diff = ruinPos - pathCell.Center; + float distSqr = diff.LengthSquared(); + if (distSqr < 1.0f) + { + diff = Vector2.UnitY; + distSqr = 1.0f; + } + if (distSqr > 10000.0f * 10000.0f) continue; - Vector2 moveAmount = Vector2.Normalize(ruinPos - pathCell.Center) * 100000.0f / dist; + Vector2 moveAmount = Vector2.Normalize(diff) * 100000.0f / (float)Math.Sqrt(distSqr); weighedPathPos += moveAmount; weighedPathPos.Y = Math.Min(borders.Y + borders.Height - ruinSize.Y / 2, weighedPathPos.Y); } ruinPos = weighedPathPos; + System.Diagnostics.Debug.WriteLine(iter+": " + ruinPos); if (iter > 10000) break; } @@ -859,7 +938,8 @@ namespace Barotrauma } } - var ruin = new Ruin(closestPathCell, cells, new Rectangle(MathUtils.ToPoint(ruinPos - ruinSize * 0.5f), MathUtils.ToPoint(ruinSize))); + System.Diagnostics.Debug.WriteLine("Final ruin pos: " + ruinPos); + var ruin = new Ruin(closestPathCell, cells, new Rectangle(MathUtils.ToPoint(ruinPos - ruinSize * 0.5f), MathUtils.ToPoint(ruinSize)), mirror); ruins.Add(ruin); ruin.RuinShapes.Sort((shape1, shape2) => shape2.DistanceFromEntrance.CompareTo(shape1.DistanceFromEntrance)); diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/Ruins/RuinGenerator.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/Ruins/RuinGenerator.cs index ad9c9a4d0..3fc1a12a0 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/Ruins/RuinGenerator.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/Ruins/RuinGenerator.cs @@ -139,11 +139,28 @@ namespace Barotrauma.RuinGeneration Walls = newLines; } + + public void MirrorX(Vector2 mirrorOrigin) + { + rect.X = (int)(mirrorOrigin.X + (mirrorOrigin.X - rect.Right)); + for (int i = 0; i < Walls.Count; i++) + { + Walls[i].A = new Vector2(mirrorOrigin.X + (mirrorOrigin.X - Walls[i].A.X), Walls[i].A.Y); + Walls[i].B = new Vector2(mirrorOrigin.X + (mirrorOrigin.X - Walls[i].B.X), Walls[i].B.Y); + + if (Walls[i].B.X < Walls[i].A.X) + { + var temp = Walls[i].A.X; + Walls[i].A.X = Walls[i].B.X; + Walls[i].B.X = temp; + } + } + } } - struct Line + class Line { - public readonly Vector2 A, B; + public Vector2 A, B; public readonly RuinStructureType Type; @@ -183,7 +200,7 @@ namespace Barotrauma.RuinGeneration private set; } - public Ruin(VoronoiCell closestPathCell, List caveCells, Rectangle area) + public Ruin(VoronoiCell closestPathCell, List caveCells, Rectangle area, bool mirror = false) { Area = area; @@ -194,10 +211,10 @@ namespace Barotrauma.RuinGeneration allShapes = new List(); - Generate(closestPathCell, caveCells, area); + Generate(closestPathCell, caveCells, area, mirror); } - public void Generate(VoronoiCell closestPathCell, List caveCells, Rectangle area) + public void Generate(VoronoiCell closestPathCell, List caveCells, Rectangle area, bool mirror = false) { corridors.Clear(); rooms.Clear(); @@ -289,13 +306,21 @@ namespace Barotrauma.RuinGeneration BTRoom.CalculateDistancesFromEntrance(entranceRoom, corridors); - allShapes = GenerateStructures(caveCells); + allShapes = GenerateStructures(caveCells, area, mirror); } - private List GenerateStructures(List caveCells) + private List GenerateStructures(List caveCells, Rectangle ruinArea, bool mirror) { List shapes = new List(rooms); shapes.AddRange(corridors); + + if (mirror) + { + foreach (RuinShape shape in shapes) + { + shape.MirrorX(ruinArea.Center.ToVector2()); + } + } foreach (RuinShape leaf in shapes) { From 5b019621b7562bac4c932a00e431b8b7f788bcfb Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 27 Jul 2018 11:14:09 +0300 Subject: [PATCH 080/198] Fixed sea floor having a wrong collision category, causing artifacts to spawn below it. Closes #532 --- Barotrauma/BarotraumaShared/Source/Map/Levels/LevelWall.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelWall.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelWall.cs index b79ba05fc..306a7ad40 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelWall.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelWall.cs @@ -30,6 +30,7 @@ namespace Barotrauma vertices[3] = vertices[1] + extendAmount; VoronoiCell wallCell = new VoronoiCell(vertices); + wallCell.CellType = CellType.Edge; wallCell.edges[0].cell1 = wallCell; wallCell.edges[1].cell1 = wallCell; wallCell.edges[2].cell1 = wallCell; @@ -47,6 +48,10 @@ namespace Barotrauma } bodies = CaveGenerator.GeneratePolygons(cells, level, out List triangles, false); + foreach (var body in bodies) + { + body.CollisionCategories = Physics.CollisionLevel; + } #if CLIENT List bodyVertices = CaveGenerator.GenerateRenderVerticeList(triangles); From 3b4a6c00813348292b3de4c781d60d5c4ef42443 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 27 Jul 2018 13:01:04 +0300 Subject: [PATCH 081/198] Ruin and seafloor mirroring works now --- .../Source/Map/Levels/Level.cs | 49 +++++++++++++------ .../Source/Map/Levels/Ruins/RuinGenerator.cs | 24 +++++---- 2 files changed, 50 insertions(+), 23 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs index a7314c6dd..a8a394c14 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs @@ -397,11 +397,13 @@ namespace Barotrauma // remove unnecessary cells and create some holes at the bottom of the level //---------------------------------------------------------------------------------- + System.Diagnostics.Debug.WriteLine("cellcount before cleaning: " + cells.Count); cells = CleanCells(pathCells); pathCells.AddRange(CreateBottomHoles(generationParams.BottomHoleProbability, new Rectangle( (int)(borders.Width * 0.2f), 0, (int)(borders.Width * 0.6f), (int)(borders.Height * 0.8f)))); - + System.Diagnostics.Debug.WriteLine("cellcount after bottom holes: " + cells.Count); + foreach (VoronoiCell cell in cells) { if (cell.Center.Y < borders.Height / 2) continue; @@ -412,6 +414,7 @@ namespace Barotrauma // initialize the cells that are still left and insert them into the cell grid //---------------------------------------------------------------------------------- + System.Diagnostics.Debug.WriteLine("pathcells before init: " + cells.Count); foreach (VoronoiCell cell in pathCells) { cell.edges.ForEach(e => e.OutsideLevel = false); @@ -432,11 +435,15 @@ namespace Barotrauma // mirror if needed //---------------------------------------------------------------------------------- + System.Diagnostics.Debug.WriteLine("cellcount: "+cells.Count); + System.Diagnostics.Debug.WriteLine("pathcellcount: " + pathCells.Count); if (mirror) { HashSet mirroredEdges = new HashSet(); HashSet mirroredSites = new HashSet(); - foreach (VoronoiCell cell in cells) + List allCells = new List(cells); + allCells.AddRange(pathCells); + foreach (VoronoiCell cell in allCells) { foreach (GraphEdge edge in cell.edges) { @@ -500,7 +507,7 @@ namespace Barotrauma } int testSync = Rand.Int(1000, Rand.RandSync.Server); - DebugConsole.NewMessage("TESTSYNC: " + testSync + " ---------------------------------------------",Color.White); + System.Diagnostics.Debug.WriteLine("TESTSYNC: " + testSync + " ---------------------------------------------",Color.White); //---------------------------------------------------------------------------------- // generate the bodies and rendered triangles of the cells @@ -529,7 +536,7 @@ namespace Barotrauma bodies.Add(TopBarrier); - GenerateSeaFloor(); + GenerateSeaFloor(mirror); backgroundSpriteManager.PlaceSprites(this, generationParams.BackgroundSpriteAmount); #if CLIENT @@ -728,7 +735,7 @@ namespace Barotrauma return newCells; } - private void GenerateSeaFloor() + private void GenerateSeaFloor(bool mirror) { BottomPos = generationParams.SeaFloorDepth; SeaFloorTopPos = BottomPos; @@ -751,16 +758,24 @@ namespace Barotrauma { for (int i = 0; i < bottomPositions.Count - 1; i++) { - bottomPositions.Insert(i+1, + bottomPositions.Insert(i + 1, (bottomPositions[i] + bottomPositions[i + 1]) / 2.0f + Vector2.UnitY * Rand.Range(0.0f, generationParams.SeaFloorVariance, Rand.RandSync.Server)); - + i++; } currInverval /= 2.0f; } + if (mirror) + { + for (int i = 0; i < bottomPositions.Count; i++) + { + bottomPositions[i] = new Vector2(borders.Size.X - bottomPositions[i].X, bottomPositions[i].Y); + } + } + SeaFloorTopPos = bottomPositions.Max(p => p.Y); extraWalls = new LevelWall[] { new LevelWall(bottomPositions, new Vector2(0.0f, -2000.0f), backgroundColor, this) }; @@ -878,6 +893,10 @@ namespace Barotrauma float ruinRadius = Math.Max(ruinSize.X, ruinSize.Y) * 0.5f; System.Diagnostics.Debug.WriteLine("Cell count " + cells.Count); + for (int i = 0; i shape2.DistanceFromEntrance.CompareTo(shape1.DistanceFromEntrance)); for (int i = 0; i < 4; i++) { @@ -1091,15 +1115,12 @@ namespace Barotrauma List cells = new List(); - for (int x = startX; x <= endX; x++) + for (int y = startY; y <= endY; y++) { - for (int y = startY; y <= endY; y++) + for (int x = startX; x <= endX; x++) { - foreach (VoronoiCell cell in cellGrid[x, y]) - { - cells.Add(cell); - } - } + foreach (VoronoiCell cell in cellGrid[x, y]) cells.Add(cell); + } } if (extraWalls != null) diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/Ruins/RuinGenerator.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/Ruins/RuinGenerator.cs index 3fc1a12a0..f4126af3e 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/Ruins/RuinGenerator.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/Ruins/RuinGenerator.cs @@ -1,6 +1,7 @@ using FarseerPhysics; using FarseerPhysics.Factories; using Microsoft.Xna.Framework; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -35,7 +36,7 @@ namespace Barotrauma.RuinGeneration public Alignment GetLineAlignment(Line line) { - if (line.A.Y == line.B.Y) + if (line.IsHorizontal) { if (line.A.Y > rect.Center.Y && line.B.Y > rect.Center.Y) { @@ -70,7 +71,7 @@ namespace Barotrauma.RuinGeneration foreach (Line line in Walls) { - if (line.A.X == line.B.X) //vertical line + if (!line.IsHorizontal) //vertical line { //line doesn't intersect the rectangle if (rectangle.X > line.A.X || rectangle.Right < line.A.X || @@ -100,7 +101,7 @@ namespace Barotrauma.RuinGeneration newLines.Add(new Line(new Vector2(line.A.X, rectangle.Bottom), line.B, line.Type)); } } - else if (line.A.Y == line.B.Y) //horizontal line + else { //line doesn't intersect the rectangle if (rectangle.X > line.B.X || rectangle.Right < line.A.X || @@ -130,11 +131,6 @@ namespace Barotrauma.RuinGeneration newLines.Add(new Line(new Vector2(rectangle.Right, line.A.Y), line.B, line.Type)); } } - else - { - DebugConsole.ThrowError("Error in StructureGenerator.SplitLines - lines must be axis aligned"); - } - } Walls = newLines; @@ -164,6 +160,11 @@ namespace Barotrauma.RuinGeneration public readonly RuinStructureType Type; + public bool IsHorizontal + { + get { return Math.Abs(A.Y - B.Y) < Math.Abs(A.X - B.X); } + } + public Line(Vector2 a, Vector2 b, RuinStructureType type) { Debug.Assert(a.X <= b.X); @@ -260,7 +261,12 @@ namespace Barotrauma.RuinGeneration float shortestDistance = 0.0f; foreach (BTRoom leaf in rooms) { - float distance = Vector2.Distance(leaf.Rect.Center.ToVector2(), closestPathCell.Center); + Vector2 leafPos = leaf.Rect.Center.ToVector2(); + if (mirror) + { + leafPos.X = area.Center.X + (area.Center.X - leafPos.X); + } + float distance = Vector2.Distance(leafPos, closestPathCell.Center); if (entranceRoom == null || distance < shortestDistance) { entranceRoom = leaf; From 5dcb7bba9b9807464442b122a94c51c27b645a45 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 27 Jul 2018 14:50:42 +0300 Subject: [PATCH 082/198] Mirroring background sprite positions partially working. They spawn at the correct edge, but not at the correct position or rotation. --- .../Source/Map/Levels/BackgroundSpriteManager.cs | 8 +++++++- Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/BackgroundSpriteManager.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/BackgroundSpriteManager.cs index 565d8e90f..8d90ae775 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/BackgroundSpriteManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/BackgroundSpriteManager.cs @@ -243,7 +243,13 @@ namespace Barotrauma if (prefab.SpawnPos.HasFlag(BackgroundSpritePrefab.SpawnPosType.Wall)) cells.AddRange(level.GetCells(randomPos)); if (prefab.SpawnPos.HasFlag(BackgroundSpritePrefab.SpawnPosType.SeaFloor)) cells.AddRange(level.ExtraWalls[0].Cells); - + + //make sure the cells are in the same order regardless of whether the level is mirrored or not + cells.Sort((c1, c2) => { return level.Mirrored ? Math.Sign(c1.Center.X - c2.Center.X) : -Math.Sign(c1.Center.X - c2.Center.X); }); + + /*System.Diagnostics.Debug.WriteLine("FindSpritePosition - prefab: "+System.IO.Path.GetFileNameWithoutExtension(prefab.Sprite.FilePath)+" cells: "+cells.Count+" spawnpos: "+ prefab.SpawnPos+" randompos: "+ randomPos); + System.Diagnostics.Debug.WriteLine(string.Join(", ",cells.Select(c => level.cells.IndexOf(c))));*/ + if (cells.Any()) { VoronoiCell cell = cells[Rand.Int(cells.Count, Rand.RandSync.Server)]; diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs index a8a394c14..5c46f7405 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs @@ -55,7 +55,8 @@ namespace Barotrauma //private float shaftHeight; //List bodies; - private List cells; + //TODO: change back to private + public List cells; //private VertexBuffer vertexBuffer; @@ -452,11 +453,16 @@ namespace Barotrauma edge.point2.X = borders.Width - edge.point2.X; if (!mirroredSites.Contains(edge.site1)) { + //make sure that sites right at the edge of a grid cell end up in the same cell as in the non-mirrored level + if (edge.site1.coord.x % GridCellSize < 1.0f && + edge.site1.coord.x % GridCellSize >= 0.0f) edge.site1.coord.x += 1.0f; edge.site1.coord.x = borders.Width - edge.site1.coord.x; mirroredSites.Add(edge.site1); } if (!mirroredSites.Contains(edge.site2)) { + if (edge.site2.coord.x % GridCellSize < 1.0f && + edge.site2.coord.x % GridCellSize >= 0.0f) edge.site2.coord.x += 1.0f; edge.site2.coord.x = borders.Width - edge.site2.coord.x; mirroredSites.Add(edge.site2); } From 6209f48fffbe44b22a0f43c1e7f530b3f09b1423 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 27 Jul 2018 16:11:19 +0300 Subject: [PATCH 083/198] More error logging to diagnose invalid header errors --- .../Source/Networking/GameClient.cs | 6 ++++++ .../ClientEntityEventManager.cs | 19 ++++++++++--------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs index c794bd446..07829eb16 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs @@ -1044,6 +1044,12 @@ namespace Barotrauma.Networking } } + errorLines.Add("Last console messages:"); + for (int i = DebugConsole.Messages.Count - 1; i > 0; i--) + { + errorLines.Add("[" + DebugConsole.Messages[i].Time + "] " + DebugConsole.Messages[i].Text); + } + foreach (string line in errorLines) { DebugConsole.ThrowError(line); diff --git a/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs b/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs index f8cec2fe5..90e02538e 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs @@ -159,23 +159,24 @@ namespace Barotrauma.Networking //skip the event if we've already received it or if the entity isn't found if (thisEventID != (UInt16)(lastReceivedID + 1) || entity == null) { - if (GameSettings.VerboseLogging) + if (thisEventID != (UInt16) (lastReceivedID + 1)) { - if (thisEventID != (UInt16) (lastReceivedID + 1)) + if (GameSettings.VerboseLogging) { DebugConsole.NewMessage( - "received msg " + thisEventID + " (waiting for " + (lastReceivedID + 1) + ")", + "Received msg " + thisEventID + " (waiting for " + (lastReceivedID + 1) + ")", thisEventID < lastReceivedID + 1 ? Microsoft.Xna.Framework.Color.Yellow : Microsoft.Xna.Framework.Color.Red); } - else if (entity == null) - { - DebugConsole.NewMessage( - "received msg " + thisEventID + ", entity " + entityID + " not found", - Microsoft.Xna.Framework.Color.Red); - } } + else if (entity == null) + { + DebugConsole.NewMessage( + "Received msg " + thisEventID + ", entity " + entityID + " not found", + Microsoft.Xna.Framework.Color.Red); + } + msg.Position += msgLength * 8; } else From b7b033dac1bfdb0c12ce4b574a6f3d6c1edcb242 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Sun, 29 Jul 2018 21:16:59 +0300 Subject: [PATCH 084/198] Made the traitorlist command usable by clients (assuming they have the permission to use it). Closes #539 --- Barotrauma/BarotraumaShared/Source/DebugConsole.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs index 7ada53b2d..927c61e70 100644 --- a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs @@ -190,6 +190,17 @@ namespace Barotrauma NewMessage("- Traitor " + t.Character.Name + "'s target is " + t.TargetCharacter.Name + ".", Color.Cyan); } NewMessage("The code words are: " + traitorManager.codeWords + ", response: " + traitorManager.codeResponse + ".", Color.Cyan); + }, + null, + (Client client, Vector2 cursorPos, string[] args) => + { + TraitorManager traitorManager = GameMain.Server.TraitorManager; + if (traitorManager == null) return; + foreach (Traitor t in traitorManager.TraitorList) + { + GameMain.Server.SendConsoleMessage("- Traitor " + t.Character.Name + "'s target is " + t.TargetCharacter.Name + ".", client); + } + GameMain.Server.SendConsoleMessage("The code words are: " + traitorManager.codeWords + ", response: " + traitorManager.codeResponse + ".", client); })); commands.Add(new Command("itemlist", "itemlist: List all the item prefabs available for spawning.", (string[] args) => From 458c972580c2f9be9c0d64629be173eb697b6155 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Sun, 29 Jul 2018 22:28:56 +0300 Subject: [PATCH 085/198] More server-side EntityEvent error logging, Item.ServerWrite does some error checks and writes NetEntityEvent.Type.Invalid as the type of the event instead of attempting to write a potentially unreadable message. --- .../BarotraumaClient/Source/Items/Item.cs | 2 + .../BarotraumaShared/Source/Items/Item.cs | 56 ++++++++++++++++++- .../Source/Networking/GameServer.cs | 5 +- .../NetEntityEvent/NetEntityEvent.cs | 1 + 4 files changed, 60 insertions(+), 4 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Item.cs b/Barotrauma/BarotraumaClient/Source/Items/Item.cs index 5544e8c3a..b360b4475 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Item.cs @@ -406,6 +406,8 @@ namespace Barotrauma case NetEntityEvent.Type.ChangeProperty: ReadPropertyChange(msg); break; + case NetEntityEvent.Type.Invalid: + break; } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index 42fb6979a..515283a0b 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -1284,18 +1284,58 @@ namespace Barotrauma { if (extraData == null || extraData.Length == 0 || !(extraData[0] is NetEntityEvent.Type)) { + string errorMsg = ""; + if (extraData == null) + { + errorMsg = "Failed to write a network event for the item \"" + Name + "\" - event data was null."; + } + else if (extraData.Length == 0) + { + errorMsg = "Failed to write a network event for the item \"" + Name + "\" - event data was empty."; + } + else + { + errorMsg = "Failed to write a network event for the item \"" + Name + "\" - event type not set."; + } + msg.WriteRangedInteger(0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1, (int)NetEntityEvent.Type.Invalid); + DebugConsole.Log(errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Item.ServerWrite:InvalidData" + Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); return; } + long initialWritePos = msg.Position; + NetEntityEvent.Type eventType = (NetEntityEvent.Type)extraData[0]; msg.WriteRangedInteger(0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1, (int)eventType); switch (eventType) { case NetEntityEvent.Type.ComponentState: + string componentErrorMsg = ""; + if (extraData.Length < 2 || !(extraData[1] is int)) + { + componentErrorMsg = "Failed to write a component state event for the item \"" + Name + "\" - component index not given."; + } int componentIndex = (int)extraData[1]; - msg.WriteRangedInteger(0, components.Count-1, componentIndex); - - (components[componentIndex] as IServerSerializable).ServerWrite(msg, c, extraData); + if (componentIndex < 0 || componentIndex >= components.Count) + { + componentErrorMsg = "Failed to write a component state event for the item \"" + Name + "\" - component index out of range (" + componentIndex + ")."; + } + else if (!(components[componentIndex] is IServerSerializable)) + { + componentErrorMsg = "Failed to write a component state event for the item \"" + Name + "\" - component \"" + components[componentIndex] + "\" is not server serializable."; + } + if (string.IsNullOrEmpty(componentErrorMsg)) + { + msg.WriteRangedInteger(0, components.Count - 1, componentIndex); + (components[componentIndex] as IServerSerializable).ServerWrite(msg, c, extraData); + } + else + { + msg.Position = initialWritePos; + msg.WriteRangedInteger(0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1, (int)NetEntityEvent.Type.Invalid); + DebugConsole.Log(componentErrorMsg); + GameAnalyticsManager.AddErrorEventOnce("Item.ServerWrite:InvalidComponentData" + Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, componentErrorMsg); + } break; case NetEntityEvent.Type.InventoryState: ownInventory.ServerWrite(msg, c, extraData); @@ -1321,6 +1361,16 @@ namespace Barotrauma case NetEntityEvent.Type.ChangeProperty: WritePropertyChange(msg, extraData); break; + default: + { + //event type not valid for items - rewind the write position and write invalid event type + msg.Position = initialWritePos; + msg.WriteRangedInteger(0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1, (int)NetEntityEvent.Type.Invalid); + string errorMsg = "Failed to write a network event for the item \"" + Name + "\" - \"" + eventType + "\" is not a valid entity event type for items."; + DebugConsole.Log(errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Item.ServerWrite:InvalidData" + Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + } + break; } } diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs index 6cd095e1a..cd34f1c6f 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs @@ -504,7 +504,10 @@ namespace Barotrauma.Networking } catch (Exception e) { - DebugConsole.ThrowError("Failed to write a network message for the client \""+c.Name+"\"!", e); + DebugConsole.ThrowError("Failed to write a network message for the client \"" + c.Name + "\"!", e); + GameAnalyticsManager.AddErrorEventOnce("GameServer.Update:ClientWriteFailed" + e.StackTrace, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Failed to write a network message for the client \"" + c.Name + "\"! (MidRoundSyncing: " + c.NeedsMidRoundSync + ")\n" + + e.Message + "\n" + e.StackTrace); } } diff --git a/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEvent.cs b/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEvent.cs index e074a78de..53b5af950 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEvent.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEvent.cs @@ -7,6 +7,7 @@ namespace Barotrauma.Networking { public enum Type { + Invalid, ComponentState, InventoryState, Status, From 5233b51e18b86b0fc7c5f12740e7c8e8cc91b982 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 30 Jul 2018 10:12:55 +0300 Subject: [PATCH 086/198] More error checks in Item.ServerWrite --- .../BarotraumaShared/Source/Items/Item.cs | 58 ++++++++++--------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index 515283a0b..0defef5e2 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -1282,9 +1282,9 @@ namespace Barotrauma public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null) { + string errorMsg = ""; if (extraData == null || extraData.Length == 0 || !(extraData[0] is NetEntityEvent.Type)) { - string errorMsg = ""; if (extraData == null) { errorMsg = "Failed to write a network event for the item \"" + Name + "\" - event data was null."; @@ -1310,32 +1310,24 @@ namespace Barotrauma switch (eventType) { case NetEntityEvent.Type.ComponentState: - string componentErrorMsg = ""; if (extraData.Length < 2 || !(extraData[1] is int)) { - componentErrorMsg = "Failed to write a component state event for the item \"" + Name + "\" - component index not given."; + errorMsg = "Failed to write a component state event for the item \"" + Name + "\" - component index not given."; + break; } int componentIndex = (int)extraData[1]; if (componentIndex < 0 || componentIndex >= components.Count) { - componentErrorMsg = "Failed to write a component state event for the item \"" + Name + "\" - component index out of range (" + componentIndex + ")."; + errorMsg = "Failed to write a component state event for the item \"" + Name + "\" - component index out of range (" + componentIndex + ")."; + break; } else if (!(components[componentIndex] is IServerSerializable)) { - componentErrorMsg = "Failed to write a component state event for the item \"" + Name + "\" - component \"" + components[componentIndex] + "\" is not server serializable."; - } - if (string.IsNullOrEmpty(componentErrorMsg)) - { - msg.WriteRangedInteger(0, components.Count - 1, componentIndex); - (components[componentIndex] as IServerSerializable).ServerWrite(msg, c, extraData); - } - else - { - msg.Position = initialWritePos; - msg.WriteRangedInteger(0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1, (int)NetEntityEvent.Type.Invalid); - DebugConsole.Log(componentErrorMsg); - GameAnalyticsManager.AddErrorEventOnce("Item.ServerWrite:InvalidComponentData" + Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, componentErrorMsg); + errorMsg = "Failed to write a component state event for the item \"" + Name + "\" - component \"" + components[componentIndex] + "\" is not server serializable."; + break; } + msg.WriteRangedInteger(0, components.Count - 1, componentIndex); + (components[componentIndex] as IServerSerializable).ServerWrite(msg, c, extraData); break; case NetEntityEvent.Type.InventoryState: ownInventory.ServerWrite(msg, c, extraData); @@ -1359,19 +1351,29 @@ namespace Barotrauma msg.Write(targetID); break; case NetEntityEvent.Type.ChangeProperty: - WritePropertyChange(msg, extraData); - break; - default: + try { - //event type not valid for items - rewind the write position and write invalid event type - msg.Position = initialWritePos; - msg.WriteRangedInteger(0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1, (int)NetEntityEvent.Type.Invalid); - string errorMsg = "Failed to write a network event for the item \"" + Name + "\" - \"" + eventType + "\" is not a valid entity event type for items."; - DebugConsole.Log(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("Item.ServerWrite:InvalidData" + Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + WritePropertyChange(msg, extraData); + } + catch (Exception e) + { + errorMsg = "Failed to write a ChangeProperty network event for the item \"" + Name + "\" (" + e.Message + ")"; } break; + default: + errorMsg = "Failed to write a network event for the item \"" + Name + "\" - \"" + eventType + "\" is not a valid entity event type for items."; + break; } + + if (!string.IsNullOrEmpty(errorMsg)) + { + //something went wrong - rewind the write position and write invalid event type to prevent creating an unreadable event + msg.Position = initialWritePos; + msg.WriteRangedInteger(0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1, (int)NetEntityEvent.Type.Invalid); + DebugConsole.Log(errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Item.ServerWrite:" + errorMsg, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + } + } public void ServerRead(ClientNetObject type, NetBuffer msg, Client c) @@ -1503,6 +1505,10 @@ namespace Barotrauma throw new System.NotImplementedException("Serializing item properties of the type \"" + value.GetType() + "\" not supported"); } } + else + { + throw new ArgumentException("Failed to write propery value - property \"" + (property == null ? "null" : property.Name) + "\" is not serializable."); + } } private void ReadPropertyChange(NetBuffer msg) From 9964923eba092eff2f38af9774ee638536c94655 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 30 Jul 2018 10:46:52 +0300 Subject: [PATCH 087/198] Fixed server writing invalid item network events incorrectly (NetBuffer.Position is the read position, not write position) --- Barotrauma/BarotraumaShared/Source/Items/Item.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index 0defef5e2..f37865b53 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -1303,7 +1303,7 @@ namespace Barotrauma return; } - long initialWritePos = msg.Position; + int initialWritePos = msg.LengthBits; NetEntityEvent.Type eventType = (NetEntityEvent.Type)extraData[0]; msg.WriteRangedInteger(0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1, (int)eventType); @@ -1368,7 +1368,8 @@ namespace Barotrauma if (!string.IsNullOrEmpty(errorMsg)) { //something went wrong - rewind the write position and write invalid event type to prevent creating an unreadable event - msg.Position = initialWritePos; + msg.ReadBits(msg.Data, 0, initialWritePos); + msg.LengthBits = initialWritePos; msg.WriteRangedInteger(0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1, (int)NetEntityEvent.Type.Invalid); DebugConsole.Log(errorMsg); GameAnalyticsManager.AddErrorEventOnce("Item.ServerWrite:" + errorMsg, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); From c66098ca4e7dd8f3233e3592809e61b4e617ca42 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 30 Jul 2018 11:00:21 +0300 Subject: [PATCH 088/198] Fixed only the first inventory of an item being synced (e.g. clients wouldn't get notified when an item is contained in the "output inventory" of a fabricator or deconstructor). --- .../BarotraumaClient/Source/Items/Item.cs | 8 ++++--- .../BarotraumaShared/Source/DebugConsole.cs | 2 +- .../Source/Items/Inventory.cs | 2 +- .../BarotraumaShared/Source/Items/Item.cs | 22 +++++++++++++++++-- .../Source/Items/ItemInventory.cs | 22 +++++++++++++++++++ 5 files changed, 49 insertions(+), 7 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Item.cs b/Barotrauma/BarotraumaClient/Source/Items/Item.cs index b360b4475..437e302a3 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Item.cs @@ -377,7 +377,8 @@ namespace Barotrauma (components[componentIndex] as IServerSerializable).ClientRead(type, msg, sendingTime); break; case NetEntityEvent.Type.InventoryState: - ownInventory.ClientRead(type, msg, sendingTime); + int containerIndex = msg.ReadRangedInteger(0, components.Count - 1); + (components[containerIndex] as ItemContainer).Inventory.ClientRead(type, msg, sendingTime); break; case NetEntityEvent.Type.Status: condition = msg.ReadRangedSingle(0.0f, prefab.Health, 8); @@ -425,11 +426,12 @@ namespace Barotrauma case NetEntityEvent.Type.ComponentState: int componentIndex = (int)extraData[1]; msg.WriteRangedInteger(0, components.Count - 1, componentIndex); - (components[componentIndex] as IClientSerializable).ClientWrite(msg, extraData); break; case NetEntityEvent.Type.InventoryState: - ownInventory.ClientWrite(msg, extraData); + int containerIndex = (int)extraData[1]; + msg.WriteRangedInteger(0, components.Count - 1, containerIndex); + (components[containerIndex] as ItemContainer).Inventory.ClientWrite(msg, extraData); break; case NetEntityEvent.Type.Repair: if (FixRequirements.Count > 0) diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs index 927c61e70..ebeaf4076 100644 --- a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs @@ -1723,7 +1723,7 @@ namespace Barotrauma var itemContainer = item.GetComponent(); if (itemContainer != null) { - GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.InventoryState }); + GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.InventoryState, 0 }); } GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.Status }); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs b/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs index eedb8504a..43a9a8efa 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs @@ -140,7 +140,7 @@ namespace Barotrauma } } - private void CreateNetworkEvent() + protected virtual void CreateNetworkEvent() { if (GameMain.Server != null) { diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index f37865b53..9625a01f7 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -1330,7 +1330,24 @@ namespace Barotrauma (components[componentIndex] as IServerSerializable).ServerWrite(msg, c, extraData); break; case NetEntityEvent.Type.InventoryState: - ownInventory.ServerWrite(msg, c, extraData); + if (extraData.Length < 2 || !(extraData[1] is int)) + { + errorMsg = "Failed to write an inventory state event for the item \"" + Name + "\" - component index not given."; + break; + } + int containerIndex = (int)extraData[1]; + if (containerIndex < 0 || containerIndex >= components.Count) + { + errorMsg = "Failed to write an inventory state event for the item \"" + Name + "\" - container index out of range (" + containerIndex + ")."; + break; + } + else if (!(components[containerIndex] is ItemContainer)) + { + errorMsg = "Failed to write an inventory state event for the item \"" + Name + "\" - component \"" + components[containerIndex] + "\" is not server serializable."; + break; + } + msg.WriteRangedInteger(0, components.Count - 1, containerIndex); + (components[containerIndex] as ItemContainer).Inventory.ServerWrite(msg, c); break; case NetEntityEvent.Type.Status: //clamp to (MaxHealth / 255.0f) if condition > 0.0f @@ -1389,7 +1406,8 @@ namespace Barotrauma (components[componentIndex] as IClientSerializable).ServerRead(type, msg, c); break; case NetEntityEvent.Type.InventoryState: - ownInventory.ServerRead(type, msg, c); + int containerIndex = msg.ReadRangedInteger(0, components.Count - 1); + (components[containerIndex] as ItemContainer).Inventory.ServerRead(type, msg, c); break; case NetEntityEvent.Type.Repair: if (FixRequirements.Count == 0) return; diff --git a/Barotrauma/BarotraumaShared/Source/Items/ItemInventory.cs b/Barotrauma/BarotraumaShared/Source/Items/ItemInventory.cs index 728eb9390..eb6039bc6 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/ItemInventory.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/ItemInventory.cs @@ -1,4 +1,5 @@ using Barotrauma.Items.Components; +using Barotrauma.Networking; using Microsoft.Xna.Framework; using System.Collections.Generic; @@ -84,6 +85,27 @@ namespace Barotrauma return wasPut; } + protected override void CreateNetworkEvent() + { + int componentIndex = container.Item.components.IndexOf(container); + if (componentIndex == -1) + { + DebugConsole.Log("Creating a network event for the item \"" + container.Item + "\" failed, ItemContainer not found in components"); + return; + } + + if (GameMain.Server != null) + { + GameMain.Server.CreateEntityEvent(Owner as IServerSerializable, new object[] { NetEntityEvent.Type.InventoryState, componentIndex }); + } +#if CLIENT + else if (GameMain.Client != null) + { + GameMain.Client.CreateEntityEvent(Owner as IClientSerializable, new object[] { NetEntityEvent.Type.InventoryState, componentIndex }); + } +#endif + } + public override void RemoveItem(Item item) { base.RemoveItem(item); From 8c598db10b106a25ea511aa9cf3c16288bfe4577 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 30 Jul 2018 11:41:03 +0300 Subject: [PATCH 089/198] Fixed errors in RespawnManager.ClientRead when not using a shuttle, extra diving gear does not spawn when respawning in main sub (TODO: automatically give and equip diving gear if the sub is flooded?) --- .../Source/Networking/RespawnManager.cs | 51 +++++++++++-------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs b/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs index d32650e6d..3189a49e8 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs @@ -353,6 +353,11 @@ namespace Barotrauma.Networking private IEnumerable ForceShuttleToPos(Vector2 position, float speed) { + if (respawnShuttle == null) + { + yield return CoroutineStatus.Success; + } + respawnShuttle.PhysicsBody.FarseerBody.IgnoreCollisionWith(Level.Loaded.TopBarrier); while (Math.Abs(position.Y - respawnShuttle.WorldPosition.Y) > 100.0f) @@ -374,6 +379,8 @@ namespace Barotrauma.Networking shuttleTransportTimer = maxTransportTime; shuttleReturnTimer = maxTransportTime; + if (respawnShuttle == null) return; + foreach (Item item in Item.ItemList) { if (item.Submarine != respawnShuttle) continue; @@ -448,7 +455,7 @@ namespace Barotrauma.Networking var server = networkMember as GameServer; if (server == null) return; - var respawnSub = respawnShuttle != null ? respawnShuttle : Submarine.MainSub; + var respawnSub = respawnShuttle ?? Submarine.MainSub; var clients = GetClientsToRespawn(); foreach (Client c in clients) @@ -514,31 +521,33 @@ namespace Barotrauma.Networking } #endif - Vector2 pos = cargoSp == null ? character.Position : cargoSp.Position; - - if (divingSuitPrefab != null && oxyPrefab != null) + if (respawnShuttle != null) { - var divingSuit = new Item(divingSuitPrefab, pos, respawnSub); - Spawner.CreateNetworkEvent(divingSuit, false); - respawnItems.Add(divingSuit); + Vector2 pos = cargoSp == null ? character.Position : cargoSp.Position; + if (divingSuitPrefab != null && oxyPrefab != null) + { + var divingSuit = new Item(divingSuitPrefab, pos, respawnSub); + Spawner.CreateNetworkEvent(divingSuit, false); + respawnItems.Add(divingSuit); - var oxyTank = new Item(oxyPrefab, pos, respawnSub); - Spawner.CreateNetworkEvent(oxyTank, false); - divingSuit.Combine(oxyTank); - respawnItems.Add(oxyTank); - } + var oxyTank = new Item(oxyPrefab, pos, respawnSub); + Spawner.CreateNetworkEvent(oxyTank, false); + divingSuit.Combine(oxyTank); + respawnItems.Add(oxyTank); + } - if (scooterPrefab != null && batteryPrefab != null) - { - var scooter = new Item(scooterPrefab, pos, respawnSub); - Spawner.CreateNetworkEvent(scooter, false); + if (scooterPrefab != null && batteryPrefab != null) + { + var scooter = new Item(scooterPrefab, pos, respawnSub); + Spawner.CreateNetworkEvent(scooter, false); - var battery = new Item(batteryPrefab, pos, respawnSub); - Spawner.CreateNetworkEvent(battery, false); + var battery = new Item(batteryPrefab, pos, respawnSub); + Spawner.CreateNetworkEvent(battery, false); - scooter.Combine(battery); - respawnItems.Add(scooter); - respawnItems.Add(battery); + scooter.Combine(battery); + respawnItems.Add(scooter); + respawnItems.Add(battery); + } } //give the character the items they would've gotten if they had spawned in the main sub From 60a563fe758787b048669feac97074526c49a10d Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 30 Jul 2018 13:35:26 +0300 Subject: [PATCH 090/198] EntityEvent fixes: - Clients wait for midround syncing to finish before applying the remote state to connection panels and inventories (because the wires connected to the connection panel or items in the inventory may not exist before the EntitySpawner events have been received). - Server writes 0 as the projectile ID if the projectile doesn't exist anymore when a Turret event is sent. - More info in networkevent error messages. --- .../Source/Items/Components/ItemComponent.cs | 14 ++-- .../Components/Signal/ConnectionPanel.cs | 65 ++++++++++++++++++ .../Source/Items/Components/Turret.cs | 4 +- .../Source/Items/Inventory.cs | 66 +++++++++++++++++++ .../Source/Networking/GameClient.cs | 5 ++ .../ClientEntityEventManager.cs | 15 ++++- .../Components/Signal/ConnectionPanel.cs | 54 +-------------- .../Source/Items/Components/Turret.cs | 4 +- .../Source/Items/Inventory.cs | 58 ---------------- 9 files changed, 167 insertions(+), 118 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs index ee2740938..764f758da 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs @@ -224,21 +224,27 @@ namespace Barotrauma.Items.Components } //Starts a coroutine that will read the correct state of the component from the NetBuffer when correctionTimer reaches zero. - protected void StartDelayedCorrection(ServerNetObject type, NetBuffer buffer, float sendingTime) + protected void StartDelayedCorrection(ServerNetObject type, NetBuffer buffer, float sendingTime, bool waitForMidRoundSync = false) { if (delayedCorrectionCoroutine != null) CoroutineManager.StopCoroutines(delayedCorrectionCoroutine); - delayedCorrectionCoroutine = CoroutineManager.StartCoroutine(DoDelayedCorrection(type, buffer, sendingTime)); + delayedCorrectionCoroutine = CoroutineManager.StartCoroutine(DoDelayedCorrection(type, buffer, sendingTime, waitForMidRoundSync)); } - private IEnumerable DoDelayedCorrection(ServerNetObject type, NetBuffer buffer, float sendingTime) + private IEnumerable DoDelayedCorrection(ServerNetObject type, NetBuffer buffer, float sendingTime, bool waitForMidRoundSync) { - while (correctionTimer > 0.0f) + while (GameMain.Client != null && + (correctionTimer > 0.0f || (waitForMidRoundSync && GameMain.Client.MidRoundSyncing))) { correctionTimer -= CoroutineManager.DeltaTime; yield return CoroutineStatus.Running; } + if (item.Removed || GameMain.Client == null) + { + yield return CoroutineStatus.Success; + } + ((IServerSerializable)this).ClientRead(type, buffer, sendingTime); correctionTimer = 0.0f; diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/ConnectionPanel.cs index b2f10ae90..c25397641 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/ConnectionPanel.cs @@ -1,5 +1,9 @@ using Barotrauma.Networking; +using Lidgren.Network; using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; +using System.Linq; namespace Barotrauma.Items.Components { @@ -24,5 +28,66 @@ namespace Barotrauma.Items.Components HighlightedWire = null; Connection.DrawConnections(spriteBatch, this, character); } + + + public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) + { + if (GameMain.Client.MidRoundSyncing) + { + //delay reading the state until midround syncing is done + //because some of the wires connected to the panel may not exist yet + int bitsToRead = Connections.Count * Connection.MaxLinked * 16; + StartDelayedCorrection(type, msg.ExtractBits(bitsToRead), sendingTime, waitForMidRoundSync: true); + } + else + { + ApplyRemoteState(msg); + } + } + + private void ApplyRemoteState(NetBuffer msg) + { + List prevWires = Connections.SelectMany(c => Array.FindAll(c.Wires, w => w != null)).ToList(); + List newWires = new List(); + + foreach (Connection connection in Connections) + { + connection.ClearConnections(); + } + + foreach (Connection connection in Connections) + { + for (int i = 0; i < Connection.MaxLinked; i++) + { + ushort wireId = msg.ReadUInt16(); + + Item wireItem = Entity.FindEntityByID(wireId) as Item; + if (wireItem == null) continue; + + Wire wireComponent = wireItem.GetComponent(); + if (wireComponent == null) continue; + + newWires.Add(wireComponent); + + connection.Wires[i] = wireComponent; + wireComponent.Connect(connection, false); + } + } + + foreach (Wire wire in prevWires) + { + if (wire.Connections[0] == null && wire.Connections[1] == null) + { + wire.Item.Drop(null); + } + //wires that are not in anyone's inventory (i.e. not currently being rewired) can never be connected to only one connection + // -> someone must have dropped the wire from the connection panel + else if (wire.Item.ParentInventory == null && + (wire.Connections[0] != null ^ wire.Connections[1] != null)) + { + wire.Item.Drop(null); + } + } + } } } diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Turret.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Turret.cs index 3141ee613..fdd20abf1 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Turret.cs @@ -148,8 +148,10 @@ namespace Barotrauma.Items.Components public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) { UInt16 projectileID = msg.ReadUInt16(); - Item projectile = Entity.FindEntityByID(projectileID) as Item; + //projectile removed, do nothing + if (projectileID == 0) return; + Item projectile = Entity.FindEntityByID(projectileID) as Item; if (projectile == null) { DebugConsole.ThrowError("Failed to launch a projectile - item with the ID \"" + projectileID + " not found"); diff --git a/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs b/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs index 7b1028a69..06a11e43c 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs @@ -1,4 +1,6 @@ using Barotrauma.Items.Components; +using Barotrauma.Networking; +using Lidgren.Network; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; @@ -464,5 +466,69 @@ namespace Barotrauma item.Sprite.Draw(spriteBatch, new Vector2(rect.X + rect.Width / 2, rect.Y + rect.Height / 2), item.GetSpriteColor()); } + + public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) + { + receivedItemIDs = new ushort[capacity]; + + for (int i = 0; i < capacity; i++) + { + receivedItemIDs[i] = msg.ReadUInt16(); + } + + //delay applying the new state if less than 1 second has passed since this client last sent a state to the server + //prevents the inventory from briefly reverting to an old state if items are moved around in quick succession + + //also delay if we're still midround syncing, some of the items in the inventory may not exist yet + if (syncItemsDelay > 0.0f || GameMain.Client.MidRoundSyncing) + { + if (syncItemsCoroutine != null) CoroutineManager.StopCoroutines(syncItemsCoroutine); + syncItemsCoroutine = CoroutineManager.StartCoroutine(SyncItemsAfterDelay()); + } + else + { + ApplyReceivedState(); + } + } + + private IEnumerable SyncItemsAfterDelay() + { + while (syncItemsDelay > 0.0f && GameMain.Client != null && GameMain.Client.MidRoundSyncing) + { + syncItemsDelay -= CoroutineManager.DeltaTime; + yield return CoroutineStatus.Running; + } + + if (Owner.Removed || GameMain.Client == null) + { + yield return CoroutineStatus.Success; + } + + ApplyReceivedState(); + + yield return CoroutineStatus.Success; + } + + private void ApplyReceivedState() + { + if (receivedItemIDs == null) return; + + for (int i = 0; i < capacity; i++) + { + if (receivedItemIDs[i] == 0) + { + if (Items[i] != null) Items[i].Drop(); + } + else + { + var item = Entity.FindEntityByID(receivedItemIDs[i]) as Item; + if (item == null) continue; + + TryPutItem(item, i, true, true, null, false); + } + } + + receivedItemIDs = null; + } } } diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs index 07829eb16..55f6d0feb 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs @@ -64,6 +64,11 @@ namespace Barotrauma.Networking { get { return fileReceiver; } } + + public bool MidRoundSyncing + { + get { return entityEventManager.MidRoundSyncing; } + } public GameClient(string newName) { diff --git a/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs b/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs index 90e02538e..79cfc308b 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs @@ -23,6 +23,11 @@ namespace Barotrauma.Networking private UInt16 lastReceivedID; + public bool MidRoundSyncing + { + get { return firstNewID.HasValue; } + } + public ClientEntityEventManager(GameClient client) { events = new List(); @@ -195,13 +200,19 @@ namespace Barotrauma.Networking catch (Exception e) { + string errorMsg = "Failed to read event for entity \"" + entity.ToString() + "\"! (MidRoundSyncing: " + thisClient.MidRoundSyncing + ")\n" + e.StackTrace; + errorMsg += "\nPrevious entities:"; + for (int j = entities.Count - 2; j >= 0; j--) + { + errorMsg += "\n" + (entities[j] == null ? "NULL" : entities[j].ToString()); + } + if (GameSettings.VerboseLogging) { DebugConsole.ThrowError("Failed to read event for entity \"" + entity.ToString() + "\"!", e); } GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:ReadFailed" + entity.ToString(), - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, - "Failed to read event for entity \"" + entity.ToString() + "\"!\n" + e.StackTrace); + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); msg.Position = msgPosition + msgLength * 8; } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs index 7cffc8836..159372397 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs @@ -150,11 +150,9 @@ namespace Barotrauma.Items.Components { foreach (Connection connection in Connections) { - Wire[] wires = Array.FindAll(connection.Wires, w => w != null); - msg.WriteRangedInteger(0, Connection.MaxLinked, wires.Length); - for (int i = 0; i < wires.Length; i++) + for (int i = 0; i < Connection.MaxLinked; i++) { - msg.Write(wires[i].Item.ID); + msg.Write(connection.Wires[i]?.Item == null ? (ushort)0 : connection.Wires[i].Item.ID); } } } @@ -295,52 +293,6 @@ namespace Barotrauma.Items.Components public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null) { ClientWrite(msg, extraData); - } - - public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) - { - List prevWires = Connections.SelectMany(c => Array.FindAll(c.Wires, w => w != null)).ToList(); - List newWires = new List(); - - foreach (Connection connection in Connections) - { - connection.ClearConnections(); - } - - foreach (Connection connection in Connections) - { - int wireCount = msg.ReadRangedInteger(0, Connection.MaxLinked); - for (int i = 0; i < wireCount; i++) - { - ushort wireId = msg.ReadUInt16(); - - Item wireItem = Entity.FindEntityByID(wireId) as Item; - if (wireItem == null) continue; - - Wire wireComponent = wireItem.GetComponent(); - if (wireComponent == null) continue; - - newWires.Add(wireComponent); - - connection.Wires[i] = wireComponent; - wireComponent.Connect(connection, false); - } - } - - foreach (Wire wire in prevWires) - { - if (wire.Connections[0] == null && wire.Connections[1] == null) - { - wire.Item.Drop(null); - } - //wires that are not in anyone's inventory (i.e. not currently being rewired) can never be connected to only one connection - // -> someone must have dropped the wire from the connection panel - else if (wire.Item.ParentInventory == null && - (wire.Connections[0] != null ^ wire.Connections[1] != null)) - { - wire.Item.Drop(null); - } - } - } + } } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs index 7ad0320a4..0c622bbc4 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs @@ -419,8 +419,8 @@ namespace Barotrauma.Items.Components public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null) { - //ID of the launched projectile - msg.Write(((Item)extraData[2]).ID); + Item item = (Item)extraData[2]; + msg.Write(item.Removed ? (ushort)0 : item.ID); } } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs b/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs index 43a9a8efa..c315aa67a 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs @@ -269,63 +269,5 @@ namespace Barotrauma msg.Write((ushort)(Items[i] == null ? 0 : Items[i].ID)); } } - - public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) - { - receivedItemIDs = new ushort[capacity]; - - for (int i = 0; i < capacity; i++) - { - receivedItemIDs[i] = msg.ReadUInt16(); - } - - if (syncItemsDelay > 0.0f) - { - //delay applying the new state if less than 1 second has passed since this client last sent a state to the server - //prevents the inventory from briefly reverting to an old state if items are moved around in quick succession - if (syncItemsCoroutine != null) CoroutineManager.StopCoroutines(syncItemsCoroutine); - - syncItemsCoroutine = CoroutineManager.StartCoroutine(SyncItemsAfterDelay()); - } - else - { - ApplyReceivedState(); - } - } - - private IEnumerable SyncItemsAfterDelay() - { - while (syncItemsDelay > 0.0f) - { - syncItemsDelay -= CoroutineManager.DeltaTime; - yield return CoroutineStatus.Running; - } - - ApplyReceivedState(); - - yield return CoroutineStatus.Success; - } - - private void ApplyReceivedState() - { - if (receivedItemIDs == null) return; - - for (int i = 0; i < capacity; i++) - { - if (receivedItemIDs[i] == 0) - { - if (Items[i] != null) Items[i].Drop(); - } - else - { - var item = Entity.FindEntityByID(receivedItemIDs[i]) as Item; - if (item == null) continue; - - TryPutItem(item, i, true, true, null, false); - } - } - - receivedItemIDs = null; - } } } From b4d0a1bd386a952a46c5defe35d3db989e18f402 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 30 Jul 2018 14:28:04 +0300 Subject: [PATCH 091/198] Inventory sync fix (component index included in events created when the server receives an inventory update from a client). --- 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 c315aa67a..0c76077c3 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs @@ -228,7 +228,7 @@ namespace Barotrauma } } - GameMain.Server.CreateEntityEvent(Owner as IServerSerializable, new object[] { NetEntityEvent.Type.InventoryState }); + CreateNetworkEvent(); foreach (Item item in Items.Distinct()) { From 7acd3f746d59102fbbc36e4e3fc1ef9f1774bd18 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 30 Jul 2018 14:30:27 +0300 Subject: [PATCH 092/198] StatusEffects don't attempt to use removed items or apply property values to removed entities. --- .../BarotraumaShared/Source/StatusEffects/StatusEffect.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/Source/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/Source/StatusEffects/StatusEffect.cs index 7f6db5d43..51096948a 100644 --- a/Barotrauma/BarotraumaShared/Source/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/Source/StatusEffects/StatusEffect.cs @@ -370,6 +370,7 @@ namespace Barotrauma { foreach (Item item in targets.FindAll(t => t is Item).Cast()) { + if (item.Removed) continue; item.Use(deltaTime, targets.FirstOrDefault(t => t is Character) as Character); } } @@ -396,10 +397,14 @@ namespace Barotrauma { foreach (ISerializableEntity target in targets) { + if (target is Entity targetEntity) + { + if (targetEntity.Removed) continue; + } + for (int i = 0; i < propertyNames.Length; i++) { SerializableProperty property; - if (target == null || target.SerializableProperties == null || !target.SerializableProperties.TryGetValue(propertyNames[i], out property)) continue; ApplyToProperty(property, propertyEffects[i], deltaTime); From 86df5f6b1a0233d1003613fe72ae50bff5c73a50 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 30 Jul 2018 14:31:30 +0300 Subject: [PATCH 093/198] Physics error checks & logging --- .../Source/Map/SubmarineBody.cs | 20 +++++++++++++++---- .../Source/Physics/PhysicsBody.cs | 9 +++++++-- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs b/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs index 09f9e7a30..94663b760 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs @@ -480,9 +480,7 @@ namespace Barotrauma Vector2 avgContactNormal = Vector2.Zero; foreach (Contact levelContact in levelContacts) { - Vector2 contactNormal; - FixedArray2 temp; - levelContact.GetWorldManifold(out contactNormal, out temp); + levelContact.GetWorldManifold(out Vector2 contactNormal, out FixedArray2 temp); //if the contact normal is pointing from the limb towards the level cell it's touching, flip the normal VoronoiCell cell = levelContact.FixtureB.UserData is VoronoiCell ? @@ -504,7 +502,21 @@ namespace Barotrauma float contactDot = Vector2.Dot(Body.LinearVelocity, -avgContactNormal); if (contactDot > 0.0f) { - Body.LinearVelocity -= Vector2.Normalize(Body.LinearVelocity) * contactDot; + Vector2 velChange = Vector2.Normalize(Body.LinearVelocity) * contactDot; + if (!MathUtils.IsValid(velChange)) + { + GameAnalyticsManager.AddErrorEventOnce( + "SubmarineBody.HandleLimbCollision:" + submarine.ID, + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Invalid velocity change in SubmarineBody.HandleLimbCollision (submarine velocity: " + Body.LinearVelocity + + ", avgContactNormal: " + avgContactNormal + + ", contactDot: " + contactDot + + ", velChange: " + velChange + ")"); + return; + } + + + Body.LinearVelocity -= velChange; float damageAmount = contactDot * Body.Mass / limb.character.Mass; diff --git a/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs b/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs index b1e88218a..3829c257e 100644 --- a/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs +++ b/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs @@ -184,11 +184,16 @@ namespace Barotrauma } catch (Exception e) { - DebugConsole.ThrowError("Exception in PhysicsBody.Enabled = "+value+" ("+isPhysEnabled+")",e); - if (UserData!=null) DebugConsole.NewMessage("PhysicsBody UserData: "+UserData.GetType().ToString(), Color.Red); + DebugConsole.ThrowError("Exception in PhysicsBody.Enabled = " + value + " (" + isPhysEnabled + ")", e); + if (UserData != null) DebugConsole.NewMessage("PhysicsBody UserData: " + UserData.GetType().ToString(), Color.Red); if (GameMain.World.ContactManager == null) DebugConsole.NewMessage("ContactManager is null!", Color.Red); else if (GameMain.World.ContactManager.BroadPhase == null) DebugConsole.NewMessage("Broadphase is null!", Color.Red); if (body.FixtureList == null) DebugConsole.NewMessage("FixtureList is null!", Color.Red); + + if (UserData is Entity entity) + { + DebugConsole.NewMessage("Entity \"" + entity.ToString() + "\" removed!", Color.Red); + } } } } From 13a2e4d33b6726c7ef249609b57e3ee35df06ac3 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 30 Jul 2018 20:02:56 +0300 Subject: [PATCH 094/198] StatusEffect.StopAll clears DurationList and DelayList. Closes #543 --- .../BarotraumaShared/Source/StatusEffects/DelayedEffect.cs | 2 +- .../BarotraumaShared/Source/StatusEffects/StatusEffect.cs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/StatusEffects/DelayedEffect.cs b/Barotrauma/BarotraumaShared/Source/StatusEffects/DelayedEffect.cs index 225c061ca..d8c318728 100644 --- a/Barotrauma/BarotraumaShared/Source/StatusEffects/DelayedEffect.cs +++ b/Barotrauma/BarotraumaShared/Source/StatusEffects/DelayedEffect.cs @@ -13,7 +13,7 @@ namespace Barotrauma } class DelayedEffect : StatusEffect { - public static List DelayList = new List(); + public static readonly List DelayList = new List(); private float delay; diff --git a/Barotrauma/BarotraumaShared/Source/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/Source/StatusEffects/StatusEffect.cs index 51096948a..4293ee1b3 100644 --- a/Barotrauma/BarotraumaShared/Source/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/Source/StatusEffects/StatusEffect.cs @@ -50,7 +50,7 @@ namespace Barotrauma private HashSet tags; private readonly float duration; - public static List DurationList = new List(); + public static readonly List DurationList = new List(); public bool CheckConditionalAlways; //Always do the conditional checks for the duration/delay. If false, only check conditional on apply. @@ -510,6 +510,8 @@ namespace Barotrauma public static void StopAll() { CoroutineManager.StopCoroutines("statuseffect"); + DelayedEffect.DelayList.Clear(); + DurationList.Clear(); } public void AddTag(string tag) From d881a7060bd9ebaa3a9178b02bb0faa07c566170 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 31 Jul 2018 10:41:41 +0300 Subject: [PATCH 095/198] Updated tutorial info text to match the new color of the sonar display. Closes #550 --- .../Source/GameSession/GameModes/Tutorials/BasicTutorial.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/BasicTutorial.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/BasicTutorial.cs index 84140c9db..ac584f7b8 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/BasicTutorial.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/BasicTutorial.cs @@ -269,7 +269,7 @@ namespace Barotrauma.Tutorials } yield return new WaitForSeconds(0.5f); - infoBox = CreateInfoFrame("The green rectangle in the middle is the submarine, and the flickering shapes outside it are the walls of an underwater cavern. " + infoBox = CreateInfoFrame("The blue rectangle in the middle is the submarine, and the flickering shapes outside it are the walls of an underwater cavern. " + "Try moving the submarine by clicking somewhere on the monitor and dragging the pointer to the direction you want to go to."); while (steering.TargetVelocity == Vector2.Zero && steering.TargetVelocity.Length() < 50.0f) From d81ee1a27e53f2ec631d92755fae047dee9fe8d3 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 31 Jul 2018 12:28:04 +0300 Subject: [PATCH 096/198] Added a bunch of checks to make sure a normalized zero vector (= NaN, NaN) is not used in any position/velocity/movement calculations. There were at least three places where it was causing problems according to error reports: when a character that can't enter a sub spawns at the center of a hull and when using an underwater scooter or throwing something while the cursor is at the position of the character, but there were tons of other places as well where it may have potentially caused physics errors. --- .../Source/Characters/AI/EnemyAIController.cs | 8 ++++++-- .../AI/Objectives/AIObjectiveCombat.cs | 1 + .../Source/Characters/AI/SteeringManager.cs | 17 +++++++++-------- .../Source/Characters/Animation/Ragdoll.cs | 7 +++++-- .../Items/Components/Holdable/Propulsion.cs | 2 ++ .../Items/Components/Holdable/Throwable.cs | 5 +++-- .../Items/Components/Machines/Controller.cs | 2 +- .../Items/Components/Machines/Steering.cs | 10 +++++----- .../BarotraumaShared/Source/Map/Explosion.cs | 1 + .../Source/Map/Levels/Level.cs | 2 +- .../Source/Map/SubmarineBody.cs | 18 +++++++++++++----- .../Source/Map/TransitionCinematic.cs | 4 +++- .../Source/Networking/RespawnManager.cs | 8 ++++++-- 13 files changed, 56 insertions(+), 29 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs index 72b956137..ba7a0f353 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs @@ -280,7 +280,9 @@ namespace Barotrauma return; } - SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(SimPosition - selectedAiTarget.SimPosition) * 5); + Vector2 escapeDir = Vector2.Normalize(SimPosition - selectedAiTarget.SimPosition); + if (!MathUtils.IsValid(escapeDir)) escapeDir = Vector2.UnitY; + SteeringManager.SteeringManual(deltaTime, escapeDir * 5); SteeringManager.SteeringWander(1.0f); SteeringManager.SteeringAvoid(deltaTime, 2f); } @@ -490,8 +492,10 @@ namespace Barotrauma if (dist < ConvertUnits.ToSimUnits(500.0f)) { + Vector2 attackDir = Vector2.Normalize(Character.SimPosition - attackPosition); + if (!MathUtils.IsValid(attackDir)) attackDir = Vector2.UnitY; steeringManager.SteeringSeek(attackPosition, -0.8f); - steeringManager.SteeringManual(deltaTime, Vector2.Normalize(Character.SimPosition - attackPosition) * (1.0f - (dist / 500.0f))); + steeringManager.SteeringManual(deltaTime, attackDir * (1.0f - (dist / 500.0f))); } steeringManager.SteeringAvoid(deltaTime, 1.0f); diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs index 84c807617..423a9ec82 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs @@ -63,6 +63,7 @@ namespace Barotrauma character.SetInput(InputType.Aim, false, true); Vector2 enemyDiff = Vector2.Normalize(enemy.Position - character.Position); + if (!MathUtils.IsValid(enemyDiff)) enemyDiff = Rand.Vector(1.0f); float weaponAngle = ((weapon.body.Dir == 1.0f) ? weapon.body.Rotation : weapon.body.Rotation - MathHelper.Pi); Vector2 weaponDir = new Vector2((float)Math.Cos(weaponAngle), (float)Math.Sin(weaponAngle)); diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/SteeringManager.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/SteeringManager.cs index 910c8d8c2..e3d4aac22 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/SteeringManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/SteeringManager.cs @@ -80,9 +80,9 @@ namespace Barotrauma } float steeringSpeed = steering.Length(); - if (steeringSpeed>speed) + if (steeringSpeed > speed) { - steering = Vector2.Normalize(steering) * Math.Abs(speed); + steering = Vector2.Normalize(steering) * Math.Abs(speed); } host.Steering = steering; @@ -97,12 +97,12 @@ namespace Barotrauma targetVel = Vector2.Normalize(targetVel) * speed; Vector2 newSteering = targetVel - host.Steering; - if (newSteering==Vector2.Zero) return Vector2.Zero; + if (newSteering == Vector2.Zero) return Vector2.Zero; float steeringSpeed = (newSteering + host.Steering).Length(); if (steeringSpeed > Math.Abs(speed)) { - newSteering = Vector2.Normalize(newSteering)*Math.Abs(speed); + newSteering = Vector2.Normalize(newSteering) * Math.Abs(speed); } return newSteering; @@ -151,8 +151,7 @@ namespace Barotrauma } else { - Structure closestStructure = closestBody.UserData as Structure; - if (closestStructure != null) + if (closestBody.UserData is Structure closestStructure) { Vector2 obstaclePosition = Submarine.LastPickedPosition; if (closestStructure.IsHorizontal) @@ -166,15 +165,17 @@ namespace Barotrauma avoidSteering = Vector2.Normalize(Submarine.LastPickedPosition - obstaclePosition); } - else if (closestBody.UserData is Item) + else if (closestBody.UserData is Item item) { - Item item = (Item)closestBody.UserData; avoidSteering = Vector2.Normalize(Submarine.LastPickedPosition - item.SimPosition); } else { avoidSteering = Vector2.Normalize(host.SimPosition - Submarine.LastPickedPosition); } + //failed to normalize (the obstacle to avoid is at the same position as the character?) + // -> move to a random direction + if (!MathUtils.IsValid(avoidSteering)) avoidSteering = Rand.Vector(1.0f); } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs index d2f06b11a..fb5ed6df7 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs @@ -766,10 +766,13 @@ namespace Barotrauma //this is preferable to the cost of using continuous collision detection for the character collider if (newHull != null) { + Vector2 hullDiff = WorldPosition - newHull.WorldPosition; + Vector2 moveDir = hullDiff.LengthSquared() < 0.001f ? Vector2.UnitY : Vector2.Normalize(hullDiff); + //find a position 32 units away from the hull Vector2? intersection = MathUtils.GetLineRectangleIntersection( newHull.WorldPosition, - newHull.WorldPosition + Vector2.Normalize(WorldPosition - newHull.WorldPosition) * Math.Max(newHull.Rect.Width, newHull.Rect.Height), + newHull.WorldPosition + moveDir * Math.Max(newHull.Rect.Width, newHull.Rect.Height), new Rectangle(newHull.WorldRect.X - 32, newHull.WorldRect.Y + 32, newHull.WorldRect.Width + 64, newHull.Rect.Height + 64)); if (intersection != null) @@ -1494,7 +1497,7 @@ namespace Barotrauma Vector2 force = Vector2.Zero; foreach (Gap gap in Gap.GapList) { - if (gap.Open <= 0.0f || gap.FlowTargetHull != currentHull || gap.LerpedFlowForce == Vector2.Zero) continue; + if (gap.Open <= 0.0f || gap.FlowTargetHull != currentHull || gap.LerpedFlowForce.LengthSquared() < 0.01f) continue; Vector2 gapPos = gap.SimPosition; float dist = Vector2.Distance(limbPos, gapPos); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Propulsion.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Propulsion.cs index 0d4079573..97a54afc4 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Propulsion.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Propulsion.cs @@ -74,6 +74,8 @@ namespace Barotrauma.Items.Components } Vector2 dir = Vector2.Normalize(character.CursorPosition - character.Position); + //move upwards if the cursor is at the position of the character + if (!MathUtils.IsValid(dir)) dir = Vector2.UnitY; Vector2 propulsion = dir * force; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Throwable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Throwable.cs index 13c66dde4..4369bbf7a 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Throwable.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Throwable.cs @@ -94,8 +94,9 @@ namespace Barotrauma.Items.Components if (throwPos < -0.0) { - Vector2 throwVector = picker.CursorWorldPosition - picker.WorldPosition; - throwVector = Vector2.Normalize(throwVector); + Vector2 throwVector = Vector2.Normalize(picker.CursorWorldPosition - picker.WorldPosition); + //throw upwards if cursor is at the position of the character + if (!MathUtils.IsValid(throwVector)) throwVector = Vector2.UnitY; GameServer.Log(picker.LogName + " threw " + item.Name, ServerLog.MessageType.ItemInteraction); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Controller.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Controller.cs index 9c7dd768c..d7256eaa9 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Controller.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Controller.cs @@ -116,7 +116,7 @@ namespace Barotrauma.Items.Components else { diff.Y = 0.0f; - if (diff != Vector2.Zero && diff.Length() > 10.0f) + if (diff != Vector2.Zero && diff.LengthSquared() > 10.0f * 10.0f) { character.AnimController.TargetMovement = Vector2.Normalize(diff); character.AnimController.TargetDir = diff.X > 0.0f ? Direction.Right : Direction.Left; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Steering.cs index 00326b53b..8f9b0e30b 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Steering.cs @@ -211,8 +211,9 @@ namespace Barotrauma.Items.Components { Vector2 diff = item.Submarine.WorldPosition - (Vector2)intersection; - //far enough -> ignore - if (diff.Length() > avoidRadius) continue; + float dist = diff.Length(); + //far enough or too close to normalize the diff -> ignore + if (dist > avoidRadius || dist < 0.00001f) continue; float dot = item.Submarine.Velocity == Vector2.Zero ? 0.0f : Vector2.Dot(item.Submarine.Velocity, -Vector2.Normalize(diff)); @@ -241,16 +242,15 @@ namespace Barotrauma.Items.Components float otherSize = Math.Max(sub.Borders.Width, sub.Borders.Height); Vector2 diff = item.Submarine.WorldPosition - sub.WorldPosition; - float dist = diff == Vector2.Zero ? 0.0f : diff.Length(); //far enough -> ignore if (dist > thisSize + otherSize) continue; - diff = Vector2.Normalize(diff); + Vector2 dir = dist <= 0.0001f ? Vector2.UnitY : diff / dist; float dot = item.Submarine.Velocity == Vector2.Zero ? - 0.0f : Vector2.Dot(Vector2.Normalize(item.Submarine.Velocity), -Vector2.Normalize(diff)); + 0.0f : Vector2.Dot(Vector2.Normalize(item.Submarine.Velocity), -dir); //heading away -> ignore if (dot < 0.0f) continue; diff --git a/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs b/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs index 121fe5417..3280a3f51 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs @@ -180,6 +180,7 @@ namespace Barotrauma if (limb.WorldPosition != worldPosition && force > 0.0f) { Vector2 limbDiff = Vector2.Normalize(limb.WorldPosition - worldPosition); + if (!MathUtils.IsValid(limbDiff)) limbDiff = Rand.Vector(1.0f); Vector2 impulsePoint = limb.SimPosition - limbDiff * limbRadius; limb.body.ApplyLinearImpulse(limbDiff * distFactor * force, impulsePoint); } diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs index 7d7ce68e1..2304b34cd 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs @@ -919,7 +919,7 @@ namespace Barotrauma ConvertUnits.ToSimUnits(endPos), null, Physics.CollisionLevel) != null) { - position = ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) + Vector2.Normalize(startPos - endPos)*offsetFromWall; + position = ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) + Vector2.Normalize(startPos - endPos) * offsetFromWall; break; } diff --git a/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs b/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs index 94663b760..22d147ad4 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs @@ -214,7 +214,7 @@ namespace Barotrauma closestSub = Character.Controlled.Submarine; } - bool displace = moveAmount.Length() > 100.0f; + bool displace = moveAmount.LengthSquared() > 100.0f * 100.0f; foreach (Submarine sub in subsToMove) { sub.PhysicsBody.SetTransform(sub.PhysicsBody.SimPosition + ConvertUnits.ToSimUnits(moveAmount), 0.0f); @@ -259,7 +259,7 @@ namespace Barotrauma Vector2 totalForce = CalculateBuoyancy(); - if (Body.LinearVelocity.LengthSquared() > 0.000001f) + if (Body.LinearVelocity.LengthSquared() > 0.0001f) { float dragCoefficient = 0.01f; @@ -285,6 +285,7 @@ namespace Barotrauma worldBorders.Location += MathUtils.ToPoint(ConvertUnits.ToDisplayUnits(Body.SimPosition)); Vector2 translateDir = Vector2.Normalize(subTranslation); + if (!MathUtils.IsValid(translateDir)) translateDir = Vector2.UnitY; foreach (Character c in Character.CharacterList) { @@ -384,7 +385,9 @@ namespace Barotrauma if (f2.UserData is VoronoiCell cell) { - HandleLevelCollision(contact, Vector2.Normalize(ConvertUnits.ToDisplayUnits(Body.SimPosition) - cell.Center)); + Vector2 collisionNormal = Vector2.Normalize(ConvertUnits.ToDisplayUnits(Body.SimPosition) - cell.Center); + if (!MathUtils.IsValid(collisionNormal)) collisionNormal = Rand.Vector(1.0f); + HandleLevelCollision(contact, collisionNormal); return true; } @@ -445,7 +448,9 @@ namespace Barotrauma { if (limb.Mass > 100.0f) { - Vector2 normal = Vector2.Normalize(Body.SimPosition - limb.SimPosition); + Vector2 normal = Vector2.DistanceSquared(Body.SimPosition, limb.SimPosition) < 0.0001f ? + Vector2.UnitY : + 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); @@ -634,7 +639,10 @@ namespace Barotrauma float contactDot = Vector2.Dot(otherSub.PhysicsBody.LinearVelocity, -avgContactNormal); if (contactDot > 0.0f) { - otherSub.PhysicsBody.LinearVelocity -= Vector2.Normalize(otherSub.PhysicsBody.LinearVelocity) * contactDot; + if (otherSub.PhysicsBody.LinearVelocity.LengthSquared() > 0.0001f) + { + otherSub.PhysicsBody.LinearVelocity -= Vector2.Normalize(otherSub.PhysicsBody.LinearVelocity) * contactDot; + } impact = Vector2.Dot(otherSub.Velocity, normal); otherSub.SubBody.ApplyImpact(impact, normal, contact); diff --git a/Barotrauma/BarotraumaShared/Source/Map/TransitionCinematic.cs b/Barotrauma/BarotraumaShared/Source/Map/TransitionCinematic.cs index 60a9a8449..528862426 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/TransitionCinematic.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/TransitionCinematic.cs @@ -89,7 +89,9 @@ namespace Barotrauma foreach (Submarine sub in subs) { if (sub.Position == targetPos) continue; - sub.ApplyForce((Vector2.Normalize(targetPos - sub.Position) * targetSpeed - sub.Velocity) * 500.0f); + Vector2 dir = Vector2.Normalize(targetPos - sub.Position); + if (!MathUtils.IsValid(dir)) continue; + sub.ApplyForce((dir * targetSpeed - sub.Velocity) * 500.0f); } timer += CoroutineManager.UnscaledDeltaTime; diff --git a/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs b/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs index 3189a49e8..f5a526544 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs @@ -362,8 +362,12 @@ namespace Barotrauma.Networking while (Math.Abs(position.Y - respawnShuttle.WorldPosition.Y) > 100.0f) { - Vector2 displayVel = Vector2.Normalize(position - respawnShuttle.WorldPosition) * speed; - respawnShuttle.SubBody.Body.LinearVelocity = ConvertUnits.ToSimUnits(displayVel); + Vector2 diff = position - respawnShuttle.WorldPosition; + if (diff.LengthSquared() > 0.01f) + { + Vector2 displayVel = Vector2.Normalize(diff) * speed; + respawnShuttle.SubBody.Body.LinearVelocity = ConvertUnits.ToSimUnits(displayVel); + } yield return CoroutineStatus.Running; if (respawnShuttle.SubBody == null) yield return CoroutineStatus.Success; From 82103cf39449d6a5132fa37b98b9a9e0947cf22f Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 31 Jul 2018 15:03:20 +0300 Subject: [PATCH 097/198] Controllers don't have to be wired directly to an item for camera focus to work (= railguns and cameras can be used over wifi). Closes #535 --- .../Items/Components/Machines/Controller.cs | 4 +-- .../Items/Components/Machines/Controller.cs | 30 +++++++++---------- .../Items/Components/Signal/Connection.cs | 5 ++++ .../Items/Components/Signal/WifiComponent.cs | 10 +++++++ .../BarotraumaShared/Source/Items/Item.cs | 14 +++++++-- 5 files changed, 42 insertions(+), 21 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Controller.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Controller.cs index d9e7aa18a..d7847cd6d 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Controller.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Controller.cs @@ -6,9 +6,7 @@ namespace Barotrauma.Items.Components { public override void DrawHUD(SpriteBatch spriteBatch, Character character) { - var focusTarget = GetFocusTarget(); - if (focusTarget == null) return; - if (character.ViewTarget == focusTarget) + if (focusTarget != null && character.ViewTarget == focusTarget) { foreach (ItemComponent ic in focusTarget.components) { diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Controller.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Controller.cs index d7256eaa9..ede3f0aed 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Controller.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Controller.cs @@ -33,6 +33,9 @@ namespace Barotrauma.Items.Components private Character character; + private Item focusTarget; + private float targetRotation; + public Vector2 UserPos { get { return userPos; } @@ -174,7 +177,7 @@ namespace Barotrauma.Items.Components return true; } - + public override bool SecondaryUse(float deltaTime, Character character = null) { if (this.character != character) @@ -190,7 +193,7 @@ namespace Barotrauma.Items.Components } if (character == null) return false; - Entity focusTarget = GetFocusTarget(); + focusTarget = GetFocusTarget(); if (focusTarget == null) { Vector2 centerPos = new Vector2(item.WorldRect.Center.X, item.WorldRect.Center.Y); @@ -198,9 +201,7 @@ namespace Barotrauma.Items.Components Vector2 offset = character.CursorWorldPosition - centerPos; offset.Y = -offset.Y; - float targetRotation = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(offset)); - - item.SendSignal(0, targetRotation.ToString(), "position_out", character); + targetRotation = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(offset)); return false; } @@ -211,7 +212,7 @@ namespace Barotrauma.Items.Components Lights.LightManager.ViewTarget = focusTarget; cam.TargetPos = focusTarget.WorldPosition; - cam.OffsetAmount = MathHelper.Lerp(cam.OffsetAmount, (focusTarget as Item).Prefab.OffsetOnSelected, deltaTime*10.0f); + cam.OffsetAmount = MathHelper.Lerp(cam.OffsetAmount, (focusTarget as Item).Prefab.OffsetOnSelected, deltaTime * 10.0f); } #endif @@ -232,9 +233,7 @@ namespace Barotrauma.Items.Components Vector2 offset = character.CursorWorldPosition - centerPos; offset.Y = -offset.Y; - float targetRotation = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(offset)); - - item.SendSignal(0, targetRotation.ToString(), "position_out", character); + targetRotation = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(offset)); } return true; @@ -242,17 +241,16 @@ namespace Barotrauma.Items.Components private Item GetFocusTarget() { - foreach (Connection c in item.Connections) - { - if (c.Name != "position_out") continue; + item.SendSignal(0, targetRotation.ToString(), "position_out", character); - foreach (Connection c2 in c.Recipients) + for (int i = item.LastSentSignalRecipients.Count - 1; i >= 0; i--) + { + if (item.LastSentSignalRecipients[i].Prefab.FocusOnSelected) { - if (c2 == null || c2.Item == null || !c2.Item.Prefab.FocusOnSelected) continue; - return c2.Item; + return item.LastSentSignalRecipients[i]; } } - + return null; } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Connection.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Connection.cs index eea444c2b..5cd768fc9 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Connection.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Connection.cs @@ -160,6 +160,11 @@ namespace Barotrauma.Items.Components if (recipient == null) continue; if (recipient.item == this.item || recipient.item == source) continue; + if (source != null && !source.LastSentSignalRecipients.Contains(recipient.item)) + { + source.LastSentSignalRecipients.Add(recipient.item); + } + foreach (ItemComponent ic in recipient.item.components) { ic.ReceiveSignal(stepsTaken, signal, recipient, item, sender, power); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/WifiComponent.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/WifiComponent.cs index e3ee5ff30..92be4b891 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/WifiComponent.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/WifiComponent.cs @@ -95,6 +95,16 @@ namespace Barotrauma.Items.Components foreach (WifiComponent wifiComp in receivers) { wifiComp.item.SendSignal(stepsTaken, signal, "signal_out", sender); + if (source != null) + { + foreach (Item receiverItem in wifiComp.item.LastSentSignalRecipients) + { + if (!source.LastSentSignalRecipients.Contains(receiverItem)) + { + source.LastSentSignalRecipients.Add(receiverItem); + } + } + } } break; } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index 9625a01f7..0d2fe6908 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -268,7 +268,16 @@ namespace Barotrauma return IsInWater(); } } - + + /// + /// A list of items the last signal sent by this item went through + /// + public List LastSentSignalRecipients + { + get; + private set; + } = new List(); + public ItemPrefab Prefab { get { return prefab; } @@ -1011,9 +1020,10 @@ namespace Barotrauma } } } - + public void SendSignal(int stepsTaken, string signal, string connectionName, Character sender, float power = 0.0f) { + LastSentSignalRecipients.Clear(); if (connections == null) return; stepsTaken++; From d8559ad63cb3758a54bb5c66f76aa00ca15af5a8 Mon Sep 17 00:00:00 2001 From: ursinewalrus Date: Tue, 31 Jul 2018 18:50:38 -0500 Subject: [PATCH 098/198] attaches item to wall where hand is, not at center of body --- .../Source/Items/Components/Holdable/Holdable.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs index f37c5e456..726c94ebc 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs @@ -160,7 +160,18 @@ namespace Barotrauma.Items.Components if (item.body != null) { item.body.ResetDynamics(); - item.SetTransform(picker.SimPosition, 0.0f); + Limb heldHand; + if (picker.Inventory.IsInLimbSlot(item, InvSlotType.LeftHand)) + { + heldHand = picker.AnimController.GetLimb(LimbType.LeftHand); + + } + else + { + heldHand = picker.AnimController.GetLimb(LimbType.RightHand); + } + + item.SetTransform(heldHand.SimPosition, 0.0f); } picker.DeselectItem(item); From bec2cd53232d595c18eefd9e9c9d11ceb94aac08 Mon Sep 17 00:00:00 2001 From: ursinewalrus Date: Tue, 31 Jul 2018 19:46:55 -0500 Subject: [PATCH 099/198] more accurate placement --- .../Source/Items/Components/Holdable/Holdable.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs index 726c94ebc..3159deed5 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs @@ -161,17 +161,24 @@ namespace Barotrauma.Items.Components { item.body.ResetDynamics(); Limb heldHand; + Limb arm; if (picker.Inventory.IsInLimbSlot(item, InvSlotType.LeftHand)) { heldHand = picker.AnimController.GetLimb(LimbType.LeftHand); + arm = picker.AnimController.GetLimb(LimbType.LeftArm); } else { heldHand = picker.AnimController.GetLimb(LimbType.RightHand); + arm = picker.AnimController.GetLimb(LimbType.RightArm); } + float xDif = (heldHand.SimPosition.X - arm.SimPosition.X) / 2f; + float yDif = (heldHand.SimPosition.Y - arm.SimPosition.Y) / 2.5f; - item.SetTransform(heldHand.SimPosition, 0.0f); + + //DebugConsole.NewMessage("hand rot at " + heldHand.SimPosition + " and simpos at " + arm, Color.Green); + item.SetTransform(heldHand.SimPosition + new Vector2(xDif,yDif), 0.0f); } picker.DeselectItem(item); From 5a6a8e25015fd6dd4a1207c730042504343f1fc4 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 1 Aug 2018 13:52:47 +0300 Subject: [PATCH 100/198] Wifi components can't communicate with the enemy sub in combat missions. Fixes #554 --- .../Source/Characters/Jobs/Job.cs | 8 ++++++- .../Source/Events/Missions/CombatMission.cs | 24 ++++++++++++++++--- .../Items/Components/Signal/WifiComponent.cs | 4 +++- .../BarotraumaShared/Source/Items/Item.cs | 24 +++++++++++++++---- .../Source/Networking/GameServer.cs | 4 ++-- 5 files changed, 53 insertions(+), 11 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Jobs/Job.cs b/Barotrauma/BarotraumaShared/Source/Characters/Jobs/Job.cs index 04271943d..f1877e491 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Jobs/Job.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Jobs/Job.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using Barotrauma.Items.Components; +using System.Collections.Generic; using System.Linq; using System.Xml.Linq; @@ -135,6 +136,11 @@ namespace Barotrauma if (!string.IsNullOrWhiteSpace(spawnPoint.IdCardDesc)) item.Description = spawnPoint.IdCardDesc; } + + foreach (WifiComponent wifiComponent in item.GetComponents()) + { + wifiComponent.TeamID = character.TeamID; + } if (parentItem != null) parentItem.Combine(item); diff --git a/Barotrauma/BarotraumaShared/Source/Events/Missions/CombatMission.cs b/Barotrauma/BarotraumaShared/Source/Events/Missions/CombatMission.cs index 55133d72a..290007dba 100644 --- a/Barotrauma/BarotraumaShared/Source/Events/Missions/CombatMission.cs +++ b/Barotrauma/BarotraumaShared/Source/Events/Missions/CombatMission.cs @@ -1,4 +1,5 @@ -using Barotrauma.Networking; +using Barotrauma.Items.Components; +using Barotrauma.Networking; using Microsoft.Xna.Framework; using System.Collections.Generic; using System.Linq; @@ -72,7 +73,7 @@ namespace Barotrauma descriptions[i] = descriptions[i].Replace("[location" + (n + 1) + "]", locations[n].Name); } } - + teamNames = new string[] { prefab.ConfigElement.GetAttributeString("teamname1", "Team A"), @@ -145,6 +146,23 @@ namespace Barotrauma subs[1].SetPosition(Level.Loaded.EndPosition - new Vector2(0.0f, 2000.0f)); subs[1].FlipX(); + //prevent wifi components from communicating between subs + List wifiComponents = new List(); + foreach (Item item in Item.ItemList) + { + wifiComponents.AddRange(item.GetComponents()); + } + foreach (WifiComponent wifiComponent in wifiComponents) + { + for (int i = 0; i < 2; i++) + { + if (wifiComponent.Item.Submarine == subs[i] || subs[i].DockedTo.Contains(wifiComponent.Item.Submarine)) + { + wifiComponent.TeamID = subs[i].TeamID; + } + } + } + crews = new List[] { new List(), new List() }; foreach (Submarine submarine in Submarine.Loaded) @@ -208,7 +226,7 @@ namespace Barotrauma } else { - if (winner>=0 && subs[winner] != null && + if (winner >= 0 && subs[winner] != null && (winner == 0 && subs[winner].AtStartPosition) || (winner == 1 && subs[winner].AtEndPosition) && crews[winner].Any(c => !c.IsDead && c.Submarine == subs[winner])) { diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/WifiComponent.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/WifiComponent.cs index 92be4b891..de496e307 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/WifiComponent.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/WifiComponent.cs @@ -14,6 +14,8 @@ namespace Barotrauma.Items.Components private int channel; + public byte TeamID; + [Serialize(20000.0f, false)] public float Range { @@ -59,7 +61,7 @@ namespace Barotrauma.Items.Components { if (!HasRequiredContainedItems(false)) return false; - if (sender == null || sender.channel != channel) return false; + if (sender == null || sender.channel != channel || sender.TeamID != TeamID) return false; return Vector2.Distance(item.WorldPosition, sender.item.WorldPosition) <= sender.Range; } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index 0d2fe6908..3eea92d2e 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -1647,7 +1647,15 @@ namespace Barotrauma int index = ParentInventory.FindIndex(this); msg.Write(index < 0 ? (byte)255 : (byte)index); } - + + byte teamID = 0; + foreach (WifiComponent wifiComponent in GetComponents()) + { + teamID = wifiComponent.TeamID; + break; + } + + msg.Write(teamID); bool tagsChanged = tags.Count != prefab.Tags.Count || !tags.All(t => prefab.Tags.Contains(t)); msg.Write(tagsChanged); if (tagsChanged) @@ -1692,6 +1700,7 @@ namespace Barotrauma } } + byte teamID = msg.ReadByte(); bool tagsChanged = msg.ReadBoolean(); string tags = ""; if (tagsChanged) @@ -1725,15 +1734,22 @@ namespace Barotrauma } } - var item = new Item(itemPrefab, pos, sub); - item.ID = itemId; + var item = new Item(itemPrefab, pos, sub) + { + ID = itemId + }; + + foreach (WifiComponent wifiComponent in item.GetComponents()) + { + wifiComponent.TeamID = teamID; + } if (descriptionChanged) item.Description = itemDesc; if (tagsChanged) item.Tags = tags; if (sub != null) { item.CurrentHull = Hull.FindHull(pos + sub.Position, null, true); - item.Submarine = item.CurrentHull == null ? null : item.CurrentHull.Submarine; + item.Submarine = item.CurrentHull?.Submarine; } if (inventory != null) diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs index cd34f1c6f..2e948319a 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs @@ -1309,8 +1309,8 @@ namespace Barotrauma.Networking { Character spawnedCharacter = Character.Create(teamClients[i].CharacterInfo, assignedWayPoints[i].WorldPosition, true, false); spawnedCharacter.AnimController.Frozen = true; - spawnedCharacter.GiveJobItems(assignedWayPoints[i]); spawnedCharacter.TeamID = (byte)teamID; + spawnedCharacter.GiveJobItems(assignedWayPoints[i]); teamClients[i].Character = spawnedCharacter; @@ -1323,8 +1323,8 @@ namespace Barotrauma.Networking if (characterInfo != null && hostTeam == teamID) { myCharacter = Character.Create(characterInfo, assignedWayPoints[assignedWayPoints.Length - 1].WorldPosition, false, false); - myCharacter.GiveJobItems(assignedWayPoints.Last()); myCharacter.TeamID = (byte)teamID; + myCharacter.GiveJobItems(assignedWayPoints.Last()); Character.Controlled = myCharacter; From 027cebafddffeb0a0ff0d9efca8de9d7cd1a4cda Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 1 Aug 2018 14:09:22 +0300 Subject: [PATCH 101/198] Fixed the previously selected location staying selected but start button staying disabled when returning to the lobby screen in SP campaign. Made it impossible to progress without restarting if there were no other selectable locations. Closes #553 --- Barotrauma/BarotraumaClient/Source/Screens/LobbyScreen.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaClient/Source/Screens/LobbyScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/LobbyScreen.cs index a8df21efa..07c31eee7 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/LobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/LobbyScreen.cs @@ -82,10 +82,12 @@ namespace Barotrauma locationTitle.Text = TextManager.Get("Location") + ": " + campaign.Map.CurrentLocation.Name; + campaign.Map.SelectLocation(-1); + bottomPanel.ClearChildren(); campaignUI = new CampaignUI(campaign, bottomPanel); campaignUI.StartRound = StartRound; - campaignUI.OnLocationSelected = SelectLocation; + campaignUI.OnLocationSelected = SelectLocation; campaignUI.UpdateCharacterLists(); GameAnalyticsManager.SetCustomDimension01("singleplayer"); From 5ba3f9384a311e0f362aa155ffb4ab1aeddccb7a Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 1 Aug 2018 17:30:07 +0300 Subject: [PATCH 102/198] Debug visualization of docking port colliders --- .../Source/Items/DockingPort.cs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/Barotrauma/BarotraumaClient/Source/Items/DockingPort.cs b/Barotrauma/BarotraumaClient/Source/Items/DockingPort.cs index f7de0dab8..fee47afdb 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/DockingPort.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/DockingPort.cs @@ -1,4 +1,7 @@ using Barotrauma.Networking; +using FarseerPhysics; +using FarseerPhysics.Collision; +using FarseerPhysics.Dynamics; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; @@ -58,6 +61,41 @@ namespace Barotrauma.Items.Components rect.Width, (int)(rect.Height / 2 * dockingState)), Color.White); } } + + if (bodies != null) + { + for (int i = 0; i < bodies.Length; i++) + { + var body = bodies[i]; + if (body == null) continue; + + body.FixtureList[0].GetAABB(out AABB aabb, 0); + + Vector2 bodyDrawPos = ConvertUnits.ToDisplayUnits(new Vector2(aabb.LowerBound.X, aabb.UpperBound.Y)); + if ((i == 1 || i == 3) && dockingTarget?.item?.Submarine != null) bodyDrawPos += dockingTarget.item.Submarine.Position; + if ((i == 0 || i == 2) && item.Submarine != null) bodyDrawPos += item.Submarine.Position; + bodyDrawPos.Y = -bodyDrawPos.Y; + + GUI.DrawRectangle(spriteBatch, + bodyDrawPos, + ConvertUnits.ToDisplayUnits(aabb.Extents * 2), + Color.Gray, false, 0.0f, 4); + } + } + + if (doorBody != null && doorBody.Enabled) + { + doorBody.FixtureList[0].GetAABB(out AABB aabb, 0); + + Vector2 bodyDrawPos = ConvertUnits.ToDisplayUnits(new Vector2(aabb.LowerBound.X, aabb.UpperBound.Y)); + if (item?.Submarine != null) bodyDrawPos += item.Submarine.Position; + bodyDrawPos.Y = -bodyDrawPos.Y; + + GUI.DrawRectangle(spriteBatch, + bodyDrawPos, + ConvertUnits.ToDisplayUnits(aabb.Extents * 2), + Color.Gray, false, 0, 8); + } } } From 8c8d3e300f3e84ff8c549dba23b282ec9f1213d9 Mon Sep 17 00:00:00 2001 From: Josh Kerxhalli-Kleinfield Date: Wed, 1 Aug 2018 19:07:35 -0500 Subject: [PATCH 103/198] Comment update --- .../Source/Items/Components/Holdable/Holdable.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs index 3159deed5..5aa102b36 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs @@ -173,11 +173,10 @@ namespace Barotrauma.Items.Components heldHand = picker.AnimController.GetLimb(LimbType.RightHand); arm = picker.AnimController.GetLimb(LimbType.RightArm); } + float xDif = (heldHand.SimPosition.X - arm.SimPosition.X) / 2f; float yDif = (heldHand.SimPosition.Y - arm.SimPosition.Y) / 2.5f; - - - //DebugConsole.NewMessage("hand rot at " + heldHand.SimPosition + " and simpos at " + arm, Color.Green); + //hand simPosition is actually in the wrist so need to move the item out from it slightly item.SetTransform(heldHand.SimPosition + new Vector2(xDif,yDif), 0.0f); } From 274a29bada0b1dddeeb292fb37a2b675d007e60e Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 29 Jun 2018 17:39:10 +0300 Subject: [PATCH 104/198] Fixed fabricated items always appearing to be in full condition client-side (e.g. oxygen tanks which should be empty after being fabricated). Cherry-picked from 64896b0f. Closes #561 --- .../Source/Items/Components/Machines/Fabricator.cs | 1 - Barotrauma/BarotraumaShared/Source/Items/Item.cs | 13 +++++-------- .../Source/Networking/EntitySpawner.cs | 13 +++++++------ 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs index 4c553b0ef..ca08ce693 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs @@ -291,7 +291,6 @@ namespace Barotrauma.Items.Components } } - //TODO: apply OutCondition if (containers[1].Inventory.Items.All(i => i != null)) { Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, item.Position, item.Submarine, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index 3eea92d2e..1ff163899 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -344,18 +344,18 @@ namespace Barotrauma } } - public Item(ItemPrefab itemPrefab, Vector2 position, Submarine submarine, float? spawnCondition = null) + public Item(ItemPrefab itemPrefab, Vector2 position, Submarine submarine) : this(new Rectangle( (int)(position.X - itemPrefab.sprite.size.X / 2), (int)(position.Y + itemPrefab.sprite.size.Y / 2), (int)itemPrefab.sprite.size.X, (int)itemPrefab.sprite.size.Y), - itemPrefab, submarine, spawnCondition) + itemPrefab, submarine) { } - public Item(Rectangle newRect, ItemPrefab itemPrefab, Submarine submarine, float? spawnCondition = null) + public Item(Rectangle newRect, ItemPrefab itemPrefab, Submarine submarine) : base(itemPrefab, submarine) { prefab = itemPrefab; @@ -370,7 +370,7 @@ namespace Barotrauma rect = newRect; - condition = (float)(spawnCondition ?? prefab.Health); + condition = prefab.Health; lastSentCondition = condition; XElement element = prefab.ConfigElement; @@ -425,11 +425,8 @@ namespace Barotrauma //and add them to the corresponding statuseffect list foreach (List componentEffectList in ic.statusEffectLists.Values) { - ActionType actionType = componentEffectList.First().type; - - List statusEffectList; - if (!statusEffectLists.TryGetValue(actionType, out statusEffectList)) + if (!statusEffectLists.TryGetValue(actionType, out List statusEffectList)) { statusEffectList = new List(); statusEffectLists.Add(actionType, statusEffectList); diff --git a/Barotrauma/BarotraumaShared/Source/Networking/EntitySpawner.cs b/Barotrauma/BarotraumaShared/Source/Networking/EntitySpawner.cs index 961abccf2..14bd2ad22 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/EntitySpawner.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/EntitySpawner.cs @@ -22,13 +22,13 @@ namespace Barotrauma public readonly Vector2 Position; public readonly Inventory Inventory; public readonly Submarine Submarine; - public readonly float Condition; + public readonly float Condition; public ItemSpawnInfo(ItemPrefab prefab, Vector2 worldPosition, float? condition = null) { Prefab = prefab; Position = worldPosition; - Condition = (float)(condition ?? prefab.Health); + Condition = condition ?? prefab.Health; } public ItemSpawnInfo(ItemPrefab prefab, Vector2 position, Submarine sub, float? condition = null) @@ -36,14 +36,14 @@ namespace Barotrauma Prefab = prefab; Position = position; Submarine = sub; - Condition = (float)(condition ?? prefab.Health); + Condition = condition ?? prefab.Health; } public ItemSpawnInfo(ItemPrefab prefab, Inventory inventory, float? condition = null) { Prefab = prefab; Inventory = inventory; - Condition = (float)(condition ?? prefab.Health); + Condition = condition ?? prefab.Health; } public Entity Spawn() @@ -52,13 +52,14 @@ namespace Barotrauma if (Inventory != null) { - spawnedItem = new Item(Prefab, Vector2.Zero, null, Condition); + spawnedItem = new Item(Prefab, Vector2.Zero, null); Inventory.TryPutItem(spawnedItem, null, spawnedItem.AllowedSlots); } else { - spawnedItem = new Item(Prefab, Position, Submarine, Condition); + spawnedItem = new Item(Prefab, Position, Submarine); } + spawnedItem.Condition = Condition; return spawnedItem; } From 1379460ad9db3cf03e1088a75f082753bd58ea1c Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 2 Aug 2018 11:22:49 +0300 Subject: [PATCH 105/198] Fixed holdable components reverting their RequiredItems back to the prefab values during loading. Closes #557 --- .../Source/Items/Components/Holdable/Holdable.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs index 5aa102b36..f0a7b89d2 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs @@ -124,6 +124,17 @@ namespace Barotrauma.Items.Components } } + public override void Load(XElement componentElement) + { + base.Load(componentElement); + if (attachable) + { + prevMsg = Msg; + prevPickKey = PickKey; + prevRequiredItems = new List(requiredItems); + } + } + public override void Drop(Character dropper) { Drop(true, dropper); From 9ab5d76ed121f1364a368dbc42accfd1b45d4fc5 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 2 Aug 2018 12:18:03 +0300 Subject: [PATCH 106/198] Fixed attachable items dropping on the ground when deattaching them (but still staying in the inventory of the character detaching them). Closes #559 --- .../Items/Components/Holdable/Holdable.cs | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs index f0a7b89d2..5a8e33867 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs @@ -438,7 +438,7 @@ namespace Barotrauma.Items.Components public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) { - bool isAttached = msg.ReadBoolean(); + bool shouldBeAttached = msg.ReadBoolean(); Vector2 simPosition = new Vector2(msg.ReadFloat(), msg.ReadFloat()); if (!attachable) @@ -447,7 +447,7 @@ namespace Barotrauma.Items.Components return; } - if (isAttached) + if (shouldBeAttached) { Drop(false, null); item.SetTransform(simPosition, 0.0f); @@ -455,16 +455,19 @@ namespace Barotrauma.Items.Components } else { - DropConnectedWires(null); - - if (body != null) + if (attached) { - item.body = body; - item.body.Enabled = true; - } - IsActive = false; + DropConnectedWires(null); - DeattachFromWall(); + if (body != null) + { + item.body = body; + item.body.Enabled = true; + } + IsActive = false; + + DeattachFromWall(); + } } } } From 708f72c8832fa7e789c89adede899bb2de111f86 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 2 Aug 2018 13:24:56 +0300 Subject: [PATCH 107/198] Fixed items occasionally dropping instead of being moved to another inventory client-side. Closes #558 --- .../Source/Items/Inventory.cs | 21 ++++++++++++------- .../Source/Items/Inventory.cs | 20 ++++++++++-------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs b/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs index 06a11e43c..c4a963467 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs @@ -487,13 +487,18 @@ namespace Barotrauma } else { + if (syncItemsCoroutine != null) + { + CoroutineManager.StopCoroutines(syncItemsCoroutine); + syncItemsCoroutine = null; + } ApplyReceivedState(); } } private IEnumerable SyncItemsAfterDelay() { - while (syncItemsDelay > 0.0f && GameMain.Client != null && GameMain.Client.MidRoundSyncing) + while (syncItemsDelay > 0.0f || (GameMain.Client != null && GameMain.Client.MidRoundSyncing)) { syncItemsDelay -= CoroutineManager.DeltaTime; yield return CoroutineStatus.Running; @@ -511,19 +516,21 @@ namespace Barotrauma private void ApplyReceivedState() { - if (receivedItemIDs == null) return; - for (int i = 0; i < capacity; i++) { - if (receivedItemIDs[i] == 0) + if (receivedItemIDs[i] == 0 || (Entity.FindEntityByID(receivedItemIDs[i]) as Item != Items[i])) { if (Items[i] != null) Items[i].Drop(); + System.Diagnostics.Debug.Assert(Items[i] == null); } - else + } + + for (int i = 0; i < capacity; i++) + { + if (receivedItemIDs[i] > 0) { var item = Entity.FindEntityByID(receivedItemIDs[i]) as Item; - if (item == null) continue; - + if (item == null || item == Items[i]) continue; TryPutItem(item, i, true, true, null, false); } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs b/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs index 0c76077c3..96333d162 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs @@ -202,20 +202,22 @@ namespace Barotrauma { newItemIDs[i] = msg.ReadUInt16(); } - - if (c == null || c.Character == null || !c.Character.CanAccessInventory(this)) - { - return; - } + + if (c == null || c.Character == null || !c.Character.CanAccessInventory(this)) return; for (int i = 0; i < capacity; i++) { - if (newItemIDs[i] == 0) + if (newItemIDs[i] == 0 || (Entity.FindEntityByID(newItemIDs[i]) as Item != Items[i])) { - if (Items[i] != null) Items[i].Drop(c.Character); - System.Diagnostics.Debug.Assert(Items[i]==null); + if (Items[i] != null) Items[i].Drop(); + System.Diagnostics.Debug.Assert(Items[i] == null); } - else + } + + + for (int i = 0; i < capacity; i++) + { + if (newItemIDs[i] > 0) { var item = Entity.FindEntityByID(newItemIDs[i]) as Item; if (item == null || item == Items[i]) continue; From 9099b191d070bd1f410d0faa2925d6c511fee05d Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 2 Aug 2018 16:48:51 +0300 Subject: [PATCH 108/198] Fix (or a workaround) to characters teleporting inside/through colliders when leaving a sub (see #552). Gaps do a raycast out from the sub to see if there are obstacles outside, and if so, ragdolls place a collider at the corresponding position inside the sub. The collider is simply a straight axis-aligned line atm, so it doesn't work accurately with sloped ice/submarine walls, but does prevent the ragdoll from ending up inside the obstacles. TODO: use a few edges to approximate the shape of the obstacles more closely? --- .../Source/Characters/Animation/Ragdoll.cs | 11 ++- .../Source/Characters/Animation/Ragdoll.cs | 81 ++++++++++++++++--- Barotrauma/BarotraumaShared/Source/Map/Gap.cs | 57 ++++++++++++- .../BarotraumaShared/Source/Map/Submarine.cs | 12 +++ 4 files changed, 146 insertions(+), 15 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs index 6b9d32c9e..f5d90be8b 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs @@ -100,7 +100,6 @@ namespace Barotrauma foreach (Limb limb in Limbs) { - if (limb.pullJoint != null) { Vector2 pos = ConvertUnits.ToDisplayUnits(limb.pullJoint.WorldAnchorA); @@ -137,6 +136,16 @@ namespace Barotrauma } } + if (outsideCollisionBlocker.Enabled && currentHull.Submarine != null) + { + var edgeShape = outsideCollisionBlocker.FixtureList[0].Shape as FarseerPhysics.Collision.Shapes.EdgeShape; + Vector2 startPos = ConvertUnits.ToDisplayUnits(outsideCollisionBlocker.GetWorldPoint(edgeShape.Vertex1)) + currentHull.Submarine.Position; + Vector2 endPos = ConvertUnits.ToDisplayUnits(outsideCollisionBlocker.GetWorldPoint(edgeShape.Vertex2)) + currentHull.Submarine.Position; + startPos.Y = -startPos.Y; + endPos.Y = -endPos.Y; + GUI.DrawLine(spriteBatch, startPos, endPos, Color.Gray, 0, 5); + } + if (character.MemState.Count > 1) { Vector2 prevPos = ConvertUnits.ToDisplayUnits(character.MemState[0].Position); diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs index fb5ed6df7..ccd2c9b16 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs @@ -3,6 +3,7 @@ using FarseerPhysics; using FarseerPhysics.Dynamics; using FarseerPhysics.Dynamics.Contacts; using FarseerPhysics.Dynamics.Joints; +using FarseerPhysics.Factories; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; @@ -96,6 +97,8 @@ namespace Barotrauma private Category prevCollisionCategory = Category.None; + private Body outsideCollisionBlocker; + public PhysicsBody Collider { get @@ -356,15 +359,23 @@ namespace Barotrauma if (collider[0] == null) { DebugConsole.ThrowError("No collider configured for \"" + character.Name + "\"!"); - collider[0] = new PhysicsBody(0.0f, 0.0f, 0.5f, 5.0f); - collider[0].UserData = character; - collider[0].BodyType = BodyType.Dynamic; - collider[0].CollisionCategories = Physics.CollisionCharacter; + collider[0] = new PhysicsBody(0.0f, 0.0f, 0.5f, 5.0f) + { + UserData = character, + BodyType = BodyType.Dynamic, + CollisionCategories = Physics.CollisionCharacter + }; collider[0].FarseerBody.AngularDamping = 5.0f; collider[0].FarseerBody.FixedRotation = true; collider[0].FarseerBody.OnCollision += OnLimbCollision; } + outsideCollisionBlocker = BodyFactory.CreateEdge(GameMain.World, -Vector2.UnitX * 2.0f, Vector2.UnitX * 2.0f, "blocker"); + outsideCollisionBlocker.BodyType = BodyType.Static; + outsideCollisionBlocker.CollisionCategories = Physics.CollisionWall; + outsideCollisionBlocker.CollidesWith = Physics.CollisionCharacter; + outsideCollisionBlocker.Enabled = false; + UpdateCollisionCategories(); foreach (var joint in LimbJoints) @@ -492,7 +503,10 @@ namespace Barotrauma Structure structure = f2.Body.UserData as Structure; if (f2.Body.UserData is Submarine && character.Submarine == (Submarine)f2.Body.UserData) return false; - + + //only collide with the ragdoll's own blocker + if (f2.Body.UserData as string == "blocker" && f2.Body != outsideCollisionBlocker) return false; + //always collides with bodies other than structures if (structure == null) { @@ -789,15 +803,7 @@ namespace Barotrauma //in -> out if (newHull == null && currentHull.Submarine != null) { - for (int i = -1; i < 2; i += 2) - { - //don't teleport outside the sub if right next to a hull - if (Hull.FindHull(findPos + new Vector2(Submarine.GridSize.X * 4.0f * i, 0.0f), currentHull) != null) return; - if (Hull.FindHull(findPos + new Vector2(0.0f, Submarine.GridSize.Y * 4.0f * i), currentHull) != null) return; - } - if (Gap.FindAdjacent(currentHull.ConnectedGaps, findPos, 150.0f) != null) return; - Teleport(ConvertUnits.ToSimUnits(currentHull.Submarine.Position), currentHull.Submarine.Velocity); } //out -> in @@ -816,6 +822,48 @@ namespace Barotrauma CurrentHull = newHull; character.Submarine = currentHull?.Submarine; } + + private void PreventOutsideCollision() + { + if (currentHull?.Submarine == null) + { + outsideCollisionBlocker.Enabled = false; + return; + } + + var connectedGaps = currentHull.ConnectedGaps.Where(g => !g.IsRoomToRoom); + foreach (Gap gap in connectedGaps) + { + if (gap.IsHorizontal) + { + if (character.Position.Y > gap.Rect.Y || character.Position.Y < gap.Rect.Y - gap.Rect.Height) continue; + if (Math.Sign(gap.Rect.Center.X - currentHull.Rect.Center.X) != + Math.Sign(character.Position.X - currentHull.Rect.Center.X)) + { + continue; + } + } + else + { + if (character.Position.X < gap.Rect.X || character.Position.X > gap.Rect.Right) continue; + if (Math.Sign((gap.Rect.Y - gap.Rect.Height / 2) - (currentHull.Rect.Center.X - currentHull.Rect.Height / 2)) != + Math.Sign(character.Position.X - (currentHull.Rect.Center.X - currentHull.Rect.Height / 2))) + { + continue; + } + } + + if (!gap.GetOutsideCollider(out Vector2? outsideColliderPos, out Vector2? outsideColliderNormal)) continue; + + outsideCollisionBlocker.SetTransform( + outsideColliderPos.Value - currentHull.Submarine.SimPosition, + MathUtils.VectorToAngle(outsideColliderNormal.Value) - MathHelper.PiOver2); + outsideCollisionBlocker.Enabled = true; + return; + } + + outsideCollisionBlocker.Enabled = false; + } public void Teleport(Vector2 moveAmount, Vector2 velocityChange) { @@ -888,6 +936,7 @@ namespace Barotrauma Vector2 flowForce = Vector2.Zero; FindHull(); + PreventOutsideCollision(); splashSoundTimer -= deltaTime; @@ -1568,6 +1617,12 @@ namespace Barotrauma b.Remove(); } + if (outsideCollisionBlocker != null) + { + GameMain.World.RemoveBody(outsideCollisionBlocker); + outsideCollisionBlocker = null; + } + if (LimbJoints != null) { foreach (RevoluteJoint joint in LimbJoints) diff --git a/Barotrauma/BarotraumaShared/Source/Map/Gap.cs b/Barotrauma/BarotraumaShared/Source/Map/Gap.cs index cea75437a..dc55ee578 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Gap.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Gap.cs @@ -1,4 +1,5 @@ using Barotrauma.Items.Components; +using FarseerPhysics; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; @@ -13,6 +14,8 @@ namespace Barotrauma public static bool ShowGaps = true; + const float OutsideColliderRaycastInterval = 0.1f; + public readonly bool IsHorizontal; //a value between 0.0f-1.0f (0.0 = closed, 1.0f = open) @@ -26,12 +29,18 @@ namespace Barotrauma private float lowerSurface; private Vector2 lerpedFlowForce; - + //if set to true, hull connections of this gap won't be updated when changes are being done to hulls public bool DisableHullRechecks; //can ambient light get through the gap even if it's not open public bool PassAmbientLight; + + //position of a collider outside the gap (for example an ice wall next to the sub) + //used by ragdolls to prevent them from ending up inside colliders when teleporting out of the sub + private Vector2? outsideColliderPos; + private Vector2? outsideColliderNormal; + private float outsideColliderRaycastTimer; public float Open { @@ -191,6 +200,8 @@ namespace Barotrauma { flowForce = Vector2.Zero; + outsideColliderRaycastTimer -= deltaTime; + if (open == 0.0f || linkedTo.Count == 0) { lerpedFlowForce = Vector2.Zero; @@ -505,7 +516,51 @@ namespace Barotrauma hull1.LethalPressure += (Submarine != null && Submarine.AtDamageDepth) ? 100.0f * deltaTime : 10.0f * deltaTime; } } + } + public bool GetOutsideCollider(out Vector2? simPosition, out Vector2? normal) + { + simPosition = null; + normal = null; + + if (IsRoomToRoom || Submarine == null || open <= 0.0f) return false; + + if (outsideColliderRaycastTimer <= 0.0f) + { + UpdateOutsideColliderPos((Hull)linkedTo[0]); + outsideColliderRaycastTimer = OutsideColliderRaycastInterval; + } + + simPosition = outsideColliderPos; + normal = outsideColliderNormal; + return simPosition != null; + } + + private void UpdateOutsideColliderPos(Hull hull) + { + outsideColliderNormal = null; + outsideColliderPos = null; + + if (Submarine == null) return; + + Vector2 rayDir; + if (IsHorizontal) + { + rayDir = new Vector2(Math.Sign(rect.Center.X - hull.Rect.Center.X), 0); + } + else + { + rayDir = new Vector2(0, Math.Sign((rect.Y - rect.Height / 2) - (hull.Rect.Y - hull.Rect.Height / 2))); + } + + Vector2 rayStart = ConvertUnits.ToSimUnits(WorldPosition); + Vector2 rayEnd = rayStart + rayDir * 500.0f; + + if (Submarine.CheckVisibility(rayStart, rayEnd) != null) + { + outsideColliderNormal = -rayDir; + outsideColliderPos = Submarine.LastPickedPosition; + } } private void UpdateOxygen() diff --git a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs index ddba249ca..40ee9d171 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs @@ -75,6 +75,7 @@ namespace Barotrauma private static Vector2 lastPickedPosition; private static float lastPickedFraction; + private static Vector2 lastPickedNormal; private Md5Hash hash; @@ -120,6 +121,11 @@ namespace Barotrauma get { return lastPickedFraction; } } + public static Vector2 LastPickedNormal + { + get { return lastPickedNormal; } + } + public bool Loading { get; @@ -593,6 +599,7 @@ namespace Barotrauma } float closestFraction = 1.0f; + Vector2 closestNormal = Vector2.Zero; Body closestBody = null; GameMain.World.RayCast((fixture, point, normal, fraction) => { @@ -616,6 +623,7 @@ namespace Barotrauma if (fraction < closestFraction) { closestFraction = fraction; + closestNormal = normal; if (fixture.Body != null) closestBody = fixture.Body; } return fraction; @@ -624,6 +632,7 @@ namespace Barotrauma lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction; lastPickedFraction = closestFraction; + lastPickedNormal = closestNormal; return closestBody; } @@ -636,6 +645,7 @@ namespace Barotrauma { Body closestBody = null; float closestFraction = 1.0f; + Vector2 closestNormal = Vector2.Zero; if (Vector2.Distance(rayStart, rayEnd) < 0.01f) { @@ -664,6 +674,7 @@ namespace Barotrauma { closestBody = fixture.Body; closestFraction = fraction; + closestNormal = normal; } return closestFraction; } @@ -672,6 +683,7 @@ namespace Barotrauma lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction; lastPickedFraction = closestFraction; + lastPickedNormal = closestNormal; return closestBody; } From cafcc760641b3d15cfea6d482b837fb8266da984 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 2 Aug 2018 16:55:37 +0300 Subject: [PATCH 109/198] Fixed wall-attached sections of a wire not rendering when the item is being rewired outside the sub --- .../Source/Items/Components/Signal/Wire.cs | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Wire.cs index 57fc2835d..cd7ddf81d 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Wire.cs @@ -52,9 +52,16 @@ namespace Barotrauma.Items.Components } Vector2 drawOffset = Vector2.Zero; - if (item.Submarine != null) + Submarine sub = item.Submarine; + if (IsActive && sub == null) // currently being rewired, we need to get the sub from the connections in case the wire has been taken outside { - drawOffset = item.Submarine.DrawPosition + item.Submarine.HiddenSubPosition; + if (connections[0] != null && connections[0].Item.Submarine != null) sub = connections[0].Item.Submarine; + if (connections[1] != null && connections[1].Item.Submarine != null) sub = connections[1].Item.Submarine; + } + + if (sub != null) + { + drawOffset = sub.DrawPosition + sub.HiddenSubPosition; } float depth = item.IsSelected ? 0.0f : wireSprite.Depth + ((item.ID % 100) * 0.00001f); @@ -76,19 +83,11 @@ namespace Barotrauma.Items.Components foreach (WireSection section in sections) { - section.Draw(spriteBatch, item.Submarine == null ? Color.Green : item.Color, drawOffset, depth, 0.3f); + section.Draw(spriteBatch, item.Color, drawOffset, depth, 0.3f); } if (IsActive && nodes.Count > 0 && Vector2.Distance(newNodePos, nodes[nodes.Count - 1]) > nodeDistance) { - Submarine sub = null; - if (connections[0] != null && connections[0].Item.Submarine != null) sub = connections[0].Item.Submarine; - if (connections[1] != null && connections[1].Item.Submarine != null) sub = connections[1].Item.Submarine; - if (sub != null) - { - drawOffset = sub.DrawPosition + sub.HiddenSubPosition; - } - WireSection.Draw( spriteBatch, new Vector2(nodes[nodes.Count - 1].X, nodes[nodes.Count - 1].Y) + drawOffset, From 2f5ca65542a587aa0acaebf71b61c36d3f332e35 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 3 Aug 2018 19:54:58 +0300 Subject: [PATCH 110/198] Fixed nullref exceptions when a character dies while holding an item. --- Barotrauma/BarotraumaShared/Source/Characters/Character.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs index 1242b7aa6..0f33c6f10 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs @@ -2044,6 +2044,9 @@ namespace Barotrauma base.Remove(); + if (selectedItems[0] != null) selectedItems[0].Drop(this); + if (selectedItems[1] != null) selectedItems[1].Drop(this); + if (info != null) info.Remove(); CharacterList.Remove(this); @@ -2054,9 +2057,6 @@ namespace Barotrauma if (AnimController != null) AnimController.Remove(); - if (selectedItems[0] != null) selectedItems[0].Drop(this); - if (selectedItems[1] != null) selectedItems[1].Drop(this); - foreach (Character c in CharacterList) { if (c.focusedCharacter == this) c.focusedCharacter = null; From a69c52b3c1cfd6320564c83d392a9d804841eb5d Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 3 Aug 2018 21:32:28 +0300 Subject: [PATCH 111/198] Fixed servers failing to write network events for wires with too many nodes, causing clients syncing to fail and everyone getting kicked out due to desync. Now the wire events are split into multiple events, and there's a hard cap (255) on the number of nodes per wire. Closes #563 + Writing too much data into an event no longer breaks event syncing completely, if it happens the server just logs an error and writes an empty event. --- .../Source/Items/Components/Signal/Wire.cs | 83 +++++++++++++------ .../NetEntityEvent/NetEntityEventManager.cs | 7 ++ 2 files changed, 63 insertions(+), 27 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs index e253efe36..97907f398 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs @@ -30,6 +30,9 @@ namespace Barotrauma.Items.Components const float nodeDistance = 32.0f; const float heightFromFloor = 128.0f; + const int MaxNodeCount = 255; + const int MaxNodesPerNetworkEvent = 30; + private List nodes; private List sections; @@ -179,7 +182,7 @@ namespace Barotrauma.Items.Components { if (GameMain.Server != null) { - item.CreateServerEvent(this); + CreateNetworkEvent(); } //the wire is active if only one end has been connected IsActive = connections[0] == null ^ connections[1] == null; @@ -262,9 +265,10 @@ namespace Barotrauma.Items.Components Vector2 pullBackDir = diff == Vector2.Zero ? Vector2.Zero : Vector2.Normalize(diff); user.AnimController.Collider.ApplyForce(pullBackDir * user.Mass * 50.0f); user.AnimController.UpdateUseItem(true, user.SimPosition + pullBackDir * 2.0f); - if (currLength > MaxLength * 1.5f) + if (currLength > MaxLength * 1.5f && GameMain.Client == null) { ClearConnections(); + CreateNetworkEvent(); return; } } @@ -284,14 +288,20 @@ namespace Barotrauma.Items.Components if (newNodePos != Vector2.Zero && canPlaceNode && nodes.Count > 0 && Vector2.Distance(newNodePos, nodes[nodes.Count - 1]) > nodeDistance) { + if (nodes.Count >= MaxNodeCount) + { + nodes.RemoveAt(nodes.Count - 1); + } + nodes.Add(newNodePos); + CleanNodes(); UpdateSections(); Drawable = true; newNodePos = Vector2.Zero; if (GameMain.Server != null) { - item.CreateServerEvent(this); + CreateNetworkEvent(); } } return true; @@ -434,26 +444,20 @@ namespace Barotrauma.Items.Components private void CleanNodes() { - for (int i = nodes.Count - 2; i > 0; i--) - { - if ((nodes[i - 1].X == nodes[i].X || nodes[i - 1].Y == nodes[i].Y) && - (nodes[i + 1].X == nodes[i].X || nodes[i + 1].Y == nodes[i].Y)) - { - if (Vector2.Distance(nodes[i - 1], nodes[i]) == Vector2.Distance(nodes[i + 1], nodes[i])) - { - nodes.RemoveAt(i); - } - } - } - bool removed; do { removed = false; for (int i = nodes.Count - 2; i > 0; i--) { - if ((nodes[i - 1].X == nodes[i].X && nodes[i + 1].X == nodes[i].X) - || (nodes[i - 1].Y == nodes[i].Y && nodes[i + 1].Y == nodes[i].Y)) + if (Math.Abs(nodes[i - 1].X - nodes[i].X) < 1.0f && Math.Abs(nodes[i + 1].X - nodes[i].X) < 1.0f && + Math.Sign(nodes[i - 1].Y - nodes[i].Y) != Math.Sign(nodes[i + 1].Y - nodes[i].Y)) + { + nodes.RemoveAt(i); + removed = true; + } + else if (Math.Abs(nodes[i - 1].Y - nodes[i].Y) < 1.0f && Math.Abs(nodes[i + 1].Y - nodes[i].Y) < 1.0f && + Math.Sign(nodes[i - 1].X - nodes[i].X) != Math.Sign(nodes[i + 1].X - nodes[i].X)) { nodes.RemoveAt(i); removed = true; @@ -585,11 +589,28 @@ namespace Barotrauma.Items.Components base.RemoveComponentSpecific(); } - + + private void CreateNetworkEvent() + { + if (GameMain.Server == null) return; + //split into multiple events because one might not be enough to fit all the nodes + int eventCount = Math.Max((int)Math.Ceiling(nodes.Count / (float)MaxNodesPerNetworkEvent), 1); + for (int i = 0; i < eventCount; i++) + { + GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ComponentState, item.components.IndexOf(this), i }); + } + + } + public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null) { - msg.Write((byte)Math.Min(nodes.Count, 255)); - for (int i = 0; i < Math.Min(nodes.Count, 255); i++) + int eventIndex = (int)extraData[2]; + int nodeStartIndex = eventIndex * MaxNodesPerNetworkEvent; + int nodeCount = MathHelper.Clamp(nodes.Count - nodeStartIndex, 0, MaxNodesPerNetworkEvent); + + msg.WriteRangedInteger(0, (int)Math.Ceiling(MaxNodeCount / (float)MaxNodesPerNetworkEvent), eventIndex); + msg.WriteRangedInteger(0, MaxNodesPerNetworkEvent, nodeCount); + for (int i = nodeStartIndex; i < nodeStartIndex + nodeCount; i++) { msg.Write(nodes[i].X); msg.Write(nodes[i].Y); @@ -598,20 +619,28 @@ namespace Barotrauma.Items.Components public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) { - nodes.Clear(); + int eventIndex = msg.ReadRangedInteger(0, (int)Math.Ceiling(MaxNodeCount / (float)MaxNodesPerNetworkEvent)); + int nodeCount = msg.ReadRangedInteger(0, MaxNodesPerNetworkEvent); + int nodeStartIndex = eventIndex * MaxNodesPerNetworkEvent; - int nodeCount = msg.ReadByte(); - Vector2[] nodePositions = new Vector2[nodeCount]; + Vector2[] nodePositions = new Vector2[nodeStartIndex + nodeCount]; + for (int i = 0; i < nodes.Count && i < nodePositions.Length; i++) + { + nodePositions[i] = nodes[i]; + } for (int i = 0; i < nodeCount; i++) { - nodePositions[i] = new Vector2(msg.ReadFloat(), msg.ReadFloat()); + nodePositions[nodeStartIndex + i] = new Vector2(msg.ReadFloat(), msg.ReadFloat()); + } + + if (nodePositions.Any(n => !MathUtils.IsValid(n))) + { + nodes.Clear(); + return; } - - if (nodePositions.Any(n => !MathUtils.IsValid(n))) return; nodes = nodePositions.ToList(); - UpdateSections(); Drawable = nodes.Any(); } diff --git a/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEventManager.cs b/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEventManager.cs index 65f820231..5c1d1d2c7 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEventManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEventManager.cs @@ -55,6 +55,13 @@ namespace Barotrauma.Networking GameAnalyticsManager.AddErrorEventOnce("NetEntityEventManager.Write:TooLong" + e.Entity.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Too much data in network event for entity \"" + e.Entity.ToString() + "\" (" + tempEventBuffer.LengthBytes + " bytes"); + + //write an empty event breaking the event syncing + tempBuffer.Write((UInt16)0); + tempBuffer.WritePadBits(); + eventCount++; + continue; + } //the ID has been taken by another entity (the original entity has been removed) -> write an empty event From 5b2e31b9232e877d0d84e1e34e35a0c5fe41a294 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Sun, 5 Aug 2018 21:40:33 +0300 Subject: [PATCH 112/198] v0.8.1.11 --- .../Properties/AssemblyInfo.cs | 4 +-- .../Properties/AssemblyInfo.cs | 4 +-- Barotrauma/BarotraumaShared/changelog.txt | 32 +++++++++++++++++++ 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs index fe3acb274..5acf0fbad 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.10")] -[assembly: AssemblyFileVersion("0.8.1.10")] +[assembly: AssemblyVersion("0.8.1.11")] +[assembly: AssemblyFileVersion("0.8.1.11")] diff --git a/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs index 1cfb0fad4..09ad2201a 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.10")] -[assembly: AssemblyFileVersion("0.8.1.10")] +[assembly: AssemblyVersion("0.8.1.11")] +[assembly: AssemblyFileVersion("0.8.1.11")] diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 2263709ac..1af885e24 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,35 @@ +--------------------------------------------------------------------------------------------------------- +v0.8.1.11 +--------------------------------------------------------------------------------------------------------- + +Networking fixes: + - More error logging to diagnose "unknown object header" errors. + - Fixed output inventory of fabricators and deconstructors not being synced between clients. + - Fixed servers failing to write network events for wires with an excessive number of nodes, causing + clients to get kicked due to desync. + - Fixed client-side error messages when respawning without a respawn shuttle. + - Fixed some issues in inventory and connection panel syncing when joining mid-round. + - Fixed fabricated items always appearing to be in full condition client-side (e.g. oxygen tanks which + should be empty after being fabricated). + - Fixed attachable items dropping on the ground client-side when deattaching them (but still staying + in the inventory of the character detaching them). + - Fixed items occasionally dropping instead of being moved to another inventory client-side. + - The "traitorlist" command is usable by clients who have the permission to use it. + +Misc bugfixes: + - Fixed characters occasionally going inside/through obstacle when leaving a submarine that's right + next to another submarine or a level wall. + - More physics error checks and logging. + - Railgun controllers can be used over wifi components. + - Characters attach items to the walls at the position of their hand, not at the center of the body. + - Wifi components can't communicate with the enemy sub in combat missions. + - Fixed the previously selected location staying selected but start button staying disabled when + returning to the lobby screen in the single player campaign. Made it impossible to progress without + restarting if there were no other selectable locations. + - Fixed holdable components reverting their RequiredItems back to the prefab values during loading. + - Fixed wall-attached sections of a wire not rendering when the item is being rewired outside the sub. + - Fixed artifacts occasionally spawning under the sea floor. + --------------------------------------------------------------------------------------------------------- v0.8.1.10 --------------------------------------------------------------------------------------------------------- From 141b77f838c5c9483bed080f98fdc7f7b4a9f5fb Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 6 Aug 2018 10:50:32 +0300 Subject: [PATCH 113/198] Fixed connectionpanel syncing. Closes #575 --- .../Source/Items/Components/Signal/ConnectionPanel.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs index 159372397..aec56368e 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs @@ -165,9 +165,8 @@ namespace Barotrauma.Items.Components for (int i = 0; i < Connections.Count; i++) { wires[i] = new List(); - - int wireCount = msg.ReadRangedInteger(0, Connection.MaxLinked); - for (int j = 0; j < wireCount; j++) + + for (int j = 0; j < Connection.MaxLinked; j++) { ushort wireId = msg.ReadUInt16(); From 362c97ce0559f470c9253c91bc0aff835d0428ad Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 6 Aug 2018 11:01:13 +0300 Subject: [PATCH 114/198] Worn items use the spritecolor set in the sub editor. Closes #574 --- Barotrauma/BarotraumaClient/Source/Characters/Limb.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs b/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs index 6bf04257c..274d8f309 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs @@ -194,10 +194,11 @@ namespace Barotrauma } } + Color wearableColor = wearable.WearableComponent.Item.GetSpriteColor(); wearable.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X, -body.DrawPosition.Y), - color, origin, - -body.DrawRotation, + new Color((color.R * wearableColor.R) / 255.0f, (color.G * wearableColor.G) / 255.0f, (color.B * wearableColor.B) / 255.0f, (color.A * wearableColor.A) / 255.0f), + origin, -body.DrawRotation, Scale, spriteEffect, depth); } From 0fa046b3251aa11716953ffe7f0ffcbbfc302cdd Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 6 Aug 2018 11:02:48 +0300 Subject: [PATCH 115/198] Removed monitor (unused item that doesn't function atm). Closes #580 --- .../Content/Items/Electricity/monitor.png | Bin 216 -> 0 bytes .../Content/Items/Electricity/monitors.xml | 18 ------------------ .../Data/ContentPackages/Vanilla 0.8.xml | 1 - 3 files changed, 19 deletions(-) delete mode 100644 Barotrauma/BarotraumaShared/Content/Items/Electricity/monitor.png delete mode 100644 Barotrauma/BarotraumaShared/Content/Items/Electricity/monitors.xml diff --git a/Barotrauma/BarotraumaShared/Content/Items/Electricity/monitor.png b/Barotrauma/BarotraumaShared/Content/Items/Electricity/monitor.png deleted file mode 100644 index dab830060fdab0484e4efacde06945e573fbc8d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 216 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP*5S+ zBgmJ5p-Pp3p`n?9;pcxK{gQ#9)PRBERRRNp)eHs(@%%~gN8NyG<2+p)Ln>}1r6eT$ zIPbuikeHB=5V7H#K;$gprnd@g2P~E_%vh>(qG^^}pG)Sxx;* - - - - - - - - - - - - \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.8.xml b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.8.xml index 1d25166aa..a2f81245b 100644 --- a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.8.xml +++ b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.8.xml @@ -13,7 +13,6 @@ - From 3956c1d7a02acd670a1243803f8ec778c46b4c58 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 6 Aug 2018 11:09:06 +0300 Subject: [PATCH 116/198] StatusEffects with a duration ignore removed targets and automatically stop if all targets have been removed. Closes #571 --- .../Source/StatusEffects/StatusEffect.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/Source/StatusEffects/StatusEffect.cs index 4293ee1b3..16c1079d1 100644 --- a/Barotrauma/BarotraumaShared/Source/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/Source/StatusEffects/StatusEffect.cs @@ -484,7 +484,14 @@ namespace Barotrauma if (element.Parent.CheckConditionalAlways && !element.Parent.HasRequiredConditions(element.Targets)) { - DurationList.Remove(element); + DurationList.RemoveAt(i); + continue; + } + + element.Targets.RemoveAll(t => t is Entity entity && entity.Removed); + if (element.Targets.Count == 0) + { + DurationList.RemoveAt(i); continue; } @@ -492,9 +499,7 @@ namespace Barotrauma { for (int n = 0; n < element.Parent.propertyNames.Length; n++) { - SerializableProperty property; - - if (target == null || target.SerializableProperties == null || !target.SerializableProperties.TryGetValue(element.Parent.propertyNames[n], out property)) continue; + if (target == null || target.SerializableProperties == null || !target.SerializableProperties.TryGetValue(element.Parent.propertyNames[n], out SerializableProperty property)) continue; element.Parent.ApplyToProperty(property, element.Parent.propertyEffects[n], CoroutineManager.UnscaledDeltaTime); } From a8604deeb68b28e9f80ba1ee14f8f74b5dcaa063 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 6 Aug 2018 11:18:37 +0300 Subject: [PATCH 117/198] Removed monitor files from the vs project --- Barotrauma/BarotraumaShared/BarotraumaShared.projitems | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Barotrauma/BarotraumaShared/BarotraumaShared.projitems b/Barotrauma/BarotraumaShared/BarotraumaShared.projitems index 204cfebb4..e739a4fc7 100644 --- a/Barotrauma/BarotraumaShared/BarotraumaShared.projitems +++ b/Barotrauma/BarotraumaShared/BarotraumaShared.projitems @@ -325,12 +325,6 @@ PreserveNewest - - PreserveNewest - - - PreserveNewest - PreserveNewest From e57eb739e27acbfbbc0ea62e49df1c6fe11e6829 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 6 Aug 2018 11:19:10 +0300 Subject: [PATCH 118/198] Cloned items copy the RequiredItems from the original item. Closes #581 --- Barotrauma/BarotraumaShared/Source/Items/Item.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index 1ff163899..6fabcf24b 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -492,7 +492,12 @@ namespace Barotrauma if (!property.Value.Attributes.OfType().Any()) continue; clone.components[i].properties[property.Key].TrySetValue(property.Value.GetValue()); } + for (int j = 0; j < components[i].requiredItems.Count; j++) + { + clone.components[i].requiredItems[j].JoinedNames = components[i].requiredItems[j].JoinedNames; + } } + if (ContainedItems != null) { foreach (Item containedItem in ContainedItems) From 9052782a6c8278c09e895ca9286132643d17b648 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 6 Aug 2018 11:22:50 +0300 Subject: [PATCH 119/198] Fixed index out of range exception when trying to determine outside collider position for a gap that's not connected to anything --- Barotrauma/BarotraumaShared/Source/Map/Gap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/Source/Map/Gap.cs b/Barotrauma/BarotraumaShared/Source/Map/Gap.cs index dc55ee578..e0e69ae50 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Gap.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Gap.cs @@ -523,7 +523,7 @@ namespace Barotrauma simPosition = null; normal = null; - if (IsRoomToRoom || Submarine == null || open <= 0.0f) return false; + if (IsRoomToRoom || Submarine == null || open <= 0.0f || linkedTo.Count == 0 || !(linkedTo[0] is Hull)) return false; if (outsideColliderRaycastTimer <= 0.0f) { From d51d01c3e844232b6c26719cf725dbd2e1f46182 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 6 Aug 2018 11:43:46 +0300 Subject: [PATCH 120/198] Fixed Submarine.HandleLimbCollision attempting to set the velocity of the sub to an invalid value when the current velocity is extremely small --- Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs b/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs index 22d147ad4..89de82b74 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs @@ -505,7 +505,7 @@ namespace Barotrauma avgContactNormal /= levelContacts.Count; float contactDot = Vector2.Dot(Body.LinearVelocity, -avgContactNormal); - if (contactDot > 0.0f) + if (contactDot > 0.001f) { Vector2 velChange = Vector2.Normalize(Body.LinearVelocity) * contactDot; if (!MathUtils.IsValid(velChange)) From af3fa80011b01f396f531382eae92f04867aafec Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 6 Aug 2018 12:32:00 +0300 Subject: [PATCH 121/198] Clients only include the last 20 console messages in the "invalid object header" error messages, and the console messages are not displayed in the console itself. Closes #569 --- .../Source/Networking/GameClient.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs index 55f6d0feb..3d0c1d412 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs @@ -1048,17 +1048,16 @@ namespace Barotrauma.Networking errorLines.Add(" - " + e.ToString()); } } - - errorLines.Add("Last console messages:"); - for (int i = DebugConsole.Messages.Count - 1; i > 0; i--) - { - errorLines.Add("[" + DebugConsole.Messages[i].Time + "] " + DebugConsole.Messages[i].Text); - } - + foreach (string line in errorLines) { DebugConsole.ThrowError(line); } + errorLines.Add("Last console messages:"); + for (int i = DebugConsole.Messages.Count - 1; i > Math.Max(0, DebugConsole.Messages.Count - 20); i--) + { + errorLines.Add("[" + DebugConsole.Messages[i].Time + "] " + DebugConsole.Messages[i].Text); + } GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadInGameUpdate", GameAnalyticsSDK.Net.EGAErrorSeverity.Critical, string.Join("\n", errorLines)); DebugConsole.ThrowError("Writing object data to \"crashreport_object.bin\", please send this file to us at http://github.com/Regalis11/Barotrauma/issues"); From 4ebe3d715e6092ff43318c2be657946e8c47722e Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 6 Aug 2018 13:38:39 +0300 Subject: [PATCH 122/198] Characters stay alive for 30 seconds after a client disconnects, and if the client rejoins during that time they regain control of the character. Closes #42 --- .../Source/Characters/CharacterNetworking.cs | 9 +++-- .../Source/Networking/GameServer.cs | 33 +++++++++++++++++-- .../Source/Networking/GameServerSettings.cs | 7 ++++ 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaShared/Source/Characters/CharacterNetworking.cs index 81a5d9e98..794ab3e4e 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/CharacterNetworking.cs @@ -79,11 +79,16 @@ namespace Barotrauma private List memState = new List(); private List memLocalState = new List(); - + private bool networkUpdateSent; public bool isSynced = false; - + + public string OwnerClientIP; + public string OwnerClientName; + public bool ClientDisconnected; + public float KillDisconnectedTimer; + public List MemState { get { return memState; } diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs index 2e948319a..6d65e1ded 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs @@ -368,6 +368,28 @@ namespace Barotrauma.Networking entityEventManager.Update(connectedClients); + foreach (Character character in Character.CharacterList) + { + if (character.IsDead || !character.ClientDisconnected) continue; + + character.KillDisconnectedTimer += deltaTime; + character.SetStun(1.0f); + if (character.KillDisconnectedTimer > KillDisconnectedTime) + { + character.Kill(CauseOfDeath.Disconnected); + continue; + } + + Client owner = connectedClients.Find(c => + c.InGame && !c.NeedsMidRoundSync && + c.Name == character.OwnerClientName && + c.Connection.RemoteEndPoint.Address.ToString() == character.OwnerClientIP); + if (owner != null) + { + SetClientCharacter(owner, character); + } + } + bool isCrewDead = connectedClients.All(c => c.Character == null || c.Character.IsDead || c.Character.IsUnconscious) && (myCharacter == null || myCharacter.IsDead || myCharacter.IsUnconscious); @@ -1313,6 +1335,8 @@ namespace Barotrauma.Networking spawnedCharacter.GiveJobItems(assignedWayPoints[i]); teamClients[i].Character = spawnedCharacter; + spawnedCharacter.OwnerClientIP = teamClients[i].Connection.RemoteEndPoint.Address.ToString(); + spawnedCharacter.OwnerClientName = teamClients[i].Name; #if CLIENT GameMain.GameSession.CrewManager.AddCharacter(spawnedCharacter); @@ -1610,8 +1634,8 @@ namespace Barotrauma.Networking if (gameStarted && client.Character != null) { + client.Character.ClientDisconnected = true; client.Character.ClearInputs(); - client.Character.Kill(CauseOfDeath.Disconnected, true); } client.Character = null; @@ -2006,6 +2030,8 @@ namespace Barotrauma.Networking if (client.Character != null) { client.Character.IsRemotePlayer = false; + client.Character.OwnerClientIP = null; + client.Character.OwnerClientName = null; } if (newCharacter == null) @@ -2015,16 +2041,19 @@ namespace Barotrauma.Networking CreateEntityEvent(client.Character, new object[] { NetEntityEvent.Type.Control, null }); client.Character = null; } - } else //taking control of a new character { + newCharacter.ClientDisconnected = false; + newCharacter.KillDisconnectedTimer = 0.0f; newCharacter.ResetNetState(); if (client.Character != null) { newCharacter.LastNetworkUpdateID = client.Character.LastNetworkUpdateID; } + newCharacter.OwnerClientIP = client.Connection.RemoteEndPoint.Address.ToString(); + newCharacter.OwnerClientName = client.Name; newCharacter.IsRemotePlayer = true; newCharacter.Enabled = true; client.Character = newCharacter; diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs index 63af136a5..fdc1d8a48 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs @@ -219,6 +219,13 @@ namespace Barotrauma.Networking private set; } + [Serialize(30.0f, true)] + public bool KillDisconnectedTime + { + get; + private set; + } + [Serialize(true, true)] public bool TraitorUseRatio { From fa65372715c9f202412a31ae600dedd9f7b9eece Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 6 Aug 2018 13:41:36 +0300 Subject: [PATCH 123/198] Whoops, save before committing --- .../BarotraumaShared/Source/Networking/GameServerSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs index fdc1d8a48..087259b39 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs @@ -220,7 +220,7 @@ namespace Barotrauma.Networking } [Serialize(30.0f, true)] - public bool KillDisconnectedTime + public float KillDisconnectedTime { get; private set; From c463baed44a2572c7ac431808cb42e0deb52acb9 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 6 Aug 2018 14:08:00 +0300 Subject: [PATCH 124/198] Made medical & toxic cabinets waterproof. Closes #578 --- .../Content/Items/Containers/containers.xml | 14 ++++++++------ .../BarotraumaShared/Source/Items/Item.cs | 19 ++++++++++++++++--- .../Source/Items/ItemPrefab.cs | 7 +++++++ 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Content/Items/Containers/containers.xml b/Barotrauma/BarotraumaShared/Content/Items/Containers/containers.xml index 2d754d99f..0a826eb83 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Containers/containers.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Containers/containers.xml @@ -26,7 +26,8 @@ + pickdistance="150" + waterproof="true"> @@ -37,9 +38,10 @@ + name="Toxic Cabinet" + linkable="true" + pickdistance="150" + waterproof="true"> @@ -52,7 +54,7 @@ + pickdistance="150"> @@ -74,7 +76,7 @@ + pickdistance="150"> diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index 6fabcf24b..a037b0c81 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -252,6 +252,11 @@ namespace Barotrauma get { return prefab.FireProof; } } + public bool WaterProof + { + get { return prefab.WaterProof; } + } + public bool CanUseOnSelf { get { return prefab.CanUseOnSelf; } @@ -662,7 +667,6 @@ namespace Barotrauma if (Container == null) return null; Item rootContainer = Container; - while (rootContainer.Container != null) { rootContainer = rootContainer.Container; @@ -882,8 +886,17 @@ namespace Barotrauma } inWater = IsInWater(); - - if (inWater) ApplyStatusEffects(ActionType.InWater, deltaTime); + if (inWater) + { + bool waterProof = WaterProof; + Item container = this.Container; + while (!waterProof && container != null) + { + waterProof = container.WaterProof; + container = container.Container; + } + if (!waterProof) ApplyStatusEffects(ActionType.InWater, deltaTime); + } if (body == null || !body.Enabled || !inWater || ParentInventory != null) return; diff --git a/Barotrauma/BarotraumaShared/Source/Items/ItemPrefab.cs b/Barotrauma/BarotraumaShared/Source/Items/ItemPrefab.cs index 856cee705..84c1a467c 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/ItemPrefab.cs @@ -122,6 +122,13 @@ namespace Barotrauma private set; } + [Serialize(false, false)] + public bool WaterProof + { + get; + private set; + } + [Serialize(0.0f, false)] public float ImpactTolerance { From 1a5099677450c8777f4c14eeb1706374e2f5a21a Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 6 Aug 2018 14:14:30 +0300 Subject: [PATCH 125/198] Server doesn't force reconnecting spectate-only clients to control their previous character. --- Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs index 6d65e1ded..d95c18533 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs @@ -384,7 +384,7 @@ namespace Barotrauma.Networking c.InGame && !c.NeedsMidRoundSync && c.Name == character.OwnerClientName && c.Connection.RemoteEndPoint.Address.ToString() == character.OwnerClientIP); - if (owner != null) + if (owner != null && (!AllowSpectating || !owner.SpectateOnly)) { SetClientCharacter(owner, character); } From 7c93730fc3d481f95c273651a9183397e5181609 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 6 Aug 2018 14:28:54 +0300 Subject: [PATCH 126/198] Fixed errors in netlobbyscreen when attempting to select a sub that fails to load (for example due to a missing/corrupt file), campaign cannot be started if the selected submarine cannot be loaded --- .../BarotraumaClient/Source/Screens/CampaignSetupUI.cs | 8 ++++++++ .../BarotraumaClient/Source/Screens/NetLobbyScreen.cs | 7 ++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs b/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs index 578ee4bf0..b5f920245 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs @@ -57,6 +57,14 @@ namespace Barotrauma Submarine selectedSub = subList.SelectedData as Submarine; if (selectedSub == null) return false; + + if (string.IsNullOrEmpty(selectedSub.MD5Hash.Hash)) + { + ((GUITextBlock)subList.Selected).TextColor = Color.DarkRed * 0.8f; + subList.Selected.CanBeFocused = false; + subList.Deselect(); + return false; + } string savePath = SaveUtil.CreateSavePath(isMultiplayer ? SaveUtil.SaveType.Multiplayer : SaveUtil.SaveType.Singleplayer, saveNameBox.Text); if (selectedSub.HasTag(SubmarineTag.Shuttle) || !selectedSub.CompatibleContentPackages.Contains(GameMain.SelectedPackage.Name)) diff --git a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs index c75161cb0..4240e64ea 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs @@ -737,10 +737,11 @@ namespace Barotrauma //hash will be null if opening the sub file failed -> don't select the sub if (string.IsNullOrWhiteSpace(hash)) { - if (component is GUITextBlock textBlock) + GUITextBlock submarineTextBlock = component.GetChild(); + if (submarineTextBlock != null) { - textBlock.TextColor = Color.DarkRed * 0.8f; - textBlock.CanBeFocused = false; + submarineTextBlock.TextColor = Color.DarkRed * 0.8f; + submarineTextBlock.CanBeFocused = false; } else { From e60ebc1930e616e21e87ef52e9c002e1f9770718 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 6 Aug 2018 14:53:07 +0300 Subject: [PATCH 127/198] Handling and logging exceptions thrown by ItemComponent constructors, added stack traces to a couple of error messages --- .../ClientEntityEventManager.cs | 2 +- .../Source/Items/Components/ItemComponent.cs | 19 +++++++++++++++---- .../BarotraumaShared/Source/Map/Entity.cs | 2 +- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs b/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs index 79cfc308b..e25b575a4 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs @@ -200,7 +200,7 @@ namespace Barotrauma.Networking catch (Exception e) { - string errorMsg = "Failed to read event for entity \"" + entity.ToString() + "\"! (MidRoundSyncing: " + thisClient.MidRoundSyncing + ")\n" + e.StackTrace; + string errorMsg = "Failed to read event for entity \"" + entity.ToString() + "\" (" + e.Message + ")! (MidRoundSyncing: " + thisClient.MidRoundSyncing + ")\n" + e.StackTrace; errorMsg += "\nPrevious entities:"; for (int j = entities.Count - 2; j >= 0; j--) { diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs index 9bd033a11..f397aab03 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs @@ -632,11 +632,22 @@ namespace Barotrauma.Items.Components return null; } - object[] lobject = new object[] { item, element }; - object component = constructor.Invoke(lobject); + ItemComponent ic = null; + try + { + object[] lobject = new object[] { item, element }; + object component = constructor.Invoke(lobject); - ItemComponent ic = (ItemComponent)component; - ic.name = element.Name.ToString(); + ic = (ItemComponent)component; + ic.name = element.Name.ToString(); + } + catch (TargetInvocationException e) + { + DebugConsole.ThrowError("Error while loading entity of the type " + t + ".", e.InnerException); + GameAnalyticsManager.AddErrorEventOnce("ItemComponent.Load:TargetInvocationException" + item.Name + element.Name, + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Error while loading entity of the type " + t + " (" + e.InnerException + ")\n" + Environment.StackTrace); + } return ic; } diff --git a/Barotrauma/BarotraumaShared/Source/Map/Entity.cs b/Barotrauma/BarotraumaShared/Source/Map/Entity.cs index 8490e6113..3297e0831 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Entity.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Entity.cs @@ -138,7 +138,7 @@ namespace Barotrauma GameAnalyticsManager.AddErrorEventOnce( "Entity.RemoveAll:Exception" + e.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, - "Error while removing entity \"" + e.ToString() + "\"" + exception.Message); + "Error while removing entity \"" + e.ToString() + " (" + exception.Message + ")\n" + exception.StackTrace); } } StringBuilder errorMsg = new StringBuilder(); From a5a09aa664162e65c6d7e880aa1ee0cddd7fec20 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 6 Aug 2018 15:48:47 +0300 Subject: [PATCH 128/198] Fixed OwnerClientName & OwnerClientIP not being set on respawned characters. The characters of clients who aren't in-game 30 seconds after a round starts are treated as disconnected, making it possible for the client to gain control of the character if they do manage to connect. Closes #583, #584 --- Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs | 5 ++--- .../BarotraumaShared/Source/Networking/RespawnManager.cs | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs index d95c18533..3d8252623 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs @@ -872,11 +872,10 @@ namespace Barotrauma.Networking else { //if 30 seconds have passed since the round started and the client isn't ingame yet, - //kill the client's character + //consider the client's character disconnected (causing it to die if the client does not join soon) if (gameStarted && c.Character != null && (DateTime.Now - roundStartTime).Seconds > 30.0f) { - c.Character.Kill(CauseOfDeath.Disconnected); - c.Character = null; + c.Character.ClientDisconnected = true; } ClientWriteLobby(c); diff --git a/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs b/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs index f5a526544..227e86c83 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs @@ -519,6 +519,8 @@ namespace Barotrauma.Networking { #endif clients[i].Character = character; + character.OwnerClientIP = clients[i].Connection.RemoteEndPoint.Address.ToString(); + character.OwnerClientName = clients[i].Name; GameServer.Log(string.Format("Respawning {0} ({1}) as {2}", clients[i].Name, clients[i].Connection?.RemoteEndPoint?.Address, characterInfos[i].Job.Name), ServerLog.MessageType.Spawning); #if CLIENT From cfc03f844302d540a1315fafa7d08921e9e2da3d Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 6 Aug 2018 22:14:37 +0300 Subject: [PATCH 129/198] Controllers can't focus the camera on broken items (none of the current focusable items can break but may be useful for modders). Closes #590 --- .../Source/Items/Components/Machines/Controller.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Controller.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Controller.cs index ede3f0aed..926742400 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Controller.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Controller.cs @@ -245,6 +245,7 @@ namespace Barotrauma.Items.Components for (int i = item.LastSentSignalRecipients.Count - 1; i >= 0; i--) { + if (item.LastSentSignalRecipients[i].Condition <= 0.0f) continue; if (item.LastSentSignalRecipients[i].Prefab.FocusOnSelected) { return item.LastSentSignalRecipients[i]; From 2d18da40eb84fcf30eaed3bef804ae3006a997f8 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 7 Aug 2018 11:12:09 +0300 Subject: [PATCH 130/198] Made calyxanide usable without a medical syringe and made it more effective when injected. --- .../BarotraumaShared/Content/Items/Medical/medical.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/Content/Items/Medical/medical.xml b/Barotrauma/BarotraumaShared/Content/Items/Medical/medical.xml index 08a152a65..e5edf002b 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Medical/medical.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Medical/medical.xml @@ -423,10 +423,13 @@ - + + + + From 535467028860f037844b9f35242398286c58edee Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 7 Aug 2018 11:36:53 +0300 Subject: [PATCH 131/198] Fixed StatusEffects being multiplied by deltatime when a client uses an item with the "use on self" buttons (even though the effect should be instantaneous). Caused effects with no duration to do almost nothing, making calyxanide and other meds with instantaneous effects basically useless. Closes #587 --- Barotrauma/BarotraumaClient/Source/Items/Item.cs | 3 ++- Barotrauma/BarotraumaShared/Source/Items/Item.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Item.cs b/Barotrauma/BarotraumaClient/Source/Items/Item.cs index 437e302a3..05c5f7a0b 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Item.cs @@ -402,7 +402,8 @@ namespace Barotrauma ushort targetID = msg.ReadUInt16(); Character target = FindEntityByID(targetID) as Character; - ApplyStatusEffects(actionType, (float)Timing.Step, target, true); + //ignore deltatime - using an item with the useOnSelf buttons is instantaneous + ApplyStatusEffects(actionType, 1.0f, target, true); break; case NetEntityEvent.Type.ChangeProperty: ReadPropertyChange(msg); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index a037b0c81..bad381cb9 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -1457,7 +1457,7 @@ namespace Barotrauma case NetEntityEvent.Type.ApplyStatusEffect: if (c.Character == null || !c.Character.CanInteractWith(this)) return; - ApplyStatusEffects(ActionType.OnUse, (float)Timing.Step, c.Character); + ApplyStatusEffects(ActionType.OnUse, 1.0f, c.Character); if (ContainedItems == null || ContainedItems.All(i => i == null)) { From 4b0c54d664f1db3d70bbc5f8169983571774b832 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 7 Aug 2018 12:45:45 +0300 Subject: [PATCH 132/198] Dockingport gap linking fixes. Closes #551 - Dockingports force the hatch/door gaps to be connected to the hulls between the ports, because connecting them automatically may fail if the ports are positioned in a non-standard way. - Gaps don't recheck hulls in OnMapLoaded if rechecks have been disabled. - Item.UpdateHulls and Gap.UpdateHulls are not called when OnMapLoaded is called during ruin generation. --- .../Source/Items/DockingPort.cs | 2 + .../Source/Items/Components/DockingPort.cs | 37 +++++++++++++++++-- Barotrauma/BarotraumaShared/Source/Map/Gap.cs | 2 +- .../BarotraumaShared/Source/Map/MapEntity.cs | 7 +++- 4 files changed, 41 insertions(+), 7 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Items/DockingPort.cs b/Barotrauma/BarotraumaClient/Source/Items/DockingPort.cs index fee47afdb..0800ae328 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/DockingPort.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/DockingPort.cs @@ -62,6 +62,8 @@ namespace Barotrauma.Items.Components } } + if (!GameMain.DebugDraw) return; + if (bodies != null) { for (int i = 0; i < bodies.Length; i++) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs index b1244bcf8..b87de4e83 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs @@ -123,7 +123,7 @@ namespace Barotrauma.Items.Components if (joint != null) { CreateJoint(joint is WeldJoint); - LinkHullsToGap(); + LinkHullsToGaps(); } else if (dockingTarget.joint != null) { @@ -132,7 +132,7 @@ namespace Barotrauma.Items.Components { dockingTarget.CreateJoint(dockingTarget.joint is WeldJoint); } - dockingTarget.LinkHullsToGap(); + dockingTarget.LinkHullsToGaps(); } } } @@ -435,7 +435,7 @@ namespace Barotrauma.Items.Components gap = new Gap(new Rectangle(hullRects[0].X, hullRects[0].Y+2, hullRects[0].Width, 4), false, subs[0]); } - LinkHullsToGap(); + LinkHullsToGaps(); hulls[0].ShouldBeSaved = false; hulls[1].ShouldBeSaved = false; @@ -457,7 +457,7 @@ namespace Barotrauma.Items.Components } } - private void LinkHullsToGap() + private void LinkHullsToGaps() { if (gap == null || hulls == null || hulls[0] == null || hulls[1] == null) { @@ -495,6 +495,35 @@ namespace Barotrauma.Items.Components gap.linkedTo.Add(hulls[0]); } } + + for (int i = 0; i < 2; i++) + { + Gap gap = i == 0 ? door?.LinkedGap : dockingTarget?.door?.LinkedGap; + if (gap == null || gap.linkedTo.Count == 2) continue; + + if (IsHorizontal) + { + if (item.WorldPosition.X < dockingTarget.item.WorldPosition.X) + { + if (!gap.linkedTo.Contains(hulls[0])) gap.linkedTo.Add(hulls[0]); + } + else + { + if (!gap.linkedTo.Contains(hulls[1])) gap.linkedTo.Add(hulls[1]); + } + } + else + { + if (item.WorldPosition.Y < dockingTarget.item.WorldPosition.Y) + { + if (!gap.linkedTo.Contains(hulls[0])) gap.linkedTo.Add(hulls[0]); + } + else + { + if (!gap.linkedTo.Contains(hulls[1])) gap.linkedTo.Add(hulls[1]); + } + } + } } public void Undock() diff --git a/Barotrauma/BarotraumaShared/Source/Map/Gap.cs b/Barotrauma/BarotraumaShared/Source/Map/Gap.cs index e0e69ae50..b2b991092 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Gap.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Gap.cs @@ -641,7 +641,7 @@ namespace Barotrauma public override void OnMapLoaded() { - FindHulls(); + if (!DisableHullRechecks) FindHulls(); } public static void Load(XElement element, Submarine submarine) diff --git a/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs index 2c1516ecf..7b5a55a5a 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs @@ -391,8 +391,11 @@ namespace Barotrauma mapEntityList[i].OnMapLoaded(); } - Item.UpdateHulls(); - Gap.UpdateHulls(); + if (sub != null) + { + Item.UpdateHulls(); + Gap.UpdateHulls(); + } foreach (LinkedSubmarine linkedSub in linkedSubs) { From 004250409aff39c14cd514b0c084e6b7ce95cb57 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 7 Aug 2018 13:21:46 +0300 Subject: [PATCH 133/198] Fixed number of completed missions in a level not increasing if the connection is not selected after completing a mission. Closes #594 --- Barotrauma/BarotraumaShared/Source/Map/Map/Map.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Barotrauma/BarotraumaShared/Source/Map/Map/Map.cs b/Barotrauma/BarotraumaShared/Source/Map/Map/Map.cs index 607f06e6d..f6272f365 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Map/Map.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Map/Map.cs @@ -446,6 +446,7 @@ namespace Barotrauma for (int i = 0; i < connections.Count; i++) { if (!connections[i].Passed) continue; + connections[i].CheckMissionCompleted(); var connectionElement = new XElement("connection", new XAttribute("i", i)); if (connections[i].MissionsCompleted > 0) connectionElement.Add(new XAttribute("m", connections[i].MissionsCompleted)); @@ -525,6 +526,15 @@ namespace Barotrauma MissionsCompleted = 0; } + public void CheckMissionCompleted() + { + if (mission != null && mission.Completed) + { + MissionsCompleted++; + mission = null; + } + } + public Location OtherLocation(Location location) { if (locations[0] == location) From 4aeaf93af85d0b9bd222dcd9d07bd2075e0b2467 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 7 Aug 2018 14:36:08 +0300 Subject: [PATCH 134/198] Fixed engines trying to apply infinite force to the submarine if MinVoltage is set to zero. --- .../Source/Items/Components/Machines/Engine.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Engine.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Engine.cs index f58aa9494..a62722935 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Engine.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Engine.cs @@ -56,7 +56,7 @@ namespace Barotrauma.Items.Components public float CurrentVolume { - get { return Math.Abs((force / 100.0f) * (voltage / minVoltage)); } + get { return Math.Abs((force / 100.0f) * Math.Min(voltage / minVoltage, 1.0f)); } } public override void Update(float deltaTime, Camera cam) @@ -70,7 +70,7 @@ namespace Barotrauma.Items.Components Force = MathHelper.Lerp(force, (voltage < minVoltage) ? 0.0f : targetForce, 0.1f); if (Math.Abs(Force) > 1.0f) { - Vector2 currForce = new Vector2((force / 100.0f) * maxForce * (voltage / minVoltage), 0.0f); + Vector2 currForce = new Vector2((force / 100.0f) * maxForce * Math.Min(voltage / minVoltage, 1.0f), 0.0f); item.Submarine.ApplyForce(currForce); From 865b35c6cd99bdf9cbc1e18ea77f78dea9b4e405 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 7 Aug 2018 16:49:17 +0300 Subject: [PATCH 135/198] Fixed "attempted to access a removed AITarget" errors in AIObjectiveIdle --- .../AI/Objectives/AIObjectiveIdle.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs index 6d0c6df9f..3592e8eae 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs @@ -30,14 +30,9 @@ namespace Barotrauma protected override void Act(float deltaTime) { - var pathSteering = character.AIController.SteeringManager as IndoorsSteeringManager; + if (pathSteering == null) return; - if (pathSteering==null) - { - return; - } - if (character.AnimController.InWater) { //attempt to find a safer place if in water @@ -67,16 +62,16 @@ namespace Barotrauma newTargetTimer -= deltaTime; - + //wander randomly // - if reached the end of the path // - if the target is unreachable // - if the path requires going outside - if (pathSteering==null || (pathSteering.CurrentPath != null && + if (pathSteering == null || (pathSteering.CurrentPath != null && (pathSteering.CurrentPath.NextNode == null || pathSteering.CurrentPath.Unreachable || pathSteering.CurrentPath.HasOutdoorsNodes))) { //steer away from edges of the hull - if (character.AnimController.CurrentHull!=null) + if (character.AnimController.CurrentHull != null) { float leftDist = character.Position.X - character.AnimController.CurrentHull.Rect.X; float rightDist = character.AnimController.CurrentHull.Rect.Right - character.Position.X; @@ -113,8 +108,13 @@ namespace Barotrauma return; } - - if (currentTarget == null) return; + + if (currentTarget?.Entity == null) return; + if (currentTarget.Entity.Removed) + { + currentTarget = null; + return; + } character.AIController.SteeringManager.SteeringSeek(currentTarget.SimPosition, 2.0f); } From 2859b5c7f7e392004ac08c982e257d14d71a5af6 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 7 Aug 2018 16:49:51 +0300 Subject: [PATCH 136/198] Added physics error check & logging to gap update --- Barotrauma/BarotraumaShared/Source/Map/Gap.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Map/Gap.cs b/Barotrauma/BarotraumaShared/Source/Map/Gap.cs index b2b991092..5f67267c8 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Gap.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Gap.cs @@ -225,7 +225,7 @@ namespace Barotrauma EmitParticles(deltaTime); - if (flowTargetHull != null && lerpedFlowForce != Vector2.Zero) + if (flowTargetHull != null && lerpedFlowForce.LengthSquared() > 0.0001f) { foreach (Character character in Character.CharacterList) { @@ -249,11 +249,25 @@ namespace Barotrauma if (!IsHorizontal) { float xDist = Math.Abs(limb.WorldPosition.X - WorldPosition.X); - if (xDist > rect.Width || rect.Width == 0) return; + if (xDist > rect.Width || rect.Width == 0) break; force *= 1.0f - xDist / rect.Width; } + if (!MathUtils.IsValid(force)) + { + string errorMsg = "Attempted to apply invalid flow force to the character \"" + character.Name + + "\", gap pos: " + WorldPosition + + ", limb pos: " + limb.WorldPosition + + ", flowforce: " + flowForce + ", lerpedFlowForce:" + lerpedFlowForce + + ", dist: " + dist; + + DebugConsole.Log(errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Gap.Update:InvalidFlowForce:" + character.Name, + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + errorMsg); + continue; + } character.AnimController.Collider.ApplyForce(force * limb.body.Mass); } } From c34b2361dc4ef1f9613875fd50c35d9da2110acf Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 8 Aug 2018 02:30:08 +0300 Subject: [PATCH 137/198] v0.8.1.12 --- .../Properties/AssemblyInfo.cs | 4 ++-- .../Properties/AssemblyInfo.cs | 4 ++-- Barotrauma/BarotraumaShared/changelog.txt | 21 +++++++++++++++++++ .../BarotraumaShared/serversettings.xml | 1 + 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs index 5acf0fbad..ee0f1eff0 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.11")] -[assembly: AssemblyFileVersion("0.8.1.11")] +[assembly: AssemblyVersion("0.8.1.12")] +[assembly: AssemblyFileVersion("0.8.1.12")] diff --git a/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs index 09ad2201a..145e7eaa6 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.11")] -[assembly: AssemblyFileVersion("0.8.1.11")] +[assembly: AssemblyVersion("0.8.1.12")] +[assembly: AssemblyFileVersion("0.8.1.12")] diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 1af885e24..18dc1244e 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,24 @@ +--------------------------------------------------------------------------------------------------------- +v0.8.1.12 +--------------------------------------------------------------------------------------------------------- + +- Fixed connectionpanel syncing (wires dropping client-side during rewiring). +- Characters stay alive for 30 seconds after a client disconnects, and if the client rejoins during that +time they regain control of the character. +- Changing the sprite color of an item also affects the color when the item is being worn. +- Cloned items keep the value of the "required items" field of the original item. +- Fixed crashes caused by gaps that are not connected to anything. +- Removed the unused and non-functional monitor item. +- Made medical and toxic cabinets waterproof (= potassium and other water-sensitive items inside them +are not affected by water). +- Fixed docking ports causing flooding in some custom subs. +- Fixed medical items with an immediate effect (such as calyxanide) not working when a player uses them +on themselves in multiplayer. +- Made calyxanide more effective and usable without a medical syringe. +- Fixed engines trying to apply infinite force to the submarine if MinVoltage is set to zero. +- Fixed levels resetting their mission to the previous one if the game is exited after completing a mission +without selecting the level first. + --------------------------------------------------------------------------------------------------------- v0.8.1.11 --------------------------------------------------------------------------------------------------------- diff --git a/Barotrauma/BarotraumaShared/serversettings.xml b/Barotrauma/BarotraumaShared/serversettings.xml index c42cf2a7b..1823f3dcf 100644 --- a/Barotrauma/BarotraumaShared/serversettings.xml +++ b/Barotrauma/BarotraumaShared/serversettings.xml @@ -24,6 +24,7 @@ traitoruseratio="True" traitorratio="0.2" karmaenabled="False" + KillDisconnectedTime="30" SubSelection="Manual" ModeSelection="Manual" GameMode="SandBox" From 0a84d989a837834140e2287ea9f8c46199709f57 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 8 Aug 2018 11:10:48 +0300 Subject: [PATCH 138/198] Fixed wearable sprite colors being calculated wrong. Closes #607 --- Barotrauma/BarotraumaClient/Source/Characters/Limb.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs b/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs index 274d8f309..b58997d46 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs @@ -197,7 +197,7 @@ namespace Barotrauma Color wearableColor = wearable.WearableComponent.Item.GetSpriteColor(); wearable.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X, -body.DrawPosition.Y), - new Color((color.R * wearableColor.R) / 255.0f, (color.G * wearableColor.G) / 255.0f, (color.B * wearableColor.B) / 255.0f, (color.A * wearableColor.A) / 255.0f), + new Color((color.R * wearableColor.R) / 255.0f, (color.G * wearableColor.G) / 255.0f, (color.B * wearableColor.B) / 255.0f) * ((color.A * wearableColor.A) / 255.0f), origin, -body.DrawRotation, Scale, spriteEffect, depth); } From 506c13c929ee0ffe5bc81f066d053fcad80204ec Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 8 Aug 2018 11:15:05 +0300 Subject: [PATCH 139/198] Again, save before committing --- Barotrauma/BarotraumaClient/Source/Characters/Limb.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs b/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs index b58997d46..395c60f0c 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs @@ -197,7 +197,7 @@ namespace Barotrauma Color wearableColor = wearable.WearableComponent.Item.GetSpriteColor(); wearable.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X, -body.DrawPosition.Y), - new Color((color.R * wearableColor.R) / 255.0f, (color.G * wearableColor.G) / 255.0f, (color.B * wearableColor.B) / 255.0f) * ((color.A * wearableColor.A) / 255.0f), + new Color((color.R * wearableColor.R) / (255.0f * 255.0f), (color.G * wearableColor.G) / (255.0f * 255.0f), (color.B * wearableColor.B) / (255.0f * 255.0f)) * ((color.A * wearableColor.A) / (255.0f * 255.0f)), origin, -body.DrawRotation, Scale, spriteEffect, depth); } From 5e093faaa2b732840cea9f4a2ef9526c7c689890 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 8 Aug 2018 11:27:03 +0300 Subject: [PATCH 140/198] Fixed max recharge speeds of PowerContainers resetting to the default value after saving and reloading a sub. Closes #605 --- .../Source/Items/Components/Power/PowerContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs index 475d322a9..dce278300 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs @@ -76,7 +76,7 @@ namespace Barotrauma.Items.Components } } - [Serialize(10.0f, false), Editable(ToolTip = "How fast the device can be recharged. "+ + [Serialize(10.0f, true), Editable(ToolTip = "How fast the device can be recharged. "+ "For example, a recharge speed of 100 kW and a capacity of 1000 kW*min would mean it takes 10 minutes to fully charge the device.")] public float MaxRechargeSpeed { From 67527df8ab466465127995165430581b8121ad5f Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 8 Aug 2018 11:33:06 +0300 Subject: [PATCH 141/198] Removed duplicate bleed code in Character.Update. Closes #609 --- .../Source/Characters/Character.cs | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs index 0f33c6f10..de7d70803 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs @@ -1611,13 +1611,23 @@ namespace Barotrauma //Health effects if (needsAir) UpdateOxygen(deltaTime); - Health -= bleeding * deltaTime; - Bleeding -= BleedingDecreaseSpeed * deltaTime; + if (DoesBleed) + { + Health -= bleeding * deltaTime; + Bleeding -= BleedingDecreaseSpeed * deltaTime; + } if (health <= minHealth) Kill(CauseOfDeath.Bloodloss); if (!IsDead) LockHands = false; + UpdateSightRange(); + if (aiTarget != null) aiTarget.SoundRange = 0.0f; + + lowPassMultiplier = MathHelper.Lerp(lowPassMultiplier, 1.0f, 0.1f); + + if (health <= minHealth) Kill(CauseOfDeath.Bloodloss); + //ragdoll button if (IsRagdolled) { @@ -1642,22 +1652,6 @@ namespace Barotrauma { selectedConstruction = null; } - - UpdateSightRange(); - if (aiTarget != null) aiTarget.SoundRange = 0.0f; - - lowPassMultiplier = MathHelper.Lerp(lowPassMultiplier, 1.0f, 0.1f); - - if (DoesBleed) - { - Health -= bleeding * deltaTime; - Bleeding -= BleedingDecreaseSpeed * deltaTime; - } - - if (health <= minHealth) Kill(CauseOfDeath.Bloodloss); - - if (!IsDead) LockHands = false; - //CPR stuff is handled in the UpdateCPR function in HumanoidAnimController } partial void UpdateControlled(float deltaTime, Camera cam); From 1a5a76746bc92edaa765f1389e985aebbc4fe877 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 8 Aug 2018 12:01:20 +0300 Subject: [PATCH 142/198] Fixed handcuffed players being able to perform CPR and grab/drag bodies. Closes #608 --- Barotrauma/BarotraumaShared/Source/Characters/Character.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs index de7d70803..4ba534488 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs @@ -1618,9 +1618,7 @@ namespace Barotrauma } if (health <= minHealth) Kill(CauseOfDeath.Bloodloss); - - if (!IsDead) LockHands = false; - + UpdateSightRange(); if (aiTarget != null) aiTarget.SoundRange = 0.0f; @@ -1652,6 +1650,8 @@ namespace Barotrauma { selectedConstruction = null; } + + if (!IsDead) LockHands = false; } partial void UpdateControlled(float deltaTime, Camera cam); From 2a09e68f21058996d0374b4b6e806368f9f1d764 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 8 Aug 2018 14:55:12 +0300 Subject: [PATCH 143/198] Clown costumes hide the torso limb, diving suits hide the waist which also prevents damage bypassing the damage modifiers of the suit. Closes #606, closes #611 --- Barotrauma/BarotraumaShared/Content/Items/Diving/divinggear.xml | 2 ++ Barotrauma/BarotraumaShared/Content/Items/Jobgear/misc.xml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/Content/Items/Diving/divinggear.xml b/Barotrauma/BarotraumaShared/Content/Items/Diving/divinggear.xml index c27274390..768e8d388 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Diving/divinggear.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Diving/divinggear.xml @@ -139,6 +139,8 @@ + + diff --git a/Barotrauma/BarotraumaShared/Content/Items/Jobgear/misc.xml b/Barotrauma/BarotraumaShared/Content/Items/Jobgear/misc.xml index a40911349..76809e79a 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Jobgear/misc.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Jobgear/misc.xml @@ -65,7 +65,7 @@ - + From 1c1a1eb80e507c08df22b04dfed837c19d3a47f5 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 8 Aug 2018 15:12:24 +0300 Subject: [PATCH 144/198] Background sprites spawn at the correct position and rotation in mirrored levels. --- .../Source/Map/Levels/BackgroundSpriteManager.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/BackgroundSpriteManager.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/BackgroundSpriteManager.cs index 8d90ae775..5b657bab6 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/BackgroundSpriteManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/BackgroundSpriteManager.cs @@ -136,9 +136,8 @@ namespace Barotrauma for (int i = 0 ; i < amount; i++) { BackgroundSpritePrefab prefab = GetRandomPrefab(level.GenerationParams.Name); - GraphEdge selectedEdge = null; Vector2 edgeNormal = Vector2.One; - Vector2? pos = FindSpritePosition(level, prefab, out selectedEdge, out edgeNormal); + Vector2? pos = FindSpritePosition(level, prefab, out GraphEdge selectedEdge, out edgeNormal); if (pos == null) continue; @@ -148,7 +147,8 @@ namespace Barotrauma rotation = MathUtils.VectorToAngle(new Vector2(edgeNormal.Y, edgeNormal.X)); } - rotation += Rand.Range(prefab.RandomRotation.X, prefab.RandomRotation.Y, Rand.RandSync.Server); + float randomRot = Rand.Range(prefab.RandomRotation.X, prefab.RandomRotation.Y, Rand.RandSync.Server); + rotation += level.Mirrored ? -randomRot : randomRot; var newSprite = new BackgroundSprite(prefab, new Vector3((Vector2)pos, Rand.Range(prefab.DepthRange.X, prefab.DepthRange.Y, Rand.RandSync.Server)), Rand.Range(prefab.Scale.X, prefab.Scale.Y, Rand.RandSync.Server), rotation); @@ -313,10 +313,12 @@ namespace Barotrauma edgeNormal = normals[index]; float length = Vector2.Distance(closestEdge.point1, closestEdge.point2); - Vector2 dir = (closestEdge.point1 - closestEdge.point2) / length; - Vector2 pos = closestEdge.point2 + dir * Rand.Range(prefab.Sprite.size.X / 2.0f, length - prefab.Sprite.size.X / 2.0f, Rand.RandSync.Server); - return pos; + Vector2 dir = (closestEdge.point1 - closestEdge.point2) / length; + float normalizedPos = Rand.Range(0.0f, 1.0f, Rand.RandSync.Server); + if (level.Mirrored) normalizedPos = 1.0f - normalizedPos; + + return Vector2.Lerp(closestEdge.point2 + dir * prefab.Sprite.size.X / 2.0f, closestEdge.point1 - dir * prefab.Sprite.size.X / 2.0f, normalizedPos); } public void Update(float deltaTime) From 99c76d00999d5eafed4077d50fb1535f2ace4c17 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 8 Aug 2018 15:16:53 +0300 Subject: [PATCH 145/198] Cleanup --- .../Map/Levels/BackgroundSpriteManager.cs | 5 +-- .../Source/Map/Levels/Level.cs | 45 ++++--------------- 2 files changed, 10 insertions(+), 40 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/BackgroundSpriteManager.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/BackgroundSpriteManager.cs index 5b657bab6..8228bceff 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/BackgroundSpriteManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/BackgroundSpriteManager.cs @@ -246,10 +246,7 @@ namespace Barotrauma //make sure the cells are in the same order regardless of whether the level is mirrored or not cells.Sort((c1, c2) => { return level.Mirrored ? Math.Sign(c1.Center.X - c2.Center.X) : -Math.Sign(c1.Center.X - c2.Center.X); }); - - /*System.Diagnostics.Debug.WriteLine("FindSpritePosition - prefab: "+System.IO.Path.GetFileNameWithoutExtension(prefab.Sprite.FilePath)+" cells: "+cells.Count+" spawnpos: "+ prefab.SpawnPos+" randompos: "+ randomPos); - System.Diagnostics.Debug.WriteLine(string.Join(", ",cells.Select(c => level.cells.IndexOf(c))));*/ - + if (cells.Any()) { VoronoiCell cell = cells[Rand.Int(cells.Count, Rand.RandSync.Server)]; diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs index 5c46f7405..8c243c52e 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs @@ -199,7 +199,7 @@ namespace Barotrauma return new Level(seed, Rand.Range(30.0f, 80.0f, Rand.RandSync.Server), LevelGenerationParams.GetRandom(seed)); } - public void Generate(bool mirror = false) + public void Generate(bool mirror = true) { Mirrored = mirror; @@ -389,21 +389,18 @@ namespace Barotrauma pathCells.AddRange(newPathCells); } - - Debug.WriteLine("path: " + sw2.ElapsedMilliseconds + " ms"); + sw2.Restart(); //---------------------------------------------------------------------------------- // remove unnecessary cells and create some holes at the bottom of the level //---------------------------------------------------------------------------------- - - System.Diagnostics.Debug.WriteLine("cellcount before cleaning: " + cells.Count); + cells = CleanCells(pathCells); pathCells.AddRange(CreateBottomHoles(generationParams.BottomHoleProbability, new Rectangle( (int)(borders.Width * 0.2f), 0, (int)(borders.Width * 0.6f), (int)(borders.Height * 0.8f)))); - System.Diagnostics.Debug.WriteLine("cellcount after bottom holes: " + cells.Count); foreach (VoronoiCell cell in cells) { @@ -415,7 +412,6 @@ namespace Barotrauma // initialize the cells that are still left and insert them into the cell grid //---------------------------------------------------------------------------------- - System.Diagnostics.Debug.WriteLine("pathcells before init: " + cells.Count); foreach (VoronoiCell cell in pathCells) { cell.edges.ForEach(e => e.OutsideLevel = false); @@ -435,9 +431,7 @@ namespace Barotrauma //---------------------------------------------------------------------------------- // mirror if needed //---------------------------------------------------------------------------------- - - System.Diagnostics.Debug.WriteLine("cellcount: "+cells.Count); - System.Diagnostics.Debug.WriteLine("pathcellcount: " + pathCells.Count); + if (mirror) { HashSet mirroredEdges = new HashSet(); @@ -508,13 +502,9 @@ namespace Barotrauma ruins = new List(); for (int i = 0; i < generationParams.RuinCount; i++) { - System.Diagnostics.Debug.WriteLine("Generating ruin "+i+" *******************************************"); GenerateRuin(mainPath, mirror); } - - int testSync = Rand.Int(1000, Rand.RandSync.Server); - System.Diagnostics.Debug.WriteLine("TESTSYNC: " + testSync + " ---------------------------------------------",Color.White); - + //---------------------------------------------------------------------------------- // generate the bodies and rendered triangles of the cells //---------------------------------------------------------------------------------- @@ -897,20 +887,13 @@ namespace Barotrauma { Vector2 ruinSize = new Vector2(Rand.Range(5000.0f, 8000.0f, Rand.RandSync.Server), Rand.Range(5000.0f, 8000.0f, Rand.RandSync.Server)); float ruinRadius = Math.Max(ruinSize.X, ruinSize.Y) * 0.5f; - - System.Diagnostics.Debug.WriteLine("Cell count " + cells.Count); - for (int i = 0; i Vector2.DistanceSquared(ruinPos, p.Center) < minDistSqr)) { @@ -946,8 +927,6 @@ namespace Barotrauma } ruinPos = weighedPathPos; - System.Diagnostics.Debug.WriteLine(iter+": " + ruinPos); - if (iter > 10000) break; } @@ -962,16 +941,10 @@ namespace Barotrauma closestDist = dist; } } - - System.Diagnostics.Debug.WriteLine("Final ruin pos: " + ruinPos); - int testSync = Rand.Int(1000, Rand.RandSync.Server); - System.Diagnostics.Debug.WriteLine("TESTSYNC2: " + testSync + " ---------------------------------------------", Color.White); + var ruin = new Ruin(closestPathCell, cells, new Rectangle(MathUtils.ToPoint(ruinPos - ruinSize * 0.5f), MathUtils.ToPoint(ruinSize)), mirror); ruins.Add(ruin); - - testSync = Rand.Int(1000, Rand.RandSync.Server); - System.Diagnostics.Debug.WriteLine("TESTSYNC3: " + testSync + " ---------------------------------------------", Color.White); - + ruin.RuinShapes.Sort((shape1, shape2) => shape2.DistanceFromEntrance.CompareTo(shape1.DistanceFromEntrance)); for (int i = 0; i < 4; i++) { From 5100956baed640d48f4b0a277af226826ab927ef Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 8 Aug 2018 15:30:32 +0300 Subject: [PATCH 146/198] Levels are mirrored when traveling backwards through a connection in the campaign mode. Closes #519 --- .../BarotraumaClient/Source/Networking/GameClient.cs | 5 ++++- .../BarotraumaClient/Source/Screens/LobbyScreen.cs | 5 ++++- .../Source/GameSession/GameSession.cs | 11 +++++------ .../BarotraumaShared/Source/Map/Levels/Level.cs | 2 +- .../BarotraumaShared/Source/Networking/GameServer.cs | 5 ++++- 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs index c794bd446..47bc41491 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs @@ -761,7 +761,10 @@ namespace Barotrauma.Networking else { if (GameMain.GameSession?.CrewManager != null) GameMain.GameSession.CrewManager.Reset(); - GameMain.GameSession.StartRound(campaign.Map.SelectedConnection.Level, true, false); + GameMain.GameSession.StartRound(campaign.Map.SelectedConnection.Level, + reloadSub: true, + loadSecondSub: false, + mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]); } if (respawnAllowed) respawnManager = new RespawnManager(this, GameMain.NetLobbyScreen.UsingShuttle ? GameMain.NetLobbyScreen.SelectedShuttle : null); diff --git a/Barotrauma/BarotraumaClient/Source/Screens/LobbyScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/LobbyScreen.cs index a8df21efa..8bd432931 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/LobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/LobbyScreen.cs @@ -190,7 +190,10 @@ namespace Barotrauma private IEnumerable LoadRound() { - GameMain.GameSession.StartRound(campaignUI.SelectedLevel, true); + GameMain.GameSession.StartRound(campaignUI.SelectedLevel, + reloadSub: true, + loadSecondSub: false, + mirrorLevel: GameMain.GameSession.Map.CurrentLocation != GameMain.GameSession.Map.SelectedConnection.Locations[0]); GameMain.GameScreen.Select(); yield return CoroutineStatus.Success; diff --git a/Barotrauma/BarotraumaShared/Source/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/Source/GameSession/GameSession.cs index 91ded070f..2c8b94a96 100644 --- a/Barotrauma/BarotraumaShared/Source/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/Source/GameSession/GameSession.cs @@ -182,7 +182,7 @@ namespace Barotrauma StartRound(randomLevel, true, loadSecondSub); } - public void StartRound(Level level, bool reloadSub = true, bool loadSecondSub = false) + public void StartRound(Level level, bool reloadSub = true, bool loadSecondSub = false, bool mirrorLevel = false) { #if CLIENT GameMain.LightManager.LosEnabled = GameMain.NetworkMember == null || GameMain.NetworkMember.CharacterInfo != null; @@ -210,11 +210,10 @@ namespace Barotrauma Submarine.MainSubs[1].Load(false); } } - + if (level != null) { - level.Generate(); - + level.Generate(mirrorLevel); submarine.SetPosition(submarine.FindSpawnPos(level.StartPosition - new Vector2(0.0f, 2000.0f))); } @@ -229,9 +228,9 @@ namespace Barotrauma if (GameMode != null) { GameMode.MsgBox(); - if (GameMode is MultiPlayerCampaign campaign && GameMain.Server != null) + if (GameMode is MultiPlayerCampaign mpCampaign && GameMain.Server != null) { - campaign.CargoManager.CreateItems(); + mpCampaign.CargoManager.CreateItems(); } } diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs index 8c243c52e..3137b9385 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs @@ -199,7 +199,7 @@ namespace Barotrauma return new Level(seed, Rand.Range(30.0f, 80.0f, Rand.RandSync.Server), LevelGenerationParams.GetRandom(seed)); } - public void Generate(bool mirror = true) + public void Generate(bool mirror) { Mirrored = mirror; diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs index 6cd095e1a..30e6cc5be 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs @@ -1243,7 +1243,10 @@ namespace Barotrauma.Networking #if CLIENT if (GameMain.GameSession?.CrewManager != null) GameMain.GameSession.CrewManager.Reset(); #endif - GameMain.GameSession.StartRound(campaign.Map.SelectedConnection.Level, true, teamCount > 1); + GameMain.GameSession.StartRound(campaign.Map.SelectedConnection.Level, + reloadSub: true, + loadSecondSub: teamCount > 1, + mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]); } else { From 2f0236d99b7453c1a107054d4cebf6bce0b95f98 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 8 Aug 2018 15:56:19 +0300 Subject: [PATCH 147/198] Fixed PowerContainer RechargeSpeed being capped to the MaxRechargeSpeed set in the prefab due to RechargeSpeed being loaded before MaxRechargeSpeed. Closes #612 --- .../Items/Components/Power/PowerContainer.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs index dce278300..ede2a3c1b 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs @@ -63,6 +63,14 @@ namespace Barotrauma.Items.Components } } } + + [Serialize(10.0f, true), Editable(ToolTip = "How fast the device can be recharged. "+ + "For example, a recharge speed of 100 kW and a capacity of 1000 kW*min would mean it takes 10 minutes to fully charge the device.")] + public float MaxRechargeSpeed + { + get { return maxRechargeSpeed; } + set { maxRechargeSpeed = Math.Max(value, 1.0f); } + } [Serialize(10.0f, true), Editable] public float RechargeSpeed @@ -76,14 +84,6 @@ namespace Barotrauma.Items.Components } } - [Serialize(10.0f, true), Editable(ToolTip = "How fast the device can be recharged. "+ - "For example, a recharge speed of 100 kW and a capacity of 1000 kW*min would mean it takes 10 minutes to fully charge the device.")] - public float MaxRechargeSpeed - { - get { return maxRechargeSpeed; } - set { maxRechargeSpeed = Math.Max(value, 1.0f); } - } - public PowerContainer(Item item, XElement element) : base(item, element) { From 92f3ac54690a81fb029e2157bff0d7b215b4fbb2 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 8 Aug 2018 17:07:08 +0300 Subject: [PATCH 148/198] Server setting for selecting which symbols are allowed in client names. The default setting includes symbols from the character ranges Basic Latin, Latin-1 Supplement, Latin Extended A & B and Cyrillic. Closes #534 --- .../Source/Networking/Client.cs | 26 +++++------- .../Source/Networking/GameServerLogin.cs | 2 +- .../Source/Networking/GameServerSettings.cs | 41 +++++++++++++++++++ .../BarotraumaShared/serversettings.xml | 1 + 4 files changed, 53 insertions(+), 17 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Networking/Client.cs b/Barotrauma/BarotraumaShared/Source/Networking/Client.cs index 80965f69a..f190311fb 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/Client.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/Client.cs @@ -134,17 +134,19 @@ namespace Barotrauma.Networking JobPreferences = new List(JobPrefab.List.GetRange(0, Math.Min(JobPrefab.List.Count, 3))); } - public static bool IsValidName(string name) + public static bool IsValidName(string name, GameServer server) { if (name.Contains("\n") || name.Contains("\r")) return false; + if (name.Any(c => c == ';' || c == ',' || c == '<' || c == '/')) return false; - return (name.All(c => - c != ';' && - c != ',' && - c != '<' && - c != '/')); + foreach (char character in name) + { + if (!server.AllowedClientNameChars.Any(charRange => (int)character >= charRange.First && (int)character <= charRange.Second)) return false; + } + + return true; } - + public static string SanitizeName(string name) { name = name.Trim(); @@ -155,16 +157,8 @@ namespace Barotrauma.Networking string rName = ""; for (int i = 0; i < name.Length; i++) { - if (name[i] < 32) - { - rName += '?'; - } - else - { - rName += name[i]; - } + rName += name[i] < 32 ? '?' : name[i]; } - return rName; } diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServerLogin.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServerLogin.cs index 56d21c33d..317b21583 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServerLogin.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServerLogin.cs @@ -165,7 +165,7 @@ namespace Barotrauma.Networking DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (not in whitelist)", Color.Red); return; } - if (!Client.IsValidName(clName)) + if (!Client.IsValidName(clName, this)) { DisconnectUnauthClient(inc, unauthClient, "Your name contains illegal symbols."); Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (invalid name)", ServerLog.MessageType.Error); diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs index 087259b39..3e3708352 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs @@ -281,6 +281,15 @@ namespace Barotrauma.Networking private set; } + /// + /// A list of int pairs that represent the ranges of UTF-16 codes allowed in client names + /// + public List> AllowedClientNameChars + { + get; + private set; + } = new List>(); + private void SaveSettings() { XDocument doc = new XDocument(new XElement("serversettings")); @@ -302,6 +311,8 @@ namespace Barotrauma.Networking doc.Root.SetAttributeValue("AllowedRandomMissionTypes", string.Join(",", AllowedRandomMissionTypes)); + doc.Root.SetAttributeValue("AllowedClientNameChars", string.Join(",", AllowedClientNameChars.Select(c => c.First + "-" + c.Second))); + #if SERVER doc.Root.SetAttributeValue("password", password); #endif @@ -361,6 +372,36 @@ namespace Barotrauma.Networking TraitorsEnabled = traitorsEnabled; GameMain.NetLobbyScreen.SetTraitorsEnabled(traitorsEnabled); + //"65-90", "97-122", "48-59" = upper and lower case english alphabet and numbers + string[] allowedClientNameCharsStr = doc.Root.GetAttributeStringArray("AllowedClientNameChars", new string[] { "65-90", "97-122", "48-59" }); + foreach (string allowedClientNameCharRange in allowedClientNameCharsStr) + { + string[] splitRange = allowedClientNameCharRange.Split('-'); + if (splitRange.Length == 0 || splitRange.Length > 2) + { + DebugConsole.ThrowError("Error in server settings - "+ allowedClientNameCharRange+" is not a valid range for characters allowed in client names."); + continue; + } + + int min = -1; + if (!int.TryParse(splitRange[0], out min)) + { + DebugConsole.ThrowError("Error in server settings - " + allowedClientNameCharRange + " is not a valid range for characters allowed in client names."); + continue; + } + int max = min; + if (splitRange.Length == 2) + { + if (!int.TryParse(splitRange[1], out max)) + { + DebugConsole.ThrowError("Error in server settings - " + allowedClientNameCharRange + " is not a valid range for characters allowed in client names."); + continue; + } + } + + if (min > -1 && max > -1) AllowedClientNameChars.Add(Pair.Create(min, max)); + } + AllowedRandomMissionTypes = doc.Root.GetAttributeStringArray( "AllowedRandomMissionTypes", MissionPrefab.MissionTypes.ToArray()).ToList(); diff --git a/Barotrauma/BarotraumaShared/serversettings.xml b/Barotrauma/BarotraumaShared/serversettings.xml index 1823f3dcf..7bcf40f11 100644 --- a/Barotrauma/BarotraumaShared/serversettings.xml +++ b/Barotrauma/BarotraumaShared/serversettings.xml @@ -32,4 +32,5 @@ TraitorsEnabled="No" autobantime="60" maxautobantime="360" + AllowedClientNameChars="32-33,38-46,48-57,65-90,91,93,95-122,192-255,384-591,1024-1279" /> From f1a8db5b47d5a05808562a18b75a105a731c7ce7 Mon Sep 17 00:00:00 2001 From: Juan Pablo Arce Date: Wed, 8 Aug 2018 20:57:00 -0300 Subject: [PATCH 149/198] Fixed stereo sound loading I had already fixed this on the dev branch, backporting because it's gonna take a while until it's released --- Barotrauma/BarotraumaClient/Source/Sounds/OggSound.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaClient/Source/Sounds/OggSound.cs b/Barotrauma/BarotraumaClient/Source/Sounds/OggSound.cs index 172ce99b2..9e112658b 100644 --- a/Barotrauma/BarotraumaClient/Source/Sounds/OggSound.cs +++ b/Barotrauma/BarotraumaClient/Source/Sounds/OggSound.cs @@ -39,7 +39,7 @@ namespace Barotrauma.Sounds using (VorbisReader reader = new VorbisReader(oggFile)) { - int bufferSize = (int)reader.TotalSamples; + int bufferSize = (int)reader.TotalSamples * reader.Channels; float[] buffer = new float[bufferSize]; sound.castBuffer = new short[bufferSize]; @@ -52,6 +52,8 @@ namespace Barotrauma.Sounds sound.format = reader.Channels == 1 ? ALFormat.Mono16 : ALFormat.Stereo16; sound.sampleRate = reader.SampleRate; + ALHelper.Check(); + //alSourceId = AL.GenSource(); AL.BufferData(sound.alBufferId, reader.Channels == 1 ? ALFormat.Mono16 : ALFormat.Stereo16, sound.castBuffer, readSamples * sizeof(short), reader.SampleRate); From 45939a91440f1a3cd0ef2bc7596e0a4755fc725d Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 9 Aug 2018 12:08:47 +0300 Subject: [PATCH 150/198] Fixed oxygen tank & welding fuel tank crafting causing everyone to desync. The item condition NetEntityEvent was created before the spawn event of the item, preventing clients from reading the condition event because the item doesn't exist yet. Closes #617 --- .../Items/Components/Machines/Fabricator.cs | 1 + .../BarotraumaShared/Source/Items/Item.cs | 28 +++++++++++++++---- .../Source/Networking/EntitySpawner.cs | 7 +++-- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs index ca08ce693..9c1693851 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs @@ -12,6 +12,7 @@ namespace Barotrauma.Items.Components { public readonly ItemPrefab TargetItem; + //TODO: refactor this (maybe make it a struct) public readonly List> RequiredItems; public readonly float RequiredTime; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index bad381cb9..bd0b18f85 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -1659,8 +1659,25 @@ namespace Barotrauma { msg.Write(ParentInventory.Owner.ID); - int index = ParentInventory.FindIndex(this); - msg.Write(index < 0 ? (byte)255 : (byte)index); + //find the index of the ItemContainer this item is inside to get the item to + //spawn in the correct inventory in multi-inventory items like fabricators + byte containerIndex = 0; + if (Container != null) + { + for (int i = 0; i < Container.components.Count; i++) + { + if (Container.components[i] is ItemContainer container && + container.Inventory == ParentInventory) + { + containerIndex = (byte)i; + break; + } + } + } + msg.Write(containerIndex); + + int slotIndex = ParentInventory.FindIndex(this); + msg.Write(slotIndex < 0 ? (byte)255 : (byte)slotIndex); } byte teamID = 0; @@ -1698,10 +1715,12 @@ namespace Barotrauma Vector2 pos = Vector2.Zero; Submarine sub = null; + int itemContainerIndex = -1; int inventorySlotIndex = -1; if (inventoryId > 0) { + itemContainerIndex = msg.ReadByte(); inventorySlotIndex = msg.ReadByte(); } else @@ -1741,10 +1760,9 @@ namespace Barotrauma } else if (inventoryOwner is Item) { - var containers = (inventoryOwner as Item).GetComponents(); - if (containers != null && containers.Any()) + if ((inventoryOwner as Item).components[itemContainerIndex] is ItemContainer container) { - inventory = containers.Last().Inventory; + inventory = container.Inventory; } } } diff --git a/Barotrauma/BarotraumaShared/Source/Networking/EntitySpawner.cs b/Barotrauma/BarotraumaShared/Source/Networking/EntitySpawner.cs index 14bd2ad22..4dc229269 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/EntitySpawner.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/EntitySpawner.cs @@ -49,7 +49,6 @@ namespace Barotrauma public Entity Spawn() { Item spawnedItem = null; - if (Inventory != null) { spawnedItem = new Item(Prefab, Vector2.Zero, null); @@ -59,8 +58,6 @@ namespace Barotrauma { spawnedItem = new Item(Prefab, Position, Submarine); } - spawnedItem.Condition = Condition; - return spawnedItem; } } @@ -159,6 +156,10 @@ namespace Barotrauma if (spawnedEntity != null) { CreateNetworkEvent(spawnedEntity, false); + if (spawnedEntity is Item) + { + ((Item)spawnedEntity).Condition = ((ItemSpawnInfo)entitySpawnInfo).Condition; + } } } From 7a7c92a2ce558df99c3a58f1b8e259e6a9e236c8 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 9 Aug 2018 12:39:14 +0300 Subject: [PATCH 151/198] Server writes item condition at full accuracy instead of just using 8 bits. The more inaccurate condition caused issues when the value was just above some specific threshold (e.g. minimum condition for a fabricator ingredient) server-side and below it client-side. Closes #616 --- Barotrauma/BarotraumaClient/Source/Items/Item.cs | 3 +-- Barotrauma/BarotraumaShared/Source/Items/Item.cs | 5 +---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Item.cs b/Barotrauma/BarotraumaClient/Source/Items/Item.cs index 05c5f7a0b..3ef3f3a53 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Item.cs @@ -381,8 +381,7 @@ namespace Barotrauma (components[containerIndex] as ItemContainer).Inventory.ClientRead(type, msg, sendingTime); break; case NetEntityEvent.Type.Status: - condition = msg.ReadRangedSingle(0.0f, prefab.Health, 8); - + condition = msg.ReadSingle(); if (FixRequirements.Count > 0) { if (Condition <= 0.0f) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index bd0b18f85..ca526c008 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -1375,10 +1375,7 @@ namespace Barotrauma (components[containerIndex] as ItemContainer).Inventory.ServerWrite(msg, c); break; case NetEntityEvent.Type.Status: - //clamp to (MaxHealth / 255.0f) if condition > 0.0f - //to prevent condition from being rounded down to 0.0 even if the item is not broken - msg.WriteRangedSingle(condition > 0.0f ? Math.Max(condition, prefab.Health / 255.0f) : 0.0f, 0.0f, prefab.Health, 8); - + msg.Write(condition); if (condition <= 0.0f && FixRequirements.Count > 0) { for (int i = 0; i < FixRequirements.Count; i++) From f697ec7582f791d4b17a3ab369597f6d2b553470 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 9 Aug 2018 12:55:52 +0300 Subject: [PATCH 152/198] Melee weapons don't stop the swing when they hit the first target (but only damage each target once per swing). Fixes weapons not hitting the target in tight spaces due to touching the ceiling/walls first, and allows hitting multiple characters with one swing. Closes #622 --- .../Items/Components/Holdable/MeleeWeapon.cs | 40 ++++++++----------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/MeleeWeapon.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/MeleeWeapon.cs index 6b339760c..ce3ee08c8 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/MeleeWeapon.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/MeleeWeapon.cs @@ -3,6 +3,7 @@ using FarseerPhysics.Dynamics; using FarseerPhysics.Dynamics.Contacts; using Microsoft.Xna.Framework; using System; +using System.Collections.Generic; using System.Linq; using System.Xml.Linq; @@ -24,6 +25,8 @@ namespace Barotrauma.Items.Components private float reloadTimer; + private HashSet hitTargets = new HashSet(); + [Serialize(0.0f, false)] public float Range { @@ -94,6 +97,7 @@ namespace Barotrauma.Items.Components } hitting = true; + hitTargets.Clear(); IsActive = true; return false; @@ -131,8 +135,7 @@ namespace Barotrauma.Items.Components { if (picker.IsKeyDown(InputType.Aim)) { - hitPos = Math.Min(hitPos+deltaTime*5.0f, MathHelper.Pi*0.7f); - + hitPos = Math.Min(hitPos + deltaTime * 5.0f, MathHelper.Pi * 0.7f); ac.HoldItem(deltaTime, item, handlePos, new Vector2(0.6f, -0.1f), new Vector2(-0.3f, 0.2f), false, hitPos); } else @@ -142,28 +145,13 @@ namespace Barotrauma.Items.Components } else { - //Vector2 diff = Vector2.Normalize(picker.CursorPosition - ac.RefLimb.Position); - //diff.X = diff.X * ac.Dir; - - hitPos -= deltaTime*15.0f; - - //angl = -hitPos * 2.0f; - // System.Diagnostics.Debug.WriteLine("<1.0f "+hitPos); - - - + hitPos -= deltaTime * 15.0f; ac.HoldItem(deltaTime, item, handlePos, new Vector2(0.6f, -0.1f), new Vector2(-0.3f, 0.2f), false, hitPos); - //} - //else - //{ - // System.Diagnostics.Debug.WriteLine(">1.0f " + hitPos); - // ac.HoldItem(deltaTime, item, handlePos, new Vector2(0.5f, 0.2f), new Vector2(1.0f, 0.2f), false, 0.0f); - //} - - if (hitPos < -MathHelper.PiOver4*1.2f) + if (hitPos < -MathHelper.PiOver4 * 1.2f) { RestoreCollision(); hitting = false; + hitTargets.Clear(); } } } @@ -230,15 +218,22 @@ namespace Barotrauma.Items.Components targetLimb = (Limb)f2.Body.UserData; if (targetLimb.IsSevered || targetLimb.character == null) return false; targetCharacter = targetLimb.character; + + if (hitTargets.Contains(targetCharacter)) return false; + hitTargets.Add(targetCharacter); } else if (f2.Body.UserData is Character) { targetCharacter = (Character)f2.Body.UserData; targetLimb = targetCharacter.AnimController.GetLimb(LimbType.Torso); //Otherwise armor can be bypassed in strange ways + if (hitTargets.Contains(targetCharacter)) return false; + hitTargets.Add(targetCharacter); } else if (f2.Body.UserData is Structure) { targetStructure = (Structure)f2.Body.UserData; + if (hitTargets.Contains(targetStructure)) return false; + hitTargets.Add(targetStructure); } else { @@ -266,10 +261,7 @@ namespace Barotrauma.Items.Components return false; } } - - RestoreCollision(); - hitting = false; - + if (GameMain.Client != null) return true; if (GameMain.Server != null && targetCharacter != null) //TODO: Log structure hits From f99bec4ed2bbe2961e0086687f830d41e70d0d98 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 9 Aug 2018 14:13:08 +0300 Subject: [PATCH 153/198] If the distance is excessively large when forcing the collider of a dead character to follow the body, the collider is teleported instead of moving it by setting the velocity. + More physics error logging --- .../Animation/FishAnimController.cs | 12 +++++-- .../Animation/HumanoidAnimController.cs | 36 ++++++++++++++++--- .../Source/Items/Components/ItemContainer.cs | 8 ++--- 3 files changed, 45 insertions(+), 11 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/FishAnimController.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/FishAnimController.cs index 0d67b09f8..2878f3715 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/FishAnimController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/FishAnimController.cs @@ -64,8 +64,16 @@ namespace Barotrauma } else { - Collider.LinearVelocity = (MainLimb.SimPosition - Collider.SimPosition) * 60.0f; - Collider.SmoothRotate(MainLimb.Rotation); + Vector2 diff = (MainLimb.SimPosition - Collider.SimPosition); + if (diff.LengthSquared() > 10.0f * 10.0f) + { + Collider.SetTransform(MainLimb.SimPosition, MainLimb.Rotation); + } + else + { + Collider.LinearVelocity = diff * 60.0f; + Collider.SmoothRotate(MainLimb.Rotation); + } } if (character.IsDead && deathAnimTimer < deathAnimDuration) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs index a83e895c0..5d2842fbb 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs @@ -94,14 +94,22 @@ namespace Barotrauma { levitatingCollider = false; Collider.FarseerBody.FixedRotation = false; - - if (Math.Abs(Collider.Rotation-GetLimb(LimbType.Torso).Rotation)>Math.PI*0.6f) + + if (Math.Abs(Collider.Rotation - GetLimb(LimbType.Torso).Rotation) > Math.PI * 0.6f) { Collider.SetTransform(Collider.SimPosition, MathHelper.WrapAngle(Collider.Rotation + (float)Math.PI)); } - Collider.SmoothRotate(GetLimb(LimbType.Torso).Rotation); - Collider.LinearVelocity = (GetLimb(LimbType.Waist).SimPosition - Collider.SimPosition) * 20.0f; - + + Vector2 diff = GetLimb(LimbType.Waist).SimPosition - Collider.SimPosition; + if (diff.LengthSquared() > 10.0f * 10.0f) + { + Collider.SetTransform(GetLimb(LimbType.Waist).SimPosition, GetLimb(LimbType.Torso).Rotation); + } + else + { + Collider.LinearVelocity = diff * 20.0f; + Collider.SmoothRotate(GetLimb(LimbType.Torso).Rotation); + } return; } @@ -1243,6 +1251,24 @@ namespace Barotrauma Vector2 currItemPos = (character.SelectedItems[0] == item) ? rightHand.pullJoint.WorldAnchorA - transformedHandlePos[0] : leftHand.pullJoint.WorldAnchorA - transformedHandlePos[1]; + + if (!MathUtils.IsValid(currItemPos)) + { + string errorMsg = "Attempted to move the item \"" + item + "\" to an invalid position in HumanidAnimController.HoldItem: " + + currItemPos + ", rightHandPos: " + rightHand.pullJoint.WorldAnchorA + ", leftHandPos: " + leftHand.pullJoint.WorldAnchorA + + ", handlePos[0]: " + handlePos[0] + ", handlePos[1]: " + handlePos[1] + + ", transformedHandlePos[0]: " + transformedHandlePos[0] + ", transformedHandlePos[1]:" + transformedHandlePos[1] + + ", item pos: " + item.SimPosition + ", itemAngle: " + itemAngle + + ", collider pos: " + character.SimPosition; + DebugConsole.Log(errorMsg); + GameAnalyticsManager.AddErrorEventOnce( + "HumanoidAnimController.HoldItem:InvalidPos:" + character.Name + item.Name, + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + errorMsg); + + return; + } + item.SetTransform(currItemPos, itemAngle); //item.SetTransform(MathUtils.SmoothStep(item.body.SimPosition, transformedHoldPos + bodyVelocity, 0.5f), itemAngle); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemContainer.cs index 416082a19..456315b20 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemContainer.cs @@ -202,7 +202,6 @@ namespace Barotrauma.Items.Components foreach (Item contained in Inventory.Items) { if (contained == null) continue; - if (contained.body != null) { try @@ -211,9 +210,10 @@ namespace Barotrauma.Items.Components } catch (Exception e) { -#if DEBUG - DebugConsole.ThrowError("SetTransformIgnoreContacts threw an exception in SetContainedItemPositions", e); -#endif + DebugConsole.Log("SetTransformIgnoreContacts threw an exception in SetContainedItemPositions ("+e.Message+")\n"+e.StackTrace); + GameAnalyticsManager.AddErrorEventOnce("ItemContainer.SetContainedItemPositions.InvalidPosition:"+contained.Name, + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "SetTransformIgnoreContacts threw an exception in SetContainedItemPositions (" + e.Message + ")\n" + e.StackTrace); } } From d98de2c305baf9d7f8745c1ca04cac0e37f4d00f Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Sat, 11 Aug 2018 17:09:36 +0300 Subject: [PATCH 154/198] Fire probability and the voltage required for a PowerTransfer item to take damage can be configured in the xml. Docking ports & hatches don't get broken by excess voltage. Closes #589 --- .../Content/Items/Door/doors.xml | 27 ++++----------- .../Items/Components/Power/PowerTransfer.cs | 34 ++++++++++++++++--- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Content/Items/Door/doors.xml b/Barotrauma/BarotraumaShared/Content/Items/Door/doors.xml index 19a10b0c9..a05efebf0 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Door/doors.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Door/doors.xml @@ -103,8 +103,7 @@ + linkable="true"> @@ -116,14 +115,8 @@ - - - - - - - - + + @@ -143,8 +136,7 @@ + linkable="true"> @@ -157,13 +149,7 @@ - - - - - - - + @@ -183,8 +169,7 @@ + linkable="true"> diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs index 415a15ab7..243276562 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs @@ -11,9 +11,7 @@ namespace Barotrauma.Items.Components static float fullLoad; private int updateCount; - - const float FireProbability = 0.15f; - + //affects how fast changes in power/load are carried over the grid static float inertia = 5.0f; @@ -42,6 +40,29 @@ namespace Barotrauma.Items.Components get { return powerLoad; } } + [Serialize(true, true), Editable(ToolTip = "Can the item be damaged if too much power is supplied to the power grid.")] + public bool CanBeOverloaded + { + get; + set; + } + + [Serialize(2.0f, true), Editable(MinValueFloat = 1.0f, ToolTip = + "How much power has to be supplied to the grid relative to the load before item starts taking damage. " + +"E.g. a value of 2 means that the grid has to be receiving twice as much power as the devices in the grid are consuming.")] + public float OverloadVoltage + { + get; + set; + } + + [Serialize(0.15f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f, ToolTip = "The probability for a fire to start when the item breaks.")] + public float FireProbability + { + get; + set; + } + //can the component transfer power private bool canTransfer; public bool CanTransfer @@ -147,10 +168,13 @@ namespace Barotrauma.Items.Components //(except if running as a client) if (GameMain.Client != null) continue; + //if the item can't be fixed, don't allow it to break + if (item.FixRequirements.Count == 0 || !CanBeOverloaded) continue; + //relays don't blow up if the power is higher than load, only if the output is high enough //(i.e. enough power passing through the relay) if (this is RelayComponent) continue; - if (-pt.currPowerConsumption < Math.Max(pt.powerLoad * Rand.Range(1.9f, 2.1f), 200.0f)) continue; + if (-pt.currPowerConsumption < Math.Max(pt.powerLoad, 200.0f) * OverloadVoltage) continue; float prevCondition = pt.item.Condition; pt.item.Condition -= deltaTime * 10.0f; @@ -170,7 +194,7 @@ namespace Barotrauma.Items.Components } #endif - if (FireProbability > 0.0f && Rand.Int((int)(1.0f / FireProbability)) == 1) + if (FireProbability > 0.0f && FireProbability < Rand.Range(0.0f, 1.0f)) { new FireSource(pt.item.WorldPosition); } From 091dcf35e6fc79d0a0a72d8b21a5a75a677b547c Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Sat, 11 Aug 2018 17:29:03 +0300 Subject: [PATCH 155/198] Server ignores kick votes if the client has already voted to kick the same player. Closes #615 --- Barotrauma/BarotraumaShared/Source/Networking/Client.cs | 5 +++++ Barotrauma/BarotraumaShared/Source/Networking/Voting.cs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/Source/Networking/Client.cs b/Barotrauma/BarotraumaShared/Source/Networking/Client.cs index f190311fb..c77cd7ca2 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/Client.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/Client.cs @@ -211,6 +211,11 @@ namespace Barotrauma.Networking { kickVoters.Remove(voter); } + + public bool HasKickVoteFrom(Client voter) + { + return kickVoters.Contains(voter); + } public bool HasKickVoteFromID(int id) { diff --git a/Barotrauma/BarotraumaShared/Source/Networking/Voting.cs b/Barotrauma/BarotraumaShared/Source/Networking/Voting.cs index 300198353..3d2b98a25 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/Voting.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/Voting.cs @@ -122,7 +122,7 @@ namespace Barotrauma byte kickedClientID = inc.ReadByte(); Client kicked = GameMain.Server.ConnectedClients.Find(c => c.ID == kickedClientID); - if (kicked != null) + if (kicked != null && !kicked.HasKickVoteFrom(sender)) { kicked.AddKickVote(sender); Client.UpdateKickVotes(GameMain.Server.ConnectedClients); From 1f5239b14137f780bba6df57c31f85e3972ebb72 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 13 Aug 2018 14:55:26 +0300 Subject: [PATCH 156/198] Horizontally resizeable walls are considered horizontal regardless of their size (and the same for vertical ones). Fixes orientation of short but thick walls (e.g. a tiny piece of a heavy wall) being wrong. Closes #648 --- .../BarotraumaShared/Source/Map/Structure.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Map/Structure.cs b/Barotrauma/BarotraumaShared/Source/Map/Structure.cs index 69974ebb2..2c6b073bd 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Structure.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Structure.cs @@ -226,8 +226,18 @@ namespace Barotrauma prefab = sp; spriteColor = prefab.SpriteColor; - - isHorizontal = (rect.Width > rect.Height); + if (ResizeHorizontal && !ResizeVertical) + { + isHorizontal = true; + } + else if (ResizeVertical && !ResizeHorizontal) + { + isHorizontal = false; + } + else + { + isHorizontal = (rect.Width > rect.Height); + } StairDirection = prefab.StairDirection; From de05c7f047b5596da152a16ce150617927906b95 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 13 Aug 2018 17:05:25 +0300 Subject: [PATCH 157/198] More wire color variants. Closes #660 --- .../Content/Items/Electricity/signalitems.xml | 50 +++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Content/Items/Electricity/signalitems.xml b/Barotrauma/BarotraumaShared/Content/Items/Electricity/signalitems.xml index 58f1a40c6..70fd4b527 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Electricity/signalitems.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Electricity/signalitems.xml @@ -28,7 +28,7 @@ name="Red Wire" category="Electrical" Tags="smallitem,wire" - spritecolor="1.0,0.0,0.0,1.0" + spritecolor="0.7,0.06,0.06,1.0" linkable="true" canbepicked="true" cargocontainername="Metal Crate" @@ -50,7 +50,7 @@ name="Blue Wire" category="Electrical" Tags="smallitem,wire" - spritecolor="0.0,0.6,1.0,1.0" + spritecolor="0.1,0.4,0.9,1.0" linkable="true" canbepicked="true" cargocontainername="Metal Crate" @@ -72,7 +72,7 @@ name="Orange Wire" category="Electrical" Tags="smallitem,wire" - spritecolor="1.0,0.5,0.0,1.0" + spritecolor="0.9,0.58,0.07,1.0" linkable="true" canbepicked="true" cargocontainername="Metal Crate" @@ -90,6 +90,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Date: Mon, 13 Aug 2018 17:05:51 +0300 Subject: [PATCH 158/198] Characters point the harpoon gun downwards when not aiming. Closes #661 --- 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 ef19e463e..aa495d3fe 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Weapons/weapons.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Weapons/weapons.xml @@ -40,7 +40,7 @@ + holdpos="35,-10" aimpos="35,-10" handle1="-15,-6" handle2="26,7" holdangle="-40"/> From 8d5e5a8a15e2a75e52d61be880382822ea020f3d Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 13 Aug 2018 17:14:49 +0300 Subject: [PATCH 159/198] Fixed characters being able to aim with items that have no aimPos configured (causing them to hold two-handed items like shells an crates in one hand when holding the aim button). Closes #613 --- .../Source/Items/Components/Holdable/Holdable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs index 5a8e33867..e230315e1 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs @@ -358,7 +358,7 @@ namespace Barotrauma.Items.Components if (picker.HasSelectedItem(item)) { - picker.AnimController.HoldItem(deltaTime, item, handlePos, holdPos, aimPos, picker.IsKeyDown(InputType.Aim), holdAngle); + picker.AnimController.HoldItem(deltaTime, item, handlePos, holdPos, aimPos, picker.IsKeyDown(InputType.Aim) && aimPos != Vector2.Zero, holdAngle); } else { From 6cb6e26b94c09cde89e6339827add382dd6e8401 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 14 Aug 2018 12:01:56 +0300 Subject: [PATCH 160/198] Servers can modify all Editable item properties using ChangeProperty events (not just InGameEditable ones). Closes #675 --- Barotrauma/BarotraumaClient/Source/Items/Item.cs | 4 ++-- Barotrauma/BarotraumaShared/Source/Items/Item.cs | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Item.cs b/Barotrauma/BarotraumaClient/Source/Items/Item.cs index 3ef3f3a53..d77fe94e6 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Item.cs @@ -405,7 +405,7 @@ namespace Barotrauma ApplyStatusEffects(actionType, 1.0f, target, true); break; case NetEntityEvent.Type.ChangeProperty: - ReadPropertyChange(msg); + ReadPropertyChange(msg, false); break; case NetEntityEvent.Type.Invalid: break; @@ -445,7 +445,7 @@ namespace Barotrauma //on the character of the client who sent the message break; case NetEntityEvent.Type.ChangeProperty: - WritePropertyChange(msg, extraData); + WritePropertyChange(msg, extraData, true); break; } msg.WritePadBits(); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index ca526c008..76dc69276 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -1392,7 +1392,7 @@ namespace Barotrauma case NetEntityEvent.Type.ChangeProperty: try { - WritePropertyChange(msg, extraData); + WritePropertyChange(msg, extraData, false); } catch (Exception e) { @@ -1471,14 +1471,14 @@ namespace Barotrauma break; case NetEntityEvent.Type.ChangeProperty: - ReadPropertyChange(msg); + ReadPropertyChange(msg, true); break; } } - private void WritePropertyChange(NetBuffer msg, object[] extraData) + private void WritePropertyChange(NetBuffer msg, object[] extraData, bool inGameEditableOnly) { - var allProperties = GetProperties(); + var allProperties = inGameEditableOnly ? GetProperties() : GetProperties(); SerializableProperty property = extraData[1] as SerializableProperty; if (property != null) { @@ -1552,9 +1552,9 @@ namespace Barotrauma } } - private void ReadPropertyChange(NetBuffer msg) + private void ReadPropertyChange(NetBuffer msg, bool inGameEditableOnly) { - var allProperties = GetProperties(); + var allProperties = inGameEditableOnly ? GetProperties() : GetProperties(); if (allProperties.Count == 0) return; int propertyIndex = 0; From 8160abe10909caada1190692337cbc923b972d56 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 14 Aug 2018 13:28:11 +0300 Subject: [PATCH 161/198] Fixed nullref exceptions when loading a steering component when a gamesession is running but no locations have been selected (not sure how that can even happen) --- .../Source/Items/Components/Machines/Steering.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Steering.cs index 504454253..353346a02 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Steering.cs @@ -46,14 +46,14 @@ namespace Barotrauma.Items.Components levelStartTickBox = new GUITickBox( new Rectangle(5, 70, 15, 15), - GameMain.GameSession == null ? "" : ToolBox.LimitString(GameMain.GameSession.StartLocation.Name, 20), + GameMain.GameSession?.StartLocation == null ? "" : ToolBox.LimitString(GameMain.GameSession.StartLocation.Name, 20), Alignment.TopLeft, GUI.SmallFont, GuiFrame); levelStartTickBox.Enabled = false; levelStartTickBox.OnSelected = SelectDestination; levelEndTickBox = new GUITickBox( new Rectangle(5, 90, 15, 15), - GameMain.GameSession == null ? "" : ToolBox.LimitString(GameMain.GameSession.EndLocation.Name, 20), + GameMain.GameSession?.EndLocation == null ? "" : ToolBox.LimitString(GameMain.GameSession.EndLocation.Name, 20), Alignment.TopLeft, GUI.SmallFont, GuiFrame); levelEndTickBox.Enabled = false; levelEndTickBox.OnSelected = SelectDestination; From 8233f3b8df7cfdd4373f2dd7b86aaf4257305fd9 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 14 Aug 2018 13:28:56 +0300 Subject: [PATCH 162/198] Increased minimum midround sync timeout to 10 s, an extra check to make sure the server doesn't write position updates during midround syncing (because the entities may not exist client-side yet) --- Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs | 5 +++-- .../Networking/NetEntityEvent/ServerEntityEventManager.cs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs index 490e1bfea..cc51749cb 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs @@ -963,8 +963,9 @@ namespace Barotrauma.Networking WriteChatMessages(outmsg, c); - //write as many position updates as the message can fit - while (outmsg.LengthBytes < config.MaximumTransmissionUnit - 20 && + //write as many position updates as the message can fit (only after midround syncing is done) + while (!c.NeedsMidRoundSync && + outmsg.LengthBytes < config.MaximumTransmissionUnit - 20 && c.PendingPositionUpdates.Count > 0) { var entity = c.PendingPositionUpdates.Dequeue(); diff --git a/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/ServerEntityEventManager.cs b/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/ServerEntityEventManager.cs index 648b28c43..69413017a 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/ServerEntityEventManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/ServerEntityEventManager.cs @@ -305,7 +305,7 @@ namespace Barotrauma.Networking else { double midRoundSyncTimeOut = uniqueEvents.Count / MaxEventsPerWrite * server.UpdateInterval.TotalSeconds; - midRoundSyncTimeOut = Math.Max(5.0f, midRoundSyncTimeOut * 2.0f); + midRoundSyncTimeOut = Math.Max(10.0f, midRoundSyncTimeOut * 2.0f); client.UnreceivedEntityEventCount = (UInt16)uniqueEvents.Count; client.FirstNewEventID = 0; From 1e02d7406d4f7d8049bb68953986fbde4d546509 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 14 Aug 2018 15:39:01 +0300 Subject: [PATCH 163/198] Added a collider to railgun (-> cannot go through walls or enemy subs anymore). Closes #444 --- .../Content/Items/Weapons/railgun.xml | 2 ++ .../BarotraumaShared/Source/Items/Item.cs | 5 +++ .../Source/Map/SubmarineBody.cs | 34 +++++++++++++++++-- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Content/Items/Weapons/railgun.xml b/Barotrauma/BarotraumaShared/Content/Items/Weapons/railgun.xml index 11f441901..473711a84 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Weapons/railgun.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Weapons/railgun.xml @@ -8,6 +8,8 @@ + + drawableComponents; public PhysicsBody body; + + public readonly XElement staticBodyConfig; private Vector2 lastSentPos; private bool prevBodyAwake; @@ -399,6 +401,9 @@ namespace Barotrauma case "deconstruct": case "brokensprite": break; + case "staticbody": + staticBodyConfig = subElement; + break; case "aitarget": aiTarget = new AITarget(this); aiTarget.SightRange = subElement.GetAttributeFloat("sightrange", 1000.0f); diff --git a/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs b/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs index 89de82b74..48d4d8628 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs @@ -128,10 +128,38 @@ namespace Barotrauma ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2)), farseerBody, this); } + + foreach (Item item in Item.ItemList) + { + if (item.staticBodyConfig == null) continue; + + float radius = ConvertUnits.ToSimUnits(item.staticBodyConfig.GetAttributeFloat("radius", 0.0f)); + float width = ConvertUnits.ToSimUnits(item.staticBodyConfig.GetAttributeFloat("width", 0.0f)); + float height = ConvertUnits.ToSimUnits(item.staticBodyConfig.GetAttributeFloat("height", 0.0f)); + + if (width != 0.0f && height != 0.0f) + { + FixtureFactory.AttachRectangle(width, height, 5.0f, ConvertUnits.ToSimUnits(item.Position), farseerBody, this); + } + else if (radius != 0.0f && width != 0.0f) + { + FixtureFactory.AttachRectangle(width, radius * 2, 5.0f, ConvertUnits.ToSimUnits(item.Position), farseerBody, this); + FixtureFactory.AttachCircle(radius, 5.0f, farseerBody, ConvertUnits.ToSimUnits(item.Position) - Vector2.UnitX * width / 2, this); + FixtureFactory.AttachCircle(radius, 5.0f, farseerBody, ConvertUnits.ToSimUnits(item.Position) + Vector2.UnitX * width / 2, this); + } + else if (radius != 0.0f && height != 0.0f) + { + FixtureFactory.AttachRectangle(radius * 2, height, 5.0f, ConvertUnits.ToSimUnits(item.Position), farseerBody, this); + FixtureFactory.AttachCircle(radius, 5.0f, farseerBody, ConvertUnits.ToSimUnits(item.Position) - Vector2.UnitY * height / 2, this); + FixtureFactory.AttachCircle(radius, 5.0f, farseerBody, ConvertUnits.ToSimUnits(item.Position) + Vector2.UnitX * height / 2, this); + } + else if (radius != 0.0f) + { + FixtureFactory.AttachCircle(radius, 5.0f, farseerBody, ConvertUnits.ToSimUnits(item.Position), this); + } + } } - - - + farseerBody.BodyType = BodyType.Dynamic; farseerBody.CollisionCategories = Physics.CollisionWall; farseerBody.CollidesWith = From 4866a8925327a04a8655a8ebb106905ae6ae8097 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 14 Aug 2018 16:06:23 +0300 Subject: [PATCH 164/198] Servers can disable the disguise feature. Closes #568 --- .../BarotraumaClient/Source/Networking/GameClient.cs | 9 +++++++++ .../BarotraumaShared/Source/Characters/Character.cs | 4 ++++ .../BarotraumaShared/Source/Characters/CharacterInfo.cs | 9 +++++++-- .../BarotraumaShared/Source/Networking/GameServer.cs | 2 ++ .../Source/Networking/GameServerSettings.cs | 7 +++++++ 5 files changed, 29 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs index 20038608c..cdfcd3de6 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs @@ -69,6 +69,12 @@ namespace Barotrauma.Networking { get { return entityEventManager.MidRoundSyncing; } } + + public bool AllowDisguises + { + get; + private set; + } public GameClient(string newName) { @@ -705,6 +711,7 @@ namespace Barotrauma.Networking bool respawnAllowed = inc.ReadBoolean(); bool loadSecondSub = inc.ReadBoolean(); + bool disguisesAllowed = inc.ReadBoolean(); bool isTraitor = inc.ReadBoolean(); string traitorTargetName = isTraitor ? inc.ReadString() : null; @@ -743,6 +750,8 @@ namespace Barotrauma.Networking GameMain.NetLobbyScreen.UsingShuttle = usingShuttle; + AllowDisguises = disguisesAllowed; + if (campaign == null) { if (!GameMain.NetLobbyScreen.TrySelectSub(subName, subHash, GameMain.NetLobbyScreen.SubList)) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs index 4ba534488..2448c2439 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs @@ -144,6 +144,10 @@ namespace Barotrauma { get { + if (GameMain.Server != null && !GameMain.Server.AllowDisguises) return Name; +#if CLIENT + if (GameMain.Client != null && !GameMain.Client.AllowDisguises) return Name; +#endif return info != null && !string.IsNullOrWhiteSpace(info.Name) ? info.Name + (info.DisplayName != info.Name ? " (as " + info.DisplayName + ")" : "") : SpeciesName; } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/Source/Characters/CharacterInfo.cs index ff85a049e..23898e360 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/CharacterInfo.cs @@ -17,11 +17,16 @@ namespace Barotrauma get { string disguiseName = "?"; - if (Character == null || !Character.HideFace) + if (Character == null || !Character.HideFace || (GameMain.Server != null && !GameMain.Server.AllowDisguises)) { return Name; } - +#if CLIENT + if (GameMain.Client != null && !GameMain.Client.AllowDisguises) + { + return Name; + } +#endif if (Character.Inventory != null) { int cardSlotIndex = Character.Inventory.FindLimbSlot(InvSlotType.Card); diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs index cc51749cb..f10433eba 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs @@ -1456,6 +1456,8 @@ namespace Barotrauma.Networking msg.Write(AllowRespawn && missionAllowRespawn); msg.Write(Submarine.MainSubs[1] != null); //loadSecondSub + msg.Write(AllowDisguises); + Traitor traitor = null; if (TraitorManager != null && TraitorManager.TraitorList.Count > 0) traitor = TraitorManager.TraitorList.Find(t => t.Character == client.Character); diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs index 3e3708352..ae3c37d1c 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs @@ -177,6 +177,13 @@ namespace Barotrauma.Networking set; } + [Serialize(true, true)] + public bool AllowDisguises + { + get; + set; + } + public YesNoMaybe TraitorsEnabled { get; From fe8a22a4f89f6916853b75c375b2be8baab5a081 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 15 Aug 2018 11:46:43 +0300 Subject: [PATCH 165/198] Characters can't hold things in severed hands, limb bodies are re-enabled when reviving a character. Closes #529 --- .../Characters/Animation/HumanoidAnimController.cs | 14 +++++++++----- .../Source/Characters/Character.cs | 1 + .../BarotraumaShared/Source/Characters/Limb.cs | 3 ++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs index 5d2842fbb..04875d727 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs @@ -1220,25 +1220,29 @@ namespace Barotrauma if (itemPos == Vector2.Zero || Anim == Animation.Climbing || usingController) { - if (character.SelectedItems[1] == item) - { - transformedHoldPos = leftHand.pullJoint.WorldAnchorA - transformedHandlePos[1]; - itemAngle = (leftHand.Rotation + (holdAngle - MathHelper.PiOver2) * Dir); - } if (character.SelectedItems[0] == item) { + if (rightHand.IsSevered) return; transformedHoldPos = rightHand.pullJoint.WorldAnchorA - transformedHandlePos[0]; itemAngle = (rightHand.Rotation + (holdAngle - MathHelper.PiOver2) * Dir); } + if (character.SelectedItems[1] == item) + { + if (leftHand.IsSevered) return; + transformedHoldPos = leftHand.pullJoint.WorldAnchorA - transformedHandlePos[1]; + itemAngle = (leftHand.Rotation + (holdAngle - MathHelper.PiOver2) * Dir); + } } else { if (character.SelectedItems[0] == item) { + if (rightHand.IsSevered) return; rightHand.Disabled = true; } if (character.SelectedItems[1] == item) { + if (leftHand.IsSevered) return; leftHand.Disabled = true; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs index 2448c2439..b2ac6999c 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs @@ -2022,6 +2022,7 @@ namespace Barotrauma foreach (Limb limb in AnimController.Limbs) { + limb.body.Enabled = true; limb.IsSevered = false; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Limb.cs b/Barotrauma/BarotraumaShared/Source/Characters/Limb.cs index 4507569c1..9893615e0 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Limb.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Limb.cs @@ -85,9 +85,10 @@ namespace Barotrauma set { isSevered = value; + if (!isSevered) severedFadeOutTimer = 0.0f; #if CLIENT if (isSevered) damage = 100.0f; -#endif +#endif } } From e513b379f407fafef5280abd15791a92d71523b4 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 15 Aug 2018 11:46:54 +0300 Subject: [PATCH 166/198] Added parameter autocompletion to the kill command --- Barotrauma/BarotraumaShared/Source/DebugConsole.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs index ebeaf4076..a795bc948 100644 --- a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs @@ -1513,6 +1513,13 @@ namespace Barotrauma { Character killedCharacter = (args.Length == 0) ? client.Character : FindMatchingCharacter(args); killedCharacter?.AddDamage(CauseOfDeath.Damage, killedCharacter.MaxHealth * 2, null); + }, + () => + { + return new string[][] + { + Character.CharacterList.Select(c => c.Name).Distinct().ToArray() + }; })); commands.Add(new Command("killmonsters", "killmonsters: Immediately kills all AI-controlled enemies in the level.", (string[] args) => From b1f8de887c254b77145bf1d511dc5c91407d0cdb Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 15 Aug 2018 11:52:48 +0300 Subject: [PATCH 167/198] Clients use the Revive method instead of just setting health above zero when the host revives a character. Fixes dismembered characters staying dismembered client-side. --- .../BarotraumaClient/Source/Characters/CharacterNetworking.cs | 2 +- Barotrauma/BarotraumaShared/Source/Characters/Character.cs | 2 +- Barotrauma/BarotraumaShared/Source/DebugConsole.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs index 877bf6414..2dc31ab59 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs @@ -344,7 +344,7 @@ namespace Barotrauma } else { - this.isDead = false; + if (this.isDead) Revive(); health = msg.ReadRangedSingle(minHealth, maxHealth, 8); diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs index b2ac6999c..7eb02a887 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs @@ -1995,7 +1995,7 @@ namespace Barotrauma } partial void KillProjSpecific(); - public void Revive(bool isNetworkMessage) + public void Revive() { if (Removed) { diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs index a795bc948..ef3bf9cc6 100644 --- a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs @@ -1334,7 +1334,7 @@ namespace Barotrauma Character revivedCharacter = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args); if (revivedCharacter == null) return; - revivedCharacter.Revive(false); + revivedCharacter.Revive(); if (GameMain.Server != null) { foreach (Client c in GameMain.Server.ConnectedClients) @@ -1353,7 +1353,7 @@ namespace Barotrauma Character revivedCharacter = (args.Length == 0) ? client.Character : FindMatchingCharacter(args); if (revivedCharacter == null) return; - revivedCharacter.Revive(false); + revivedCharacter.Revive(); if (GameMain.Server != null) { foreach (Client c in GameMain.Server.ConnectedClients) From c2f9e1481f304ccbe33e20d490af9c97acefdaa8 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 15 Aug 2018 12:06:22 +0300 Subject: [PATCH 168/198] Recommended crew experience is saved using text tags instead of the actual text (-> experience texts can be translated without the translation showing up in sub files). --- .../BarotraumaClient/Source/Map/Submarine.cs | 2 +- .../Source/Screens/SubEditorScreen.cs | 24 +++++++++++-------- .../BarotraumaShared/Source/Map/Submarine.cs | 9 +++++++ 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Map/Submarine.cs b/Barotrauma/BarotraumaClient/Source/Map/Submarine.cs index bccc223b4..8202dd7e8 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Submarine.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Submarine.cs @@ -146,7 +146,7 @@ namespace Barotrauma "", frame, GUI.SmallFont); new GUITextBlock(new Rectangle(246, 100, 100, 20), - TextManager.Get("RecommendedCrewExperience") + ": " + (string.IsNullOrEmpty(RecommendedCrewExperience) ? TextManager.Get("unknown") : RecommendedCrewExperience), + TextManager.Get("RecommendedCrewExperience") + ": " + (string.IsNullOrEmpty(RecommendedCrewExperience) ? TextManager.Get("unknown") : TextManager.Get(RecommendedCrewExperience)), "", frame, GUI.SmallFont); new GUITextBlock(new Rectangle(246, 120, 0, 20), diff --git a/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs index b1bbb0131..267d08db7 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs @@ -15,9 +15,9 @@ namespace Barotrauma { private static string[] crewExperienceLevels = new string[] { - TextManager.Get("CrewExperienceLow"), - TextManager.Get("CrewExperienceMid"), - TextManager.Get("CrewExperienceHigh") + "CrewExperienceLow", + "CrewExperienceMid", + "CrewExperienceHigh" }; private Camera cam; @@ -587,23 +587,26 @@ namespace Barotrauma var toggleExpRight = new GUIButton(new Rectangle(350, y, 20, 20), ">", "", saveFrame); var experienceText = new GUITextBlock(new Rectangle(250, y, 100, 20), crewExperienceLevels[0], "", Alignment.TopLeft, Alignment.Center, saveFrame); + toggleExpLeft.OnClicked += (btn, userData) => { - int currentIndex = Array.IndexOf(crewExperienceLevels, experienceText.Text); + int currentIndex = Array.IndexOf(crewExperienceLevels, (string)experienceText.UserData); currentIndex--; if (currentIndex < 0) currentIndex = crewExperienceLevels.Length - 1; - experienceText.Text = crewExperienceLevels[currentIndex]; - Submarine.MainSub.RecommendedCrewExperience = experienceText.Text; + experienceText.UserData = crewExperienceLevels[currentIndex]; + experienceText.Text = TextManager.Get(crewExperienceLevels[currentIndex]); + Submarine.MainSub.RecommendedCrewExperience = (string)experienceText.UserData; return true; }; toggleExpRight.OnClicked += (btn, userData) => { - int currentIndex = Array.IndexOf(crewExperienceLevels, experienceText.Text); + int currentIndex = Array.IndexOf(crewExperienceLevels, (string)experienceText.UserData); currentIndex++; if (currentIndex >= crewExperienceLevels.Length) currentIndex = 0; - experienceText.Text = crewExperienceLevels[currentIndex]; - Submarine.MainSub.RecommendedCrewExperience = experienceText.Text; + experienceText.UserData = crewExperienceLevels[currentIndex]; + experienceText.Text = TextManager.Get(crewExperienceLevels[currentIndex]); + Submarine.MainSub.RecommendedCrewExperience = (string)experienceText.UserData; return true; }; @@ -613,8 +616,9 @@ namespace Barotrauma int max = Submarine.MainSub.RecommendedCrewSizeMax; crewSizeMin.IntValue = min; crewSizeMax.IntValue = max; - experienceText.Text = string.IsNullOrEmpty(Submarine.MainSub.RecommendedCrewExperience) ? + experienceText.UserData = string.IsNullOrEmpty(Submarine.MainSub.RecommendedCrewExperience) ? crewExperienceLevels[0] : Submarine.MainSub.RecommendedCrewExperience; + experienceText.Text = TextManager.Get((string)experienceText.UserData); } var saveButton = new GUIButton(new Rectangle(-90, 0, 80, 20), TextManager.Get("SaveSubButton"), Alignment.Right | Alignment.Bottom, "", saveFrame); diff --git a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs index 40ee9d171..a38edae39 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs @@ -296,6 +296,15 @@ namespace Barotrauma RecommendedCrewSizeMin = doc.Root.GetAttributeInt("recommendedcrewsizemin", 0); RecommendedCrewSizeMax = doc.Root.GetAttributeInt("recommendedcrewsizemax", 0); RecommendedCrewExperience = doc.Root.GetAttributeString("recommendedcrewexperience", "Unknown"); + + //backwards compatibility (use text tags instead of the actual text) + if (RecommendedCrewExperience == "Beginner") + RecommendedCrewExperience = "CrewExperienceLow"; + else if (RecommendedCrewExperience == "Intermediate") + RecommendedCrewExperience = "CrewExperienceMid"; + else if (RecommendedCrewExperience == "Experienced") + RecommendedCrewExperience = "CrewExperienceHigh"; + string[] contentPackageNames = doc.Root.GetAttributeStringArray("compatiblecontentpackages", new string[0]); foreach (string contentPackageName in contentPackageNames) { From 0dca8bc9404fe418475db54067d492bb21219e45 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 15 Aug 2018 12:57:46 +0300 Subject: [PATCH 169/198] Clients can be given access to server logs. Closes #366 --- .../Source/Networking/ChatMessage.cs | 28 +++++--- .../Source/Networking/GameClient.cs | 17 ++++- .../Source/Networking/GameServer.cs | 31 ++------- .../Source/Networking/NetworkMember.cs | 69 ++++++++++++------- .../Source/Screens/NetLobbyScreen.cs | 10 +-- .../Data/permissionpresets.xml | 8 +-- .../Source/Networking/ChatMessage.cs | 2 +- .../Source/Networking/ClientPermissions.cs | 4 +- .../Source/Networking/GameServer.cs | 33 ++++----- .../Source/Networking/GameServerSettings.cs | 4 +- .../Source/Networking/NetworkMember.cs | 6 ++ .../Source/Networking/ServerLog.cs | 12 ++-- 12 files changed, 129 insertions(+), 95 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Networking/ChatMessage.cs b/Barotrauma/BarotraumaClient/Source/Networking/ChatMessage.cs index 75f22a273..4427bd23b 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/ChatMessage.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/ChatMessage.cs @@ -32,18 +32,26 @@ namespace Barotrauma.Networking if (NetIdUtils.IdMoreRecent(ID, LastID)) { - if (type == ChatMessageType.MessageBox) + switch (type) { - new GUIMessageBox("", txt); - } - else if (type == ChatMessageType.Console) - { - DebugConsole.NewMessage(txt, MessageColor[(int)ChatMessageType.Console]); - } - else - { - GameMain.Client.AddChatMessage(txt, type, senderName, senderCharacter); + case ChatMessageType.MessageBox: + new GUIMessageBox("", txt); + break; + case ChatMessageType.Console: + DebugConsole.NewMessage(txt, MessageColor[(int)ChatMessageType.Console]); + break; + case ChatMessageType.ServerLog: + if (!Enum.TryParse(senderName, out ServerLog.MessageType messageType)) + { + return; + } + GameMain.Client.ServerLog?.WriteLine(txt, messageType); + break; + default: + GameMain.Client.AddChatMessage(txt, type, senderName, senderCharacter); + break; } + LastID = ID; } } diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs index cdfcd3de6..85779a7ce 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs @@ -113,6 +113,8 @@ namespace Barotrauma.Networking otherClients = new List(); + ServerLog = new ServerLog(""); + ChatMessage.LastID = 0; GameMain.NetLobbyScreen = new NetLobbyScreen(); } @@ -470,6 +472,9 @@ namespace Barotrauma.Networking #if DEBUG if (PlayerInput.GetKeyboardState.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.P)) return; #endif +#if CLIENT + if (ServerLog.LogFrame != null) ServerLog.LogFrame.Update(deltaTime); +#endif base.Update(deltaTime); @@ -670,9 +675,11 @@ namespace Barotrauma.Networking 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); + GameMain.NetLobbyScreen.InfoFrame.FindChild("showlog").Visible = HasPermission(ClientPermissions.ServerLog); + showLogButton.Visible = HasPermission(ClientPermissions.ServerLog); endRoundButton.Visible = HasPermission(ClientPermissions.EndRound); } @@ -929,6 +936,8 @@ namespace Barotrauma.Networking { GameMain.NetLobbyScreen.LastUpdateID = updateID; + ServerLog.ServerName = serverName; + GameMain.NetLobbyScreen.ServerName = serverName; GameMain.NetLobbyScreen.ServerMessage.Text = serverText; @@ -1398,6 +1407,12 @@ namespace Barotrauma.Networking public override void Disconnect() { client.Shutdown(""); + + if (HasPermission(ClientPermissions.ServerLog)) + { + ServerLog?.Save(); + } + GameMain.NetworkMember = null; } diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameServer.cs index 87919d6b8..2f5048fee 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameServer.cs @@ -9,45 +9,22 @@ namespace Barotrauma.Networking { private NetStats netStats; - private GUIButton showLogButton; - private GUIScrollBar clientListScrollBar; void InitProjSpecific() { - //---------------------------------------- - var endRoundButton = new GUIButton(new Rectangle(GameMain.GraphicsWidth - 170, 20, 150, 20), "End round", Alignment.TopLeft, "", inGameHUD); endRoundButton.OnClicked = (btn, userdata) => { EndGame(); return true; }; - - showLogButton = new GUIButton(new Rectangle(GameMain.GraphicsWidth - 170 - 170, 20, 150, 20), "Server Log", Alignment.TopLeft, "", inGameHUD); - showLogButton.OnClicked = (GUIButton button, object userData) => - { - if (log.LogFrame == null) - { - log.CreateLogFrame(); - } - else - { - log.LogFrame = null; - GUIComponent.KeyboardDispatcher.Subscriber = null; - } - return true; - }; - + GUIButton settingsButton = new GUIButton(new Rectangle(GameMain.GraphicsWidth - 170 - 170 - 170, 20, 150, 20), "Settings", Alignment.TopLeft, "", inGameHUD); settingsButton.OnClicked = ToggleSettingsFrame; settingsButton.UserData = "settingsButton"; - - //---------------------------------------- } public override void AddToGUIUpdateList() { - if (started) base.AddToGUIUpdateList(); - + base.AddToGUIUpdateList(); if (settingsFrame != null) settingsFrame.AddToGUIUpdateList(); - if (log.LogFrame != null) log.LogFrame.AddToGUIUpdateList(); } public override void Draw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch) @@ -58,9 +35,9 @@ namespace Barotrauma.Networking { settingsFrame.Draw(spriteBatch); } - else if (log.LogFrame != null) + else if (ServerLog.LogFrame != null) { - log.LogFrame.Draw(spriteBatch); + ServerLog.LogFrame.Draw(spriteBatch); } if (Screen.Selected == GameMain.GameScreen && !GUI.DisableHUD) diff --git a/Barotrauma/BarotraumaClient/Source/Networking/NetworkMember.cs b/Barotrauma/BarotraumaClient/Source/Networking/NetworkMember.cs index 1bdd6110b..c6c126bc4 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/NetworkMember.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/NetworkMember.cs @@ -24,6 +24,7 @@ namespace Barotrauma.Networking protected GUIFrame inGameHUD; protected GUIListBox chatBox; protected GUITextBox chatMsgBox; + protected GUIButton showLogButton; public GUIFrame InGameHUD { @@ -35,6 +36,23 @@ namespace Barotrauma.Networking inGameHUD = new GUIFrame(new Rectangle(0, 0, 0, 0), null, null); inGameHUD.CanBeFocused = false; + showLogButton = new GUIButton(new Rectangle(GameMain.GraphicsWidth - 170 - 170, 20, 150, 20), "Server Log", Alignment.TopLeft, "", inGameHUD) + { + OnClicked = (GUIButton button, object userData) => + { + if (ServerLog.LogFrame == null) + { + ServerLog.CreateLogFrame(); + } + else + { + ServerLog.LogFrame = null; + GUIComponent.KeyboardDispatcher.Subscriber = null; + } + return true; + } + }; + int width = (int)MathHelper.Clamp(GameMain.GraphicsWidth * 0.35f, 350, 500); int height = (int)MathHelper.Clamp(GameMain.GraphicsHeight * 0.15f, 100, 200); chatBox = new GUIListBox(new Rectangle( @@ -109,39 +127,44 @@ namespace Barotrauma.Networking { inGameHUD.AddToGUIUpdateList(); } + if (ServerLog.LogFrame != null) ServerLog.LogFrame.AddToGUIUpdateList(); } public virtual void Draw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch) { - if (!gameStarted || Screen.Selected != GameMain.GameScreen || GUI.DisableHUD) return; - - GameMain.GameSession.CrewManager.Draw(spriteBatch); - - inGameHUD.Draw(spriteBatch); - - if (respawnManager != null) + if (GUI.DisableHUD) return; + + if (gameStarted && Screen.Selected == GameMain.GameScreen) { - string respawnInfo = ""; + GameMain.GameSession.CrewManager.Draw(spriteBatch); - if (respawnManager.CurrentState == RespawnManager.State.Waiting && - respawnManager.CountdownStarted) - { - respawnInfo = respawnManager.UsingShuttle ? "Respawn Shuttle dispatching in " : "Respawning players in "; - respawnInfo = respawnManager.RespawnTimer <= 0.0f ? "" : respawnInfo + ToolBox.SecondsToReadableTime(respawnManager.RespawnTimer); - } - else if (respawnManager.CurrentState == RespawnManager.State.Transporting) - { - respawnInfo = respawnManager.TransportTimer <= 0.0f ? "" : "Shuttle leaving in " + ToolBox.SecondsToReadableTime(respawnManager.TransportTimer); - } + inGameHUD.Draw(spriteBatch); - if (!string.IsNullOrEmpty(respawnInfo)) + if (respawnManager != null) { - GUI.DrawString(spriteBatch, - new Vector2(120.0f, 10), - respawnInfo, Color.White, null, 0, GUI.SmallFont); - } + string respawnInfo = ""; + if (respawnManager.CurrentState == RespawnManager.State.Waiting && + respawnManager.CountdownStarted) + { + respawnInfo = respawnManager.UsingShuttle ? "Respawn Shuttle dispatching in " : "Respawning players in "; + respawnInfo = respawnManager.RespawnTimer <= 0.0f ? "" : respawnInfo + ToolBox.SecondsToReadableTime(respawnManager.RespawnTimer); + } + else if (respawnManager.CurrentState == RespawnManager.State.Transporting) + { + respawnInfo = respawnManager.TransportTimer <= 0.0f ? "" : "Shuttle leaving in " + ToolBox.SecondsToReadableTime(respawnManager.TransportTimer); + } + + if (!string.IsNullOrEmpty(respawnInfo)) + { + GUI.DrawString(spriteBatch, + new Vector2(120.0f, 10), + respawnInfo, Color.White, null, 0, GUI.SmallFont); + } + } } + + ServerLog.LogFrame?.Draw(spriteBatch); } public virtual bool SelectCrewCharacter(Character character, GUIComponent characterFrame) diff --git a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs index 4240e64ea..f52552cf1 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs @@ -379,13 +379,13 @@ namespace Barotrauma showLogButton.UserData = "showlog"; showLogButton.OnClicked = (GUIButton button, object userData) => { - if (GameMain.Server.ServerLog.LogFrame == null) + if (GameMain.NetworkMember.ServerLog.LogFrame == null) { - GameMain.Server.ServerLog.CreateLogFrame(); + GameMain.NetworkMember.ServerLog.CreateLogFrame(); } else { - GameMain.Server.ServerLog.LogFrame = null; + GameMain.NetworkMember.ServerLog.LogFrame = null; GUIComponent.KeyboardDispatcher.Subscriber = null; } return true; @@ -435,7 +435,9 @@ namespace Barotrauma infoFrame.RemoveChild(infoFrame.children.Find(c => c.UserData as string == "settingsButton")); infoFrame.RemoveChild(infoFrame.children.Find(c => c.UserData as string == "spectateButton")); - InfoFrame.FindChild("showlog").Visible = GameMain.Server != null; + InfoFrame.FindChild("showlog").Visible = + GameMain.Server != null || + (GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.ServerLog)); if (campaignViewButton == null) { diff --git a/Barotrauma/BarotraumaShared/Data/permissionpresets.xml b/Barotrauma/BarotraumaShared/Data/permissionpresets.xml index e909124d0..ab3f2b98d 100644 --- a/Barotrauma/BarotraumaShared/Data/permissionpresets.xml +++ b/Barotrauma/BarotraumaShared/Data/permissionpresets.xml @@ -7,8 +7,8 @@ + description="Allowed to manage round settings, kick players and access server logs." + permissions="EndRound,Kick,SelectSub,SelectMode,ManageCampaign,ConsoleCommands,ServerLog"> @@ -21,8 +21,8 @@ + description="Allowed to ban and kick players, manage round settings, access server logs and use nearly all console commands." + permissions="EndRound,Kick,Ban,SelectSub,SelectMode,ManageCampaign,ConsoleCommands,ServerLog"> diff --git a/Barotrauma/BarotraumaShared/Source/Networking/ChatMessage.cs b/Barotrauma/BarotraumaShared/Source/Networking/ChatMessage.cs index 688697f0b..6a28e95dd 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, Console, MessageBox + Default, Error, Dead, Server, Radio, Private, Console, MessageBox, ServerLog } partial class ChatMessage diff --git a/Barotrauma/BarotraumaShared/Source/Networking/ClientPermissions.cs b/Barotrauma/BarotraumaShared/Source/Networking/ClientPermissions.cs index 262503795..ae4edc2a9 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/ClientPermissions.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/ClientPermissions.cs @@ -23,7 +23,9 @@ namespace Barotrauma.Networking [Description("Manage campaign")] ManageCampaign = 32, [Description("Console commands")] - ConsoleCommands = 64 + ConsoleCommands = 64, + [Description("Access server log")] + ServerLog = 128 } class PermissionPreset diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs index f10433eba..1f5e1f4e5 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs @@ -32,9 +32,7 @@ namespace Barotrauma.Networking private RestClient restClient; private bool masterServerResponded; private IRestResponse masterServerResponse; - - private ServerLog log; - + private bool initiatedStartGame; private CoroutineHandle startGameCoroutine; @@ -57,12 +55,7 @@ namespace Barotrauma.Networking { get { return entityEventManager; } } - - public ServerLog ServerLog - { - get { return log; } - } - + public TimeSpan UpdateInterval { get { return updateInterval; } @@ -115,7 +108,7 @@ namespace Barotrauma.Networking config.EnableMessageType(NetIncomingMessageType.ConnectionApproval); - log = new ServerLog(name); + ServerLog = new ServerLog(name); InitProjSpecific(); @@ -342,9 +335,9 @@ namespace Barotrauma.Networking #if CLIENT if (ShowNetStats) netStats.Update(deltaTime); if (settingsFrame != null) settingsFrame.Update(deltaTime); - if (log.LogFrame != null) log.LogFrame.Update(deltaTime); + if (ServerLog.LogFrame != null) ServerLog.LogFrame.Update(deltaTime); #endif - + if (!started) return; base.Update(deltaTime); @@ -1503,7 +1496,7 @@ namespace Barotrauma.Networking GameMain.NetLobbyScreen.LastUpdateID++; } - if (SaveServerLogs) log.Save(); + if (SaveServerLogs) ServerLog.Save(); Character.Controlled = null; @@ -1951,7 +1944,7 @@ namespace Barotrauma.Networking msg.Write((byte)transfer.SequenceChannel); server.SendMessage(msg, transfer.Connection, NetDeliveryMethod.ReliableOrdered, transfer.SequenceChannel); } - + public void UpdateVoteStatus() { if (server.Connections.Count == 0|| connectedClients.Count == 0) return; @@ -2260,7 +2253,15 @@ namespace Barotrauma.Networking { if (GameMain.Server == null || !GameMain.Server.SaveServerLogs) return; - GameMain.Server.log.WriteLine(line, messageType); + GameMain.Server.ServerLog.WriteLine(line, messageType); + + foreach (Client client in GameMain.Server.ConnectedClients) + { + if (!client.HasPermission(ClientPermissions.ServerLog)) continue; + //use sendername as the message type + GameMain.Server.SendChatMessage( + ChatMessage.Create(messageType.ToString(), line, ChatMessageType.ServerLog, null), client); + } } public override void Disconnect() @@ -2281,7 +2282,7 @@ namespace Barotrauma.Networking if (SaveServerLogs) { Log("Shutting down the server...", ServerLog.MessageType.ServerMessage); - log.Save(); + ServerLog.Save(); } GameAnalyticsManager.AddDesignEvent("GameServer:ShutDown"); diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs index ae3c37d1c..f670bdb78 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs @@ -151,11 +151,11 @@ namespace Barotrauma.Networking { get { - return log.LinesPerFile; + return ServerLog.LinesPerFile; } set { - log.LinesPerFile = value; + ServerLog.LinesPerFile = value; } } diff --git a/Barotrauma/BarotraumaShared/Source/Networking/NetworkMember.cs b/Barotrauma/BarotraumaShared/Source/Networking/NetworkMember.cs index 742f6cbab..2aad99fba 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/NetworkMember.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/NetworkMember.cs @@ -122,6 +122,12 @@ namespace Barotrauma.Networking get { return respawnManager; } } + public ServerLog ServerLog + { + get; + protected set; + } + public NetworkMember() { InitProjSpecific(); diff --git a/Barotrauma/BarotraumaShared/Source/Networking/ServerLog.cs b/Barotrauma/BarotraumaShared/Source/Networking/ServerLog.cs index 8d26d9caf..a215eb251 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/ServerLog.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/ServerLog.cs @@ -59,9 +59,7 @@ namespace Barotrauma.Networking private int linesPerFile = 800; public const string SavePath = "ServerLogs"; - - private string serverName; - + private readonly Queue lines; private int unsavedLineCount; @@ -75,10 +73,11 @@ namespace Barotrauma.Networking set { linesPerFile = Math.Max(value, 10); } } + public string ServerName; + public ServerLog(string serverName) { - this.serverName = serverName; - + ServerName = serverName; lines = new Queue(); } @@ -139,7 +138,7 @@ namespace Barotrauma.Networking } } - string fileName = serverName + "_" + DateTime.Now.ToString("yyyy-MM-dd_HH:mm") + ".txt"; + string fileName = ServerName + "_" + DateTime.Now.ToString("yyyy-MM-dd_HH:mm") + ".txt"; var invalidChars = Path.GetInvalidFileNameChars(); foreach (char invalidChar in invalidChars) @@ -156,6 +155,7 @@ namespace Barotrauma.Networking catch (Exception e) { DebugConsole.ThrowError("Saving the server log to " + filePath + " failed", e); + return; } } } From d0e014330807ea0f9891463b479a6dd59c45a9e9 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 15 Aug 2018 13:39:01 +0300 Subject: [PATCH 170/198] Respawn durations can be changed mid-round. --- .../Source/Networking/RespawnManager.cs | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs b/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs index 227e86c83..516a42aef 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs @@ -9,10 +9,7 @@ using System.Linq; namespace Barotrauma.Networking { class RespawnManager : Entity, IServerSerializable - { - private readonly float respawnInterval; - private float maxTransportTime; - + { public enum State { Waiting, @@ -66,6 +63,8 @@ namespace Barotrauma.Networking private float respawnTimer, shuttleReturnTimer, shuttleTransportTimer; + private float maxTransportTime; + private float updateReturnTimer; public Submarine RespawnShuttle @@ -113,15 +112,12 @@ namespace Barotrauma.Networking { respawnShuttle = null; } - - var server = networkMember as GameServer; - if (server != null) - { - respawnInterval = server.RespawnInterval; - maxTransportTime = server.MaxTransportTime; - } - respawnTimer = respawnInterval; + if (networkMember is GameServer server) + { + respawnTimer = server.RespawnInterval; + maxTransportTime = server.MaxTransportTime; + } } private List GetClientsToRespawn() @@ -195,7 +191,7 @@ namespace Barotrauma.Networking respawnTimer -= deltaTime; if (respawnTimer <= 0.0f) { - respawnTimer = respawnInterval; + respawnTimer = server.RespawnInterval; DispatchShuttle(); } @@ -243,6 +239,7 @@ namespace Barotrauma.Networking server.CreateEntityEvent(this); CountdownStarted = false; + maxTransportTime = server.MaxTransportTime; shuttleReturnTimer = maxTransportTime; shuttleTransportTimer = maxTransportTime; } @@ -311,7 +308,7 @@ namespace Barotrauma.Networking GameServer.Log("The respawn shuttle has left.", ServerLog.MessageType.Spawning); server.CreateEntityEvent(this); - respawnTimer = respawnInterval; + respawnTimer = server.RespawnInterval; CountdownStarted = false; } } From 9783ed3750499b254c3b6ccc906099fd2e337a0d Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 17 Aug 2018 12:58:23 +0300 Subject: [PATCH 171/198] Backported LOS effect changes from the dev branch (the brightness of the obstructer area is the same as ambient light) --- Barotrauma/BarotraumaClient/Source/Screens/GameScreen.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Screens/GameScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/GameScreen.cs index e78de2f5b..e3cfb3bef 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/GameScreen.cs @@ -244,13 +244,12 @@ namespace Barotrauma GameMain.LightManager.LosEffect.Parameters["xTexture"].SetValue(renderTargetBackground); GameMain.LightManager.LosEffect.Parameters["xLosTexture"].SetValue(GameMain.LightManager.losTexture); #endif - - - //convert the los color to HLS and make sure the luminance of the color is always the same regardless - //of the ambient light color and the luminance of the damage overlight color + //convert the los color to HLS and make sure the luminance of the color is always the same + //as the luminance of the ambient light color float r = Math.Min(CharacterHUD.damageOverlayTimer * 0.5f, 0.5f); + Vector3 ambientLightHls = GameMain.LightManager.AmbientLight.RgbToHLS(); Vector3 losColorHls = Color.Lerp(GameMain.LightManager.AmbientLight, Color.Red, r).RgbToHLS(); - losColorHls.Y = 0.1f; + losColorHls.Y = ambientLightHls.Y; Color losColor = ToolBox.HLSToRGB(losColorHls); spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.PointClamp, null, null, GameMain.LightManager.LosEffect, null); From 2f6cb84b5e238c034801513128f216568c828ceb Mon Sep 17 00:00:00 2001 From: juanjp600 Date: Fri, 17 Aug 2018 19:26:24 -0300 Subject: [PATCH 172/198] Added workaround to tabbing out breaking fullscreen --- .../BarotraumaClient/Source/GameMain.cs | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaClient/Source/GameMain.cs b/Barotrauma/BarotraumaClient/Source/GameMain.cs index 5a572c463..cc3a5e69f 100644 --- a/Barotrauma/BarotraumaClient/Source/GameMain.cs +++ b/Barotrauma/BarotraumaClient/Source/GameMain.cs @@ -10,6 +10,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using GameAnalyticsSDK.Net; +using System.Threading; namespace Barotrauma { @@ -74,7 +75,8 @@ namespace Barotrauma get; private set; } - + private static bool FullscreenOnTabIn; + public static WindowMode WindowMode { get; @@ -231,8 +233,37 @@ namespace Barotrauma TitleScreen = new LoadingScreen(GraphicsDevice); loadingCoroutine = CoroutineManager.StartCoroutine(Load()); + + var myForm = (System.Windows.Forms.Form)System.Windows.Forms.Form.FromHandle(Window.Handle); + myForm.Deactivate += new EventHandler(HandleDefocus); + myForm.Activated += new EventHandler(HandleFocus); } + private void HandleDefocus(object sender, EventArgs e) + { + if (GraphicsDeviceManager.IsFullScreen && GraphicsDeviceManager.HardwareModeSwitch) + { + GraphicsDeviceManager.IsFullScreen = false; + GraphicsDeviceManager.ApplyChanges(); + FullscreenOnTabIn = true; + Thread.Sleep(500); + } + } + + private void HandleFocus(object sender, EventArgs e) + { + if (FullscreenOnTabIn) + { + GraphicsDeviceManager.HardwareModeSwitch = true; + GraphicsDeviceManager.IsFullScreen = true; + GraphicsDeviceManager.ApplyChanges(); + FullscreenOnTabIn = false; + Thread.Sleep(500); + } + } + + + private void InitUserStats() { if (GameSettings.ShowUserStatisticsPrompt) From 3747852c13a2320f0a2d265332b4a5b25b4a465e Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 20 Aug 2018 16:23:40 +0300 Subject: [PATCH 173/198] Removed the Entity.MoveWithLevel property, fixed artifacts and other items from ruins not being saved. Closes #694 --- .../BarotraumaClient/Source/DebugConsole.cs | 4 +- .../BarotraumaClient/Source/Map/Submarine.cs | 2 +- .../Source/Characters/AI/PathFinder.cs | 2 +- .../Source/Events/ArtifactEvent.cs | 3 +- .../Source/Events/Missions/SalvageMission.cs | 1 - .../Source/Map/Levels/Level.cs | 8 +-- .../Source/Map/Levels/Ruins/RuinGenerator.cs | 51 +++++++++++-------- .../BarotraumaShared/Source/Map/MapEntity.cs | 11 +--- .../BarotraumaShared/Source/Map/Submarine.cs | 4 +- .../BarotraumaShared/Source/Map/WayPoint.cs | 2 +- 10 files changed, 41 insertions(+), 47 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs index 0a3a91434..3b878f699 100644 --- a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs @@ -355,9 +355,9 @@ namespace Barotrauma NewMessage("Removed " + me.Name + " (simposition " + me.SimPosition + ")", Color.Orange); MapEntity.mapEntityList.RemoveAt(i); } - else if (me.MoveWithLevel) + else if (!me.ShouldBeSaved) { - NewMessage("Removed " + me.Name + " (MoveWithLevel==true)", Color.Orange); + NewMessage("Removed " + me.Name + " (!ShouldBeSaved)", Color.Orange); MapEntity.mapEntityList.RemoveAt(i); } else if (me is Item) diff --git a/Barotrauma/BarotraumaClient/Source/Map/Submarine.cs b/Barotrauma/BarotraumaClient/Source/Map/Submarine.cs index 8202dd7e8..e275bceff 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Submarine.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Submarine.cs @@ -180,7 +180,7 @@ namespace Barotrauma } } - if (WayPoint.WayPointList.Find(wp => !wp.MoveWithLevel && wp.SpawnType == SpawnType.Path) == null) + if (!WayPoint.WayPointList.Any(wp => wp.ShouldBeSaved && wp.SpawnType == SpawnType.Path)) { errorMsgs.Add(TextManager.Get("NoWaypointsWarning")); } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/PathFinder.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/PathFinder.cs index cc4bcbd0a..a2c4b7168 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/PathFinder.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/PathFinder.cs @@ -92,7 +92,7 @@ namespace Barotrauma public PathFinder(List wayPoints, bool insideSubmarine = false) { - nodes = PathNode.GenerateNodes(wayPoints.FindAll(w => w.MoveWithLevel != insideSubmarine)); + nodes = PathNode.GenerateNodes(wayPoints.FindAll(w => (w.Submarine != null) == insideSubmarine)); foreach (WayPoint wp in wayPoints) { diff --git a/Barotrauma/BarotraumaShared/Source/Events/ArtifactEvent.cs b/Barotrauma/BarotraumaShared/Source/Events/ArtifactEvent.cs index be7ca10b1..19fd37154 100644 --- a/Barotrauma/BarotraumaShared/Source/Events/ArtifactEvent.cs +++ b/Barotrauma/BarotraumaShared/Source/Events/ArtifactEvent.cs @@ -37,7 +37,6 @@ namespace Barotrauma Level.PositionType.Cave | Level.PositionType.MainPath | Level.PositionType.Ruin, 500.0f, 10000.0f, 30.0f); item = new Item(itemPrefab, position, null); - item.MoveWithLevel = true; item.body.FarseerBody.IsKinematic = true; //try to find a nearby artifact holder (or any alien itemcontainer) and place the artifact inside it @@ -66,7 +65,7 @@ namespace Barotrauma switch (state) { case 0: - if (item.ParentInventory!=null) item.body.FarseerBody.IsKinematic = false; + if (item.ParentInventory != null) item.body.FarseerBody.IsKinematic = false; if (item.CurrentHull == null) return; state = 1; diff --git a/Barotrauma/BarotraumaShared/Source/Events/Missions/SalvageMission.cs b/Barotrauma/BarotraumaShared/Source/Events/Missions/SalvageMission.cs index b9fc7bb10..11c305eb3 100644 --- a/Barotrauma/BarotraumaShared/Source/Events/Missions/SalvageMission.cs +++ b/Barotrauma/BarotraumaShared/Source/Events/Missions/SalvageMission.cs @@ -50,7 +50,6 @@ namespace Barotrauma Vector2 position = Level.Loaded.GetRandomItemPos(spawnPositionType, 100.0f, minDistance, 30.0f); item = new Item(itemPrefab, position, null); - item.MoveWithLevel = true; item.body.FarseerBody.IsKinematic = true; if (item.HasTag("alien")) diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs index b52a2f9b6..35ac625d3 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs @@ -358,10 +358,7 @@ namespace Barotrauma WayPoint wayPoint = new WayPoint( mirror ? new Vector2(borders.X - positionOfInterest.Position.X, positionOfInterest.Position.Y) : positionOfInterest.Position, SpawnType.Enemy, - submarine: null) - { - MoveWithLevel = true - }; + submarine: null); } startPosition.X = pathCells[0].Center.X; @@ -611,7 +608,6 @@ namespace Barotrauma List wayPoints = new List(); var newWaypoint = new WayPoint(new Rectangle((int)pathCells[0].Center.X, borders.Height, 10, 10), null); - newWaypoint.MoveWithLevel = true; wayPoints.Add(newWaypoint); for (int i = 0; i < pathCells.Count; i++) @@ -619,7 +615,6 @@ namespace Barotrauma pathCells[i].CellType = CellType.Path; newWaypoint = new WayPoint(new Rectangle((int)pathCells[i].Center.X, (int)pathCells[i].Center.Y, 10, 10), null); - newWaypoint.MoveWithLevel = true; wayPoints.Add(newWaypoint); wayPoints[wayPoints.Count-2].linkedTo.Add(newWaypoint); @@ -637,7 +632,6 @@ namespace Barotrauma } newWaypoint = new WayPoint(new Rectangle((int)pathCells[pathCells.Count - 1].Center.X, borders.Height, 10, 10), null); - newWaypoint.MoveWithLevel = true; wayPoints.Add(newWaypoint); wayPoints[wayPoints.Count - 2].linkedTo.Add(newWaypoint); diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/Ruins/RuinGenerator.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/Ruins/RuinGenerator.cs index f4126af3e..cb5d15e0e 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/Ruins/RuinGenerator.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/Ruins/RuinGenerator.cs @@ -348,15 +348,15 @@ namespace Barotrauma.RuinGeneration var structurePrefab = RuinStructure.GetRandom(wallType, leaf.GetLineAlignment(wall)); if (structurePrefab == null) continue; - float radius = (wall.A.X == wall.B.X) ? - (structurePrefab.Prefab as StructurePrefab).Size.X * 0.5f : + float radius = (wall.A.X == wall.B.X) ? + (structurePrefab.Prefab as StructurePrefab).Size.X * 0.5f : (structurePrefab.Prefab as StructurePrefab).Size.Y * 0.5f; Rectangle rect = new Rectangle( - (int)(wall.A.X - radius), - (int)(wall.B.Y + radius), - (int)((wall.B.X - wall.A.X) + radius*2.0f), - (int)((wall.B.Y - wall.A.Y) + radius*2.0f)); + (int)(wall.A.X - radius), + (int)(wall.B.Y + radius), + (int)((wall.B.X - wall.A.X) + radius * 2.0f), + (int)((wall.B.Y - wall.A.Y) + radius * 2.0f)); //cut a section off from both ends of a horizontal wall to get nicer looking corners if (wall.A.Y == wall.B.Y) @@ -365,8 +365,10 @@ namespace Barotrauma.RuinGeneration if (rect.Width < Submarine.GridSize.X) continue; } - var structure = new Structure(rect, structurePrefab.Prefab as StructurePrefab, null); - structure.MoveWithLevel = true; + var structure = new Structure(rect, structurePrefab.Prefab as StructurePrefab, null) + { + ShouldBeSaved = false + }; structure.SetCollisionCategory(Physics.CollisionLevel); } @@ -376,11 +378,14 @@ namespace Barotrauma.RuinGeneration Rectangle backgroundRect = new Rectangle(leaf.Rect.X, leaf.Rect.Y + leaf.Rect.Height, leaf.Rect.Width, leaf.Rect.Height); - new Structure(backgroundRect, (background.Prefab as StructurePrefab), null).MoveWithLevel = true; + new Structure(backgroundRect, (background.Prefab as StructurePrefab), null) + { + ShouldBeSaved = false + }; - var submarineBlocker = BodyFactory.CreateRectangle(GameMain.World, + var submarineBlocker = BodyFactory.CreateRectangle(GameMain.World, ConvertUnits.ToSimUnits(leaf.Rect.Width), - ConvertUnits.ToSimUnits(leaf.Rect.Height), + ConvertUnits.ToSimUnits(leaf.Rect.Height), 1, ConvertUnits.ToSimUnits(leaf.Center)); submarineBlocker.IsStatic = true; @@ -420,8 +425,10 @@ namespace Barotrauma.RuinGeneration doorPos.Y = (wall.A.Y + wall.B.Y) / 2.0f; } - var door = new Item(doorPrefab.Prefab as ItemPrefab, doorPos, null); - door.MoveWithLevel = true; + var door = new Item(doorPrefab.Prefab as ItemPrefab, doorPos, null) + { + ShouldBeSaved = false + }; door.GetComponent().IsOpen = Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) < 0.8f; @@ -432,11 +439,13 @@ namespace Barotrauma.RuinGeneration var sensor = new Item(sensorPrefab, new Vector2( Rand.Range(sensorRoom.Rect.X, sensorRoom.Rect.Right, Rand.RandSync.Server), - Rand.Range(sensorRoom.Rect.Y, sensorRoom.Rect.Bottom, Rand.RandSync.Server)), null); - sensor.MoveWithLevel = true; + Rand.Range(sensorRoom.Rect.Y, sensorRoom.Rect.Bottom, Rand.RandSync.Server)), null) + { + ShouldBeSaved = false + }; var wire = new Item(wirePrefab, sensorRoom.Center, null).GetComponent(); - wire.Item.MoveWithLevel = false; + wire.Item.ShouldBeSaved = false; var conn1 = door.Connections.Find(c => c.Name == "set_state"); conn1.AddLink(0, wire); @@ -483,15 +492,17 @@ namespace Barotrauma.RuinGeneration if (prop.Prefab is ItemPrefab) { - var item = new Item((ItemPrefab)prop.Prefab, position, null); - item.MoveWithLevel = true; + new Item((ItemPrefab)prop.Prefab, position, null); } else { new Structure(new Rectangle( - (int)(position.X - size.X/2.0f), (int)(position.Y + size.Y/2.0f), + (int)(position.X - size.X / 2.0f), (int)(position.Y + size.Y / 2.0f), (int)size.X, (int)size.Y), - prop.Prefab as StructurePrefab, null).MoveWithLevel = true; + prop.Prefab as StructurePrefab, null) + { + ShouldBeSaved = false + }; } } diff --git a/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs index 7b5a55a5a..200454f55 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs @@ -20,16 +20,7 @@ namespace Barotrauma //observable collection because some entities may need to be notified when the collection is modified public ObservableCollection linkedTo; - - //protected float soundRange; - //protected float sightRange; - - public bool MoveWithLevel - { - get; - set; - } - + public bool ShouldBeSaved = true; //the position and dimensions of the entity diff --git a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs index a38edae39..49612ca16 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs @@ -719,7 +719,7 @@ namespace Barotrauma foreach (MapEntity e in subEntities) { - if (e.MoveWithLevel || e is Item) continue; + if (e is Item) continue; if (e is LinkedSubmarine) { @@ -1262,7 +1262,7 @@ namespace Barotrauma foreach (MapEntity e in MapEntity.mapEntityList) { - if (e.MoveWithLevel || e.Submarine != this || !e.ShouldBeSaved) continue; + if (e.Submarine != this || !e.ShouldBeSaved) continue; e.Save(element); } } diff --git a/Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs b/Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs index 13fbcd339..533219f87 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs @@ -609,7 +609,7 @@ namespace Barotrauma public override XElement Save(XElement parentElement) { - if (MoveWithLevel) return null; + if (!ShouldBeSaved) return null; XElement element = new XElement("WayPoint"); element.Add(new XAttribute("ID", ID), From 898e8dfdb179ea05283971b17e8c3557f3fa6c56 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 20 Aug 2018 20:12:33 +0300 Subject: [PATCH 174/198] Added a property for locking connection panels (better than the old hacky way of changing the required item to something else). Locked panels can still be rewired in the sub editor. Closes #679 --- .../Items/Components/Signal/Connection.cs | 39 ++++++++++--------- .../Components/Signal/ConnectionPanel.cs | 12 +++++- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs index 30a27fc2d..4466a40a8 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs @@ -34,16 +34,19 @@ namespace Barotrauma.Items.Components Wire equippedWire = null; - //if the Character using the panel has a wire item equipped - //and the wire hasn't been connected yet, draw it on the panel - for (int i = 0; i < character.SelectedItems.Length; i++) + if (!panel.Locked || Screen.Selected == GameMain.SubEditorScreen) { - Item selectedItem = character.SelectedItems[i]; + //if the Character using the panel has a wire item equipped + //and the wire hasn't been connected yet, draw it on the panel + for (int i = 0; i < character.SelectedItems.Length; i++) + { + Item selectedItem = character.SelectedItems[i]; - if (selectedItem == null) continue; + if (selectedItem == null) continue; - Wire wireComponent = selectedItem.GetComponent(); - if (wireComponent != null) equippedWire = wireComponent; + Wire wireComponent = selectedItem.GetComponent(); + if (wireComponent != null) equippedWire = wireComponent; + } } Vector2 rightPos = new Vector2(x + width - 130, y + 50); @@ -71,7 +74,7 @@ namespace Barotrauma.Items.Components //outputs are drawn at the right side of the panel, inputs at the left if (c.IsOutput) { - c.Draw(spriteBatch, panel.Item, rightPos, + c.Draw(spriteBatch, panel, rightPos, new Vector2(rightPos.X - GUI.SmallFont.MeasureString(c.Name).X - 20, rightPos.Y + 3), rightWirePos, mouseInRect, equippedWire, @@ -82,7 +85,7 @@ namespace Barotrauma.Items.Components } else { - c.Draw(spriteBatch, panel.Item, leftPos, + c.Draw(spriteBatch, panel, leftPos, new Vector2(leftPos.X + 20, leftPos.Y - 12), leftWirePos, mouseInRect, equippedWire, @@ -96,7 +99,7 @@ namespace Barotrauma.Items.Components if (draggingConnected != null) { - DrawWire(spriteBatch, draggingConnected, draggingConnected.Item, PlayerInput.MousePosition, new Vector2(x + width / 2, y + height), mouseInRect, null); + DrawWire(spriteBatch, draggingConnected, draggingConnected.Item, PlayerInput.MousePosition, new Vector2(x + width / 2, y + height), mouseInRect, null, panel); if (!PlayerInput.LeftButtonHeld()) { @@ -121,7 +124,7 @@ namespace Barotrauma.Items.Components { DrawWire(spriteBatch, equippedWire, equippedWire.Item, new Vector2(x + width / 2, y + height - 100), - new Vector2(x + width / 2, y + height), mouseInRect, null); + new Vector2(x + width / 2, y + height), mouseInRect, null, panel); if (draggingConnected == equippedWire) Inventory.draggingItem = equippedWire.Item; } @@ -135,7 +138,7 @@ namespace Barotrauma.Items.Components } - private void Draw(SpriteBatch spriteBatch, Item item, Vector2 position, Vector2 labelPos, Vector2 wirePosition, bool mouseIn, Wire equippedWire, float wireInterval) + private void Draw(SpriteBatch spriteBatch, ConnectionPanel panel, Vector2 position, Vector2 labelPos, Vector2 wirePosition, bool mouseIn, Wire equippedWire, float wireInterval) { //spriteBatch.DrawString(GUI.SmallFont, Name, new Vector2(labelPos.X, labelPos.Y-10), Color.White); GUI.DrawString(spriteBatch, labelPos, Name, IsPower ? Color.Red : Color.White, Color.Black, 0, GUI.SmallFont); @@ -149,7 +152,7 @@ namespace Barotrauma.Items.Components Connection recipient = Wires[i].OtherConnection(this); - DrawWire(spriteBatch, Wires[i], (recipient == null) ? Wires[i].Item : recipient.item, position, wirePosition, mouseIn, equippedWire); + DrawWire(spriteBatch, Wires[i], (recipient == null) ? Wires[i].Item : recipient.item, position, wirePosition, mouseIn, equippedWire, panel); wirePosition.Y += wireInterval; } @@ -165,9 +168,9 @@ namespace Barotrauma.Items.Components if (index > -1 && !Wires.Contains(draggingConnected)) { - bool alreadyConnected = draggingConnected.IsConnectedTo(item); + bool alreadyConnected = draggingConnected.IsConnectedTo(panel.Item); - draggingConnected.RemoveConnection(item); + draggingConnected.RemoveConnection(panel.Item); if (draggingConnected.Connect(this, !alreadyConnected, true)) { @@ -198,7 +201,7 @@ namespace Barotrauma.Items.Components } - private static void DrawWire(SpriteBatch spriteBatch, Wire wire, Item item, Vector2 end, Vector2 start, bool mouseIn, Wire equippedWire) + private static void DrawWire(SpriteBatch spriteBatch, Wire wire, Item item, Vector2 end, Vector2 start, bool mouseIn, Wire equippedWire, ConnectionPanel panel) { if (draggingConnected == wire) { @@ -225,7 +228,7 @@ namespace Barotrauma.Items.Components Vector2.Distance(end, PlayerInput.MousePosition) < 20.0f || new Rectangle((start.X < end.X) ? textX - 100 : textX, (int)start.Y - 5, 100, 14).Contains(PlayerInput.MousePosition)); - string label = wire.Locked ? item.Name + "\n" + TextManager.Get("ConnectionLocked") : item.Name; + string label = wire.Locked || panel.Locked ? item.Name + "\n" + TextManager.Get("ConnectionLocked") : item.Name; GUI.DrawString(spriteBatch, new Vector2(start.X < end.X ? textX - GUI.SmallFont.MeasureString(label).X : textX, start.Y - 5.0f), @@ -261,7 +264,7 @@ namespace Barotrauma.Items.Components { ConnectionPanel.HighlightedWire = wire; - if (!wire.Locked) + if (!wire.Locked && (!panel.Locked || Screen.Selected == GameMain.SubEditorScreen)) { //start dragging the wire if (PlayerInput.LeftButtonHeld()) draggingConnected = wire; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs index aec56368e..88a2471b8 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs @@ -14,7 +14,14 @@ namespace Barotrauma.Items.Components public List Connections; - Character user; + private Character user; + + [Serialize(false, true), Editable(ToolTip = "Locked connection panels cannot be rewired in-game.")] + public bool Locked + { + get; + set; + } public ConnectionPanel(Item item, XElement element) : base(item, element) @@ -180,6 +187,9 @@ namespace Barotrauma.Items.Components } } } + + //don't allow rewiring locked panels + if (Locked) return; item.CreateServerEvent(this); From 012245fbaa14b7fcb8c0373f8ccac6aad58f0304 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 20 Aug 2018 20:42:57 +0300 Subject: [PATCH 175/198] Items outside the sub cannot be deattached. Closes #562 --- .../Source/Items/Components/Holdable/Holdable.cs | 10 ++++++++++ Barotrauma/BarotraumaShared/Source/Items/Inventory.cs | 3 +++ Barotrauma/BarotraumaShared/Source/Items/Item.cs | 1 + 3 files changed, 14 insertions(+) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs index e230315e1..bf954d466 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs @@ -242,6 +242,14 @@ namespace Barotrauma.Items.Components IsActive = false; } + public bool CanBeDeattached() + { + if (!attachable || !attached) return true; + + //don't allow deattaching if outside hulls and not in sub editor + return item.CurrentHull != null || Screen.Selected == GameMain.SubEditorScreen; + } + public override bool Pick(Character picker) { if (!attachable) @@ -249,6 +257,8 @@ namespace Barotrauma.Items.Components return base.Pick(picker); } + if (!CanBeDeattached()) return false; + if (Attached) { return base.Pick(picker); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs b/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs index 96333d162..9ffc74290 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs @@ -224,6 +224,9 @@ namespace Barotrauma if (GameMain.Server != null) { + var holdable = item.GetComponent(); + if (holdable != null && !holdable.CanBeDeattached()) continue; + if (!item.CanClientAccess(c)) continue; } TryPutItem(item, i, true, true, c.Character, false); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index 04776d7ea..bdf40b09d 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -1236,6 +1236,7 @@ namespace Barotrauma { if (string.IsNullOrEmpty(ic.Msg)) continue; if (!ic.CanBePicked && !ic.CanBeSelected) continue; + if (ic is Holdable holdable && !holdable.CanBeDeattached()) continue; Color color = Color.Red; if (ic.HasRequiredSkills(character) && ic.HasRequiredItems(character, false)) color = Color.Orange; From f09fcfc3bedd93531859766348f570a051f8d9cc Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 20 Aug 2018 21:19:18 +0300 Subject: [PATCH 176/198] Fabricators show the list of required items even if the character doesn't have the skills to craft the item. Closes #717 --- .../Items/Components/Machines/Fabricator.cs | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Fabricator.cs index ac83492d0..de09d2759 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Fabricator.cs @@ -62,7 +62,7 @@ namespace Barotrauma.Items.Components if (selectedItemFrame != null) GuiFrame.RemoveChild(selectedItemFrame); //int width = 200, height = 150; - selectedItemFrame = new GUIFrame(new Rectangle(0, 0, (int)(GuiFrame.Rect.Width * 0.4f), 300), Color.Black * 0.8f, Alignment.CenterY | Alignment.Right, null, GuiFrame); + selectedItemFrame = new GUIFrame(new Rectangle(0, 0, (int)(GuiFrame.Rect.Width * 0.4f), 350), Color.Black * 0.8f, Alignment.CenterY | Alignment.Right, null, GuiFrame); selectedItemFrame.Padding = new Vector4(10.0f, 10.0f, 10.0f, 10.0f); @@ -105,36 +105,41 @@ namespace Barotrauma.Items.Components { inadequateSkills = targetItem.RequiredSkills.FindAll(skill => Character.Controlled.GetSkillLevel(skill.Name) < skill.Level); } - - Color textColor = Color.White; - string text; - if (!inadequateSkills.Any()) + + string text = TextManager.Get("FabricatorRequiredItems")+ ":\n"; + foreach (Tuple ip in targetItem.RequiredItems) { - text = TextManager.Get("FabricatorRequiredItems")+ ":\n"; - foreach (Tuple ip in targetItem.RequiredItems) - { - text += " - " + ip.Item1.Name + " x" + ip.Item2 + (ip.Item3 < 1.0f ? ", " + ip.Item3 * 100 + "% " + TextManager.Get("FabricatorRequiredCondition") + "\n" : "\n"); - } - text += TextManager.Get("FabricatorRequiredTime") + ": " + targetItem.RequiredTime + " s"; + text += " - " + ip.Item1.Name + " x" + ip.Item2 + (ip.Item3 < 1.0f ? ", " + ip.Item3 * 100 + "% " + TextManager.Get("FabricatorRequiredCondition") + "\n" : "\n"); } - else + text += TextManager.Get("FabricatorRequiredTime") + ": " + targetItem.RequiredTime + " s\n"; + + var requiredItemsText = new GUITextBlock( + new Rectangle(0, y, 0, 0), + text, + Color.Transparent, Color.White, + Alignment.TopLeft, + Alignment.TopLeft, null, + selectedItemFrame, + font: GUI.SmallFont); + + + if (targetItem.RequiredSkills.Any()) { text = TextManager.Get("FabricatorRequiredSkills") + ":\n"; foreach (Skill skill in inadequateSkills) { text += " - " + skill.Name + " " + TextManager.Get("Lvl").ToLower() + " " + skill.Level + "\n"; } - - textColor = Color.Red; + new GUITextBlock( + new Rectangle(0, y + requiredItemsText.Rect.Height, 0, 0), + text, + Color.Transparent, inadequateSkills.Any() ? Color.Red : Color.White, + Alignment.TopLeft, + Alignment.TopLeft, null, + selectedItemFrame, + font: GUI.SmallFont); } - - new GUITextBlock( - new Rectangle(0, y, 0, 25), - text, - Color.Transparent, textColor, - Alignment.TopLeft, - Alignment.TopLeft, null, - selectedItemFrame); + activateButton = new GUIButton(new Rectangle(0, -30, 100, 20), TextManager.Get("FabricatorCreate"), Color.White, Alignment.CenterX | Alignment.Bottom, "", selectedItemFrame); activateButton.OnClicked = StartButtonClicked; From 6cb2a7260c48c26b134eeeba495cf2302e67f5bf Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 21 Aug 2018 16:43:05 +0300 Subject: [PATCH 177/198] Clients dispose fileTransfers when shutting down (otherwise the files will stay open and cause "used by another process" exceptions when attempting to receive them again) --- Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs index 85779a7ce..fe097d88c 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs @@ -1408,6 +1408,11 @@ namespace Barotrauma.Networking { client.Shutdown(""); + foreach (var fileTransfer in FileReceiver.ActiveTransfers) + { + fileTransfer.Dispose(); + } + if (HasPermission(ClientPermissions.ServerLog)) { ServerLog?.Save(); From afc2e63b550e474c0e267b88a0110ab1b9f86022 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 23 Aug 2018 14:57:13 +0300 Subject: [PATCH 178/198] Client-side door state prediction fix: the clients wouldn't set the door to the correct state if the predicted state was the same as the door's previous known correct state, even if the latest state sent by the server was different (= a convoluted way of saying that door syncing works correctly now with complex door setups like the ones in SS Odyssey, see #579). --- Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs index 9a97d444a..80b768ae9 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs @@ -471,8 +471,10 @@ namespace Barotrauma.Items.Components public void SetState(bool open, bool isNetworkMessage, bool sendNetworkMessage = false) { - if (isStuck || (predictedState == null && isOpen == open) || (predictedState != null && isOpen == predictedState.Value)) return; - + if (isStuck || + (predictedState == null && isOpen == open) || + (predictedState != null && isOpen == predictedState.Value && isOpen == open)) return; + if (GameMain.Client != null && !isNetworkMessage) { //clients can "predict" that the door opens/closes when a signal is received From 48e14347a3b508882282d09f0e4abd9bc7d4dba9 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 24 Aug 2018 14:18:51 +0300 Subject: [PATCH 179/198] Fixed projectiles colliding with railguns due to 1e02d74 --- .../Source/Items/Components/Projectile.cs | 4 ++++ .../BarotraumaShared/Source/Items/Item.cs | 4 ++-- .../Source/Map/SubmarineBody.cs | 24 +++++++++---------- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs index 5ee79b5d7..859969cb4 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs @@ -188,6 +188,8 @@ namespace Barotrauma.Items.Components { if (fixture == null || fixture.IsSensor) return -1; + if (fixture.UserData is Item) return -1; + if (!fixture.CollisionCategories.HasFlag(Physics.CollisionCharacter) && !fixture.CollisionCategories.HasFlag(Physics.CollisionWall) && !fixture.CollisionCategories.HasFlag(Physics.CollisionLevel)) return -1; @@ -291,6 +293,8 @@ namespace Barotrauma.Items.Components if (IgnoredBodies.Contains(target.Body)) return false; + if (target.UserData is Item) return false; + if (target.CollisionCategories == Physics.CollisionCharacter && !(target.Body.UserData is Limb)) { return false; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index bdf40b09d..c46104937 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -46,7 +46,7 @@ namespace Barotrauma public PhysicsBody body; - public readonly XElement staticBodyConfig; + public readonly XElement StaticBodyConfig; private Vector2 lastSentPos; private bool prevBodyAwake; @@ -402,7 +402,7 @@ namespace Barotrauma case "brokensprite": break; case "staticbody": - staticBodyConfig = subElement; + StaticBodyConfig = subElement; break; case "aitarget": aiTarget = new AITarget(this); diff --git a/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs b/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs index 48d4d8628..29fe901d8 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs @@ -131,31 +131,31 @@ namespace Barotrauma foreach (Item item in Item.ItemList) { - if (item.staticBodyConfig == null) continue; + if (item.StaticBodyConfig == null) continue; - float radius = ConvertUnits.ToSimUnits(item.staticBodyConfig.GetAttributeFloat("radius", 0.0f)); - float width = ConvertUnits.ToSimUnits(item.staticBodyConfig.GetAttributeFloat("width", 0.0f)); - float height = ConvertUnits.ToSimUnits(item.staticBodyConfig.GetAttributeFloat("height", 0.0f)); + float radius = ConvertUnits.ToSimUnits(item.StaticBodyConfig.GetAttributeFloat("radius", 0.0f)); + float width = ConvertUnits.ToSimUnits(item.StaticBodyConfig.GetAttributeFloat("width", 0.0f)); + float height = ConvertUnits.ToSimUnits(item.StaticBodyConfig.GetAttributeFloat("height", 0.0f)); if (width != 0.0f && height != 0.0f) { - FixtureFactory.AttachRectangle(width, height, 5.0f, ConvertUnits.ToSimUnits(item.Position), farseerBody, this); + FixtureFactory.AttachRectangle(width, height, 5.0f, ConvertUnits.ToSimUnits(item.Position), farseerBody, this).UserData = item; } else if (radius != 0.0f && width != 0.0f) { - FixtureFactory.AttachRectangle(width, radius * 2, 5.0f, ConvertUnits.ToSimUnits(item.Position), farseerBody, this); - FixtureFactory.AttachCircle(radius, 5.0f, farseerBody, ConvertUnits.ToSimUnits(item.Position) - Vector2.UnitX * width / 2, this); - FixtureFactory.AttachCircle(radius, 5.0f, farseerBody, ConvertUnits.ToSimUnits(item.Position) + Vector2.UnitX * width / 2, this); + FixtureFactory.AttachRectangle(width, radius * 2, 5.0f, ConvertUnits.ToSimUnits(item.Position), farseerBody, this).UserData = item; + FixtureFactory.AttachCircle(radius, 5.0f, farseerBody, ConvertUnits.ToSimUnits(item.Position) - Vector2.UnitX * width / 2, this).UserData = item; + FixtureFactory.AttachCircle(radius, 5.0f, farseerBody, ConvertUnits.ToSimUnits(item.Position) + Vector2.UnitX * width / 2, this).UserData = item; } else if (radius != 0.0f && height != 0.0f) { - FixtureFactory.AttachRectangle(radius * 2, height, 5.0f, ConvertUnits.ToSimUnits(item.Position), farseerBody, this); - FixtureFactory.AttachCircle(radius, 5.0f, farseerBody, ConvertUnits.ToSimUnits(item.Position) - Vector2.UnitY * height / 2, this); - FixtureFactory.AttachCircle(radius, 5.0f, farseerBody, ConvertUnits.ToSimUnits(item.Position) + Vector2.UnitX * height / 2, this); + FixtureFactory.AttachRectangle(radius * 2, height, 5.0f, ConvertUnits.ToSimUnits(item.Position), farseerBody, this).UserData = item; + FixtureFactory.AttachCircle(radius, 5.0f, farseerBody, ConvertUnits.ToSimUnits(item.Position) - Vector2.UnitY * height / 2, this).UserData = item; + FixtureFactory.AttachCircle(radius, 5.0f, farseerBody, ConvertUnits.ToSimUnits(item.Position) + Vector2.UnitX * height / 2, this).UserData = item; } else if (radius != 0.0f) { - FixtureFactory.AttachCircle(radius, 5.0f, farseerBody, ConvertUnits.ToSimUnits(item.Position), this); + FixtureFactory.AttachCircle(radius, 5.0f, farseerBody, ConvertUnits.ToSimUnits(item.Position), this).UserData = item; } } } From 8d83580b7581520b6d0baa916dd8546838b25b39 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 29 Aug 2018 13:38:56 +0300 Subject: [PATCH 180/198] Docking port fixes again. Closes #551 - Non-saveable entities aren't removed from the linkedTo-list during saving, because that causes gaps to become unlinked with the hulls between docking ports when starting a campaign round. - Hull rechecks are disabled on docking port gaps, because moving linked subs into place may cause the gaps to become unlinked if they're positioned in a certain way (for example in some subs the hulls between the docking ports end up inside the gap). - Dockingports only connect the waypoints linked to their gap to each other, instead of every waypoint that happens to be inside the gap. --- .../Source/Items/Components/DockingPort.cs | 56 ++++++++----------- .../BarotraumaShared/Source/Items/Item.cs | 11 ++-- Barotrauma/BarotraumaShared/Source/Map/Gap.cs | 10 +++- .../Source/Map/LinkedSubmarine.cs | 6 -- .../BarotraumaShared/Source/Map/Submarine.cs | 11 +--- .../BarotraumaShared/Source/Map/WayPoint.cs | 1 + 6 files changed, 39 insertions(+), 56 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs index b87de4e83..9b9b4c888 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs @@ -201,22 +201,16 @@ namespace Barotrauma.Items.Components Math.Sign(dockingTarget.item.WorldPosition.X - item.WorldPosition.X) : Math.Sign(dockingTarget.item.WorldPosition.Y - item.WorldPosition.Y); dockingTarget.dockingDir = -dockingDir; - - foreach (WayPoint wp in WayPoint.WayPointList) + if (door != null && dockingTarget.door != null) { - if (wp.Submarine != item.Submarine || wp.SpawnType != SpawnType.Path) continue; + WayPoint myWayPoint = WayPoint.WayPointList.Find(wp => door.LinkedGap == wp.ConnectedGap); + WayPoint targetWayPoint = WayPoint.WayPointList.Find(wp => dockingTarget.door.LinkedGap == wp.ConnectedGap); - if (!Submarine.RectContains(item.Rect, wp.Position)) continue; - - foreach (WayPoint wp2 in WayPoint.WayPointList) + if (myWayPoint != null && targetWayPoint != null) { - if (wp2.Submarine != dockingTarget.item.Submarine || wp2.SpawnType != SpawnType.Path) continue; - - if (!Submarine.RectContains(dockingTarget.item.Rect, wp2.Position)) continue; - - wp.linkedTo.Add(wp2); - wp2.linkedTo.Add(wp); + myWayPoint.linkedTo.Add(targetWayPoint); + targetWayPoint.linkedTo.Add(myWayPoint); } } @@ -498,29 +492,31 @@ namespace Barotrauma.Items.Components for (int i = 0; i < 2; i++) { - Gap gap = i == 0 ? door?.LinkedGap : dockingTarget?.door?.LinkedGap; - if (gap == null || gap.linkedTo.Count == 2) continue; - + Gap doorGap = i == 0 ? door?.LinkedGap : dockingTarget?.door?.LinkedGap; + if (doorGap == null) continue; + doorGap.DisableHullRechecks = true; + if (doorGap.linkedTo.Count >= 2) continue; + if (IsHorizontal) { if (item.WorldPosition.X < dockingTarget.item.WorldPosition.X) { - if (!gap.linkedTo.Contains(hulls[0])) gap.linkedTo.Add(hulls[0]); + if (!doorGap.linkedTo.Contains(hulls[0])) doorGap.linkedTo.Add(hulls[0]); } else { - if (!gap.linkedTo.Contains(hulls[1])) gap.linkedTo.Add(hulls[1]); + if (!doorGap.linkedTo.Contains(hulls[1])) doorGap.linkedTo.Add(hulls[1]); } } else { if (item.WorldPosition.Y < dockingTarget.item.WorldPosition.Y) { - if (!gap.linkedTo.Contains(hulls[0])) gap.linkedTo.Add(hulls[0]); + if (!doorGap.linkedTo.Contains(hulls[0])) doorGap.linkedTo.Add(hulls[0]); } else { - if (!gap.linkedTo.Contains(hulls[1])) gap.linkedTo.Add(hulls[1]); + if (!doorGap.linkedTo.Contains(hulls[1])) doorGap.linkedTo.Add(hulls[1]); } } } @@ -536,25 +532,19 @@ namespace Barotrauma.Items.Components dockingTarget.item.Submarine.DockedTo.Remove(item.Submarine); item.Submarine.DockedTo.Remove(dockingTarget.item.Submarine); - - //remove all waypoint links between this sub and the dockingtarget - foreach (WayPoint wp in WayPoint.WayPointList) + + if (door != null && dockingTarget.door != null) { - if (wp.Submarine != item.Submarine || wp.SpawnType != SpawnType.Path) continue; + WayPoint myWayPoint = WayPoint.WayPointList.Find(wp => door.LinkedGap == wp.ConnectedGap); + WayPoint targetWayPoint = WayPoint.WayPointList.Find(wp => dockingTarget.door.LinkedGap == wp.ConnectedGap); - for (int i = wp.linkedTo.Count - 1; i >= 0; i--) + if (myWayPoint != null && targetWayPoint != null) { - var wp2 = wp.linkedTo[i] as WayPoint; - if (wp2 == null) continue; - - if (wp.Submarine == dockingTarget.item.Submarine) - { - wp.linkedTo.RemoveAt(i); - wp2.linkedTo.Remove(wp); - } + myWayPoint.linkedTo.Remove(targetWayPoint); + targetWayPoint.linkedTo.Remove(myWayPoint); } } - + item.linkedTo.Clear(); docked = false; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index c46104937..17dd9133d 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -1939,16 +1939,15 @@ namespace Barotrauma (int)(rect.X - Submarine.HiddenSubPosition.X) + "," + (int)(rect.Y - Submarine.HiddenSubPosition.Y))); } - + if (linkedTo != null && linkedTo.Count > 0) { - string[] linkedToIDs = new string[linkedTo.Count]; - - for (int i = 0; i < linkedTo.Count; i++) + var saveableLinked = linkedTo.Where(l => l.ShouldBeSaved).ToList(); + string[] linkedToIDs = new string[saveableLinked.Count]; + for (int i = 0; i < saveableLinked.Count; i++) { - linkedToIDs[i] = linkedTo[i].ID.ToString(); + linkedToIDs[i] = saveableLinked[i].ID.ToString(); } - element.Add(new XAttribute("linked", string.Join(",", linkedToIDs))); } diff --git a/Barotrauma/BarotraumaShared/Source/Map/Gap.cs b/Barotrauma/BarotraumaShared/Source/Map/Gap.cs index 5f67267c8..140d57fae 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Gap.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Gap.cs @@ -135,13 +135,21 @@ namespace Barotrauma { base.Move(amount); - FindHulls(); + if (!DisableHullRechecks) FindHulls(); } public static void UpdateHulls() { foreach (Gap g in GapList) { + for (int i = g.linkedTo.Count - 1; i >= 0; i--) + { + if (g.linkedTo[i].Removed) + { + g.linkedTo.RemoveAt(i); + } + } + if (g.DisableHullRechecks) continue; g.FindHulls(); } diff --git a/Barotrauma/BarotraumaShared/Source/Map/LinkedSubmarine.cs b/Barotrauma/BarotraumaShared/Source/Map/LinkedSubmarine.cs index 75a9b39f9..79f4c679d 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/LinkedSubmarine.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/LinkedSubmarine.cs @@ -261,22 +261,16 @@ namespace Barotrauma if (saveElement.Attribute("pos") != null) saveElement.Attribute("pos").Remove(); saveElement.Add(new XAttribute("pos", XMLExtensions.Vector2ToString(Position - Submarine.HiddenSubPosition))); - - var linkedPort = linkedTo.FirstOrDefault(lt => (lt is Item) && ((Item)lt).GetComponent() != null); if (linkedPort != null) { if (saveElement.Attribute("linkedto") != null) saveElement.Attribute("linkedto").Remove(); - saveElement.Add(new XAttribute("linkedto", linkedPort.ID)); } } else { - saveElement = new XElement("LinkedSubmarine"); - - sub.SaveToXElement(saveElement); } diff --git a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs index 49612ca16..9aa51ee67 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs @@ -1250,16 +1250,7 @@ namespace Barotrauma element.Add(new XAttribute("recommendedcrewsizemax", RecommendedCrewSizeMax)); element.Add(new XAttribute("recommendedcrewexperience", RecommendedCrewExperience ?? "")); element.Add(new XAttribute("compatiblecontentpackages", string.Join(", ", CompatibleContentPackages))); - - foreach (MapEntity e in MapEntity.mapEntityList) - { - if (e.linkedTo == null) continue; - for (int i = e.linkedTo.Count - 1; i >= 0; i--) - { - if (!e.linkedTo[i].ShouldBeSaved) e.linkedTo.RemoveAt(i); - } - } - + foreach (MapEntity e in MapEntity.mapEntityList) { if (e.Submarine != this || !e.ShouldBeSaved) continue; diff --git a/Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs b/Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs index 533219f87..46d9a7d05 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs @@ -636,6 +636,7 @@ namespace Barotrauma int i = 0; foreach (MapEntity e in linkedTo) { + if (!e.ShouldBeSaved) continue; element.Add(new XAttribute("linkedto" + i, e.ID)); i += 1; } From 7eed555522869fddab47719a94ae095c27514200 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 29 Aug 2018 14:50:44 +0300 Subject: [PATCH 181/198] Another docking port fix: EntityGrids are made slightly larger than the borders of the submarine, because docking ports may create gaps and hulls outside the borders, causing FindHull to fail and characters to get teleported outside the sub when from sub to another. Closes #551, hopefully for good now! --- Barotrauma/BarotraumaShared/Source/Map/EntityGrid.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/Source/Map/EntityGrid.cs b/Barotrauma/BarotraumaShared/Source/Map/EntityGrid.cs index efd1088ef..5efcead74 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/EntityGrid.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/EntityGrid.cs @@ -16,7 +16,15 @@ namespace Barotrauma public EntityGrid(Submarine submarine, float cellSize) { - this.limits = submarine.Borders; + //make the grid slightly larger than the borders of the submarine, + //because docking ports may create gaps and hulls outside the borders + int padding = 128; + + this.limits = new Rectangle( + submarine.Borders.X - padding, + submarine.Borders.Y + padding, + submarine.Borders.Width + padding * 2, + submarine.Borders.Height + padding * 2); this.Submarine = submarine; this.cellSize = cellSize; From 105461beb368c71fc00a93d558f979666b2e78f9 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 29 Aug 2018 16:45:35 +0300 Subject: [PATCH 182/198] Fixed LightComponents staying active on broken items. Closes #759 --- .../Source/Items/Components/Signal/LightComponent.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/LightComponent.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/LightComponent.cs index 2a7081e61..cf9c482a5 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/LightComponent.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/LightComponent.cs @@ -192,8 +192,13 @@ namespace Barotrauma.Items.Components voltage = 0.0f; } - + #if CLIENT + public override void UpdateBroken(float deltaTime, Camera cam) + { + light.Color = Color.Transparent; + } + protected override void RemoveComponentSpecific() { base.RemoveComponentSpecific(); From 2b18a1265ab3aecace3ed5531030665132ae8942 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 30 Aug 2018 10:42:25 +0300 Subject: [PATCH 183/198] Fixed railguns and depth charge tubes being directly usable by characters (= they could be launched simply by selecting them and left clicking, without the need to use a railgun controller). Closes #754 --- .../Content/Items/Weapons/depthcharge.xml | 5 +++-- .../Content/Items/Weapons/railgun.xml | 3 ++- .../Source/Items/Components/Turret.cs | 18 +++++++++++++++--- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Content/Items/Weapons/depthcharge.xml b/Barotrauma/BarotraumaShared/Content/Items/Weapons/depthcharge.xml index ed8ac06ac..587261e4f 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Weapons/depthcharge.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Weapons/depthcharge.xml @@ -9,11 +9,12 @@ - - + diff --git a/Barotrauma/BarotraumaShared/Content/Items/Weapons/railgun.xml b/Barotrauma/BarotraumaShared/Content/Items/Weapons/railgun.xml index 473711a84..2abec9bbe 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Weapons/railgun.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Weapons/railgun.xml @@ -10,7 +10,8 @@ - (); float availablePower = 0.0f; @@ -180,7 +186,7 @@ namespace Barotrauma.Items.Components battery.Item.CreateServerEvent(battery); } } - + Launch(projectiles[0].Item, character); if (character != null) @@ -413,6 +419,12 @@ namespace Barotrauma.Items.Components break; case "trigger_in": item.Use((float)Timing.Step, sender); + //triggering the Use method through item.Use will fail if the item is not characterusable and the signal was sent by a character + //so lets do it manually + if (!characterUsable && sender != null) + { + TryLaunch(sender); + } break; } } From bc7eb213656e49ac2303bab5f073ea24504b60a1 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 30 Aug 2018 11:32:02 +0300 Subject: [PATCH 184/198] Fixed railguns being impossible to rewire because of the collider --- Barotrauma/BarotraumaShared/Content/Items/Weapons/railgun.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Barotrauma/BarotraumaShared/Content/Items/Weapons/railgun.xml b/Barotrauma/BarotraumaShared/Content/Items/Weapons/railgun.xml index 2abec9bbe..dfc44a9be 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Weapons/railgun.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Weapons/railgun.xml @@ -2,6 +2,8 @@ From 565f14801b6c4e607dbb668ab651f10866f31e66 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Sun, 2 Sep 2018 19:25:48 +0300 Subject: [PATCH 185/198] Normalized line endings to CRLF --- .../Source/Networking/Voting.cs | 370 +- .../Source/Screens/CampaignUI.cs | 874 +-- .../BarotraumaShared/Source/DebugConsole.cs | 4740 ++++++++--------- .../Source/GameSession/CargoManager.cs | 268 +- 4 files changed, 3126 insertions(+), 3126 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Networking/Voting.cs b/Barotrauma/BarotraumaClient/Source/Networking/Voting.cs index 0b30e3d84..98413efac 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/Voting.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/Voting.cs @@ -1,188 +1,188 @@ -using Barotrauma.Networking; -using Lidgren.Network; -using Microsoft.Xna.Framework; -using System.Collections.Generic; -using System.Linq; - -namespace Barotrauma -{ - partial class Voting - { - public bool AllowSubVoting - { - get { return allowSubVoting; } - set - { - if (value == allowSubVoting) return; - allowSubVoting = value; - GameMain.NetLobbyScreen.SubList.Enabled = value || GameMain.Server != null || - (GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.SelectSub)); - GameMain.NetLobbyScreen.InfoFrame.FindChild("subvotes", true).Visible = value; - - if (GameMain.Server != null) - { - UpdateVoteTexts(value ? GameMain.Server.ConnectedClients : null, VoteType.Sub); - GameMain.Server.UpdateVoteStatus(); - } - else - { - UpdateVoteTexts(null, VoteType.Sub); - GameMain.NetLobbyScreen.SubList.Deselect(); - } - } - } - public bool AllowModeVoting - { - get { return allowModeVoting; } - set - { - if (value == allowModeVoting) return; - allowModeVoting = value; - GameMain.NetLobbyScreen.ModeList.Enabled = - value || GameMain.Server != null || - (GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.SelectMode)); - - GameMain.NetLobbyScreen.InfoFrame.FindChild("modevotes", true).Visible = value; - - //gray out modes that can't be voted - foreach (GUITextBlock comp in GameMain.NetLobbyScreen.ModeList.children) - { - comp.TextColor = - new Color(comp.TextColor.R, comp.TextColor.G, comp.TextColor.B, - !allowModeVoting || ((GameModePreset)comp.UserData).Votable ? (byte)255 : (byte)100); - } - - if (GameMain.Server != null) - { - UpdateVoteTexts(value ? GameMain.Server.ConnectedClients : null, VoteType.Mode); - GameMain.Server.UpdateVoteStatus(); - } - else - { - UpdateVoteTexts(null, VoteType.Mode); - GameMain.NetLobbyScreen.ModeList.Deselect(); - } - } - } - - public void UpdateVoteTexts(List clients, VoteType voteType) - { - GUIListBox listBox = (voteType == VoteType.Sub) ? - GameMain.NetLobbyScreen.SubList : GameMain.NetLobbyScreen.ModeList; - - foreach (GUIComponent comp in listBox.children) - { - GUITextBlock voteText = comp.FindChild("votes") as GUITextBlock; - if (voteText != null) comp.RemoveChild(voteText); - } - - if (clients != null) - { - List> voteList = GetVoteList(voteType, clients); - foreach (Pair votable in voteList) - { - SetVoteText(listBox, votable.First, votable.Second); - } - } - } - - private void SetVoteText(GUIListBox listBox, object userData, int votes) - { - if (userData == null) return; - foreach (GUIComponent comp in listBox.children) - { - if (comp.UserData != userData) continue; - GUITextBlock voteText = comp.FindChild("votes") as GUITextBlock; - if (voteText == null) - { - voteText = new GUITextBlock(new Rectangle(0, 0, 30, 0), "", "", Alignment.Right, Alignment.Right, comp); - voteText.UserData = "votes"; - } - - voteText.Text = votes == 0 ? "" : votes.ToString(); - } - } - - public void ClientWrite(NetBuffer msg, VoteType voteType, object data) - { - if (GameMain.Server != null) return; - - msg.Write((byte)voteType); - - switch (voteType) - { - case VoteType.Sub: - Submarine sub = data as Submarine; - if (sub == null) return; - - msg.Write(sub.Name); - break; - case VoteType.Mode: - GameModePreset gameMode = data as GameModePreset; - if (gameMode == null) return; - - msg.Write(gameMode.Name); - break; - case VoteType.EndRound: - if (!(data is bool)) return; - - msg.Write((bool)data); - break; - case VoteType.Kick: - Client votedClient = data as Client; - if (votedClient == null) return; - - msg.Write(votedClient.ID); - break; - } - - msg.WritePadBits(); - } - - public void ClientRead(NetIncomingMessage inc) - { - if (GameMain.Server != null) return; - - AllowSubVoting = inc.ReadBoolean(); - if (allowSubVoting) - { - UpdateVoteTexts(null, VoteType.Sub); - int votableCount = inc.ReadByte(); - for (int i = 0; i < votableCount; i++) - { - int votes = inc.ReadByte(); - string subName = inc.ReadString(); - List serversubs = new List(); +using Barotrauma.Networking; +using Lidgren.Network; +using Microsoft.Xna.Framework; +using System.Collections.Generic; +using System.Linq; + +namespace Barotrauma +{ + partial class Voting + { + public bool AllowSubVoting + { + get { return allowSubVoting; } + set + { + if (value == allowSubVoting) return; + allowSubVoting = value; + GameMain.NetLobbyScreen.SubList.Enabled = value || GameMain.Server != null || + (GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.SelectSub)); + GameMain.NetLobbyScreen.InfoFrame.FindChild("subvotes", true).Visible = value; + + if (GameMain.Server != null) + { + UpdateVoteTexts(value ? GameMain.Server.ConnectedClients : null, VoteType.Sub); + GameMain.Server.UpdateVoteStatus(); + } + else + { + UpdateVoteTexts(null, VoteType.Sub); + GameMain.NetLobbyScreen.SubList.Deselect(); + } + } + } + public bool AllowModeVoting + { + get { return allowModeVoting; } + set + { + if (value == allowModeVoting) return; + allowModeVoting = value; + GameMain.NetLobbyScreen.ModeList.Enabled = + value || GameMain.Server != null || + (GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.SelectMode)); + + GameMain.NetLobbyScreen.InfoFrame.FindChild("modevotes", true).Visible = value; + + //gray out modes that can't be voted + foreach (GUITextBlock comp in GameMain.NetLobbyScreen.ModeList.children) + { + comp.TextColor = + new Color(comp.TextColor.R, comp.TextColor.G, comp.TextColor.B, + !allowModeVoting || ((GameModePreset)comp.UserData).Votable ? (byte)255 : (byte)100); + } + + if (GameMain.Server != null) + { + UpdateVoteTexts(value ? GameMain.Server.ConnectedClients : null, VoteType.Mode); + GameMain.Server.UpdateVoteStatus(); + } + else + { + UpdateVoteTexts(null, VoteType.Mode); + GameMain.NetLobbyScreen.ModeList.Deselect(); + } + } + } + + public void UpdateVoteTexts(List clients, VoteType voteType) + { + GUIListBox listBox = (voteType == VoteType.Sub) ? + GameMain.NetLobbyScreen.SubList : GameMain.NetLobbyScreen.ModeList; + + foreach (GUIComponent comp in listBox.children) + { + GUITextBlock voteText = comp.FindChild("votes") as GUITextBlock; + if (voteText != null) comp.RemoveChild(voteText); + } + + if (clients != null) + { + List> voteList = GetVoteList(voteType, clients); + foreach (Pair votable in voteList) + { + SetVoteText(listBox, votable.First, votable.Second); + } + } + } + + private void SetVoteText(GUIListBox listBox, object userData, int votes) + { + if (userData == null) return; + foreach (GUIComponent comp in listBox.children) + { + if (comp.UserData != userData) continue; + GUITextBlock voteText = comp.FindChild("votes") as GUITextBlock; + if (voteText == null) + { + voteText = new GUITextBlock(new Rectangle(0, 0, 30, 0), "", "", Alignment.Right, Alignment.Right, comp); + voteText.UserData = "votes"; + } + + voteText.Text = votes == 0 ? "" : votes.ToString(); + } + } + + public void ClientWrite(NetBuffer msg, VoteType voteType, object data) + { + if (GameMain.Server != null) return; + + msg.Write((byte)voteType); + + switch (voteType) + { + case VoteType.Sub: + Submarine sub = data as Submarine; + if (sub == null) return; + + msg.Write(sub.Name); + break; + case VoteType.Mode: + GameModePreset gameMode = data as GameModePreset; + if (gameMode == null) return; + + msg.Write(gameMode.Name); + break; + case VoteType.EndRound: + if (!(data is bool)) return; + + msg.Write((bool)data); + break; + case VoteType.Kick: + Client votedClient = data as Client; + if (votedClient == null) return; + + msg.Write(votedClient.ID); + break; + } + + msg.WritePadBits(); + } + + public void ClientRead(NetIncomingMessage inc) + { + if (GameMain.Server != null) return; + + AllowSubVoting = inc.ReadBoolean(); + if (allowSubVoting) + { + UpdateVoteTexts(null, VoteType.Sub); + int votableCount = inc.ReadByte(); + for (int i = 0; i < votableCount; i++) + { + int votes = inc.ReadByte(); + string subName = inc.ReadString(); + List serversubs = new List(); foreach (GUIComponent item in GameMain.NetLobbyScreen?.SubList?.children) { if (item.UserData != null && item.UserData is Submarine) serversubs.Add(item.UserData as Submarine); - } - Submarine sub = serversubs.FirstOrDefault(sm => sm.Name == subName); - SetVoteText(GameMain.NetLobbyScreen.SubList, sub, votes); - } - } - AllowModeVoting = inc.ReadBoolean(); - if (allowModeVoting) - { - UpdateVoteTexts(null, VoteType.Mode); - int votableCount = inc.ReadByte(); - for (int i = 0; i < votableCount; i++) - { - int votes = inc.ReadByte(); - string modeName = inc.ReadString(); - GameModePreset mode = GameModePreset.list.Find(m => m.Name == modeName); - SetVoteText(GameMain.NetLobbyScreen.ModeList, mode, votes); - } - } - AllowEndVoting = inc.ReadBoolean(); - if (AllowEndVoting) - { - GameMain.NetworkMember.EndVoteCount = inc.ReadByte(); - GameMain.NetworkMember.EndVoteMax = inc.ReadByte(); - } - AllowVoteKick = inc.ReadBoolean(); - - inc.ReadPadBits(); - } - } -} + } + Submarine sub = serversubs.FirstOrDefault(sm => sm.Name == subName); + SetVoteText(GameMain.NetLobbyScreen.SubList, sub, votes); + } + } + AllowModeVoting = inc.ReadBoolean(); + if (allowModeVoting) + { + UpdateVoteTexts(null, VoteType.Mode); + int votableCount = inc.ReadByte(); + for (int i = 0; i < votableCount; i++) + { + int votes = inc.ReadByte(); + string modeName = inc.ReadString(); + GameModePreset mode = GameModePreset.list.Find(m => m.Name == modeName); + SetVoteText(GameMain.NetLobbyScreen.ModeList, mode, votes); + } + } + AllowEndVoting = inc.ReadBoolean(); + if (AllowEndVoting) + { + GameMain.NetworkMember.EndVoteCount = inc.ReadByte(); + GameMain.NetworkMember.EndVoteMax = inc.ReadByte(); + } + AllowVoteKick = inc.ReadBoolean(); + + inc.ReadPadBits(); + } + } +} diff --git a/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs b/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs index 99a3b3a45..d3b6ea59a 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs @@ -1,259 +1,259 @@ -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; - -namespace Barotrauma -{ - class CampaignUI - { - public enum Tab { Crew = 0, Map = 1, Store = 2 } - - private GUIFrame[] tabs; - - private GUIButton startButton; - - private Tab selectedTab; - - private GUIListBox characterList, hireList; - - private GUIListBox selectedItemList; - private GUIListBox storeItemList; - - private CampaignMode campaign; - - private GUIFrame characterPreviewFrame; - - private Level selectedLevel; - - private float mapZoom = 3.0f; - - public Action StartRound; - public Action OnLocationSelected; - - public Level SelectedLevel - { - get { return selectedLevel; } - } - - public CampaignMode Campaign - { - get { return campaign; } - } - - public CampaignUI(CampaignMode campaign, GUIFrame container) - { - this.campaign = campaign; - - tabs = new GUIFrame[3]; - - tabs[(int)Tab.Crew] = new GUIFrame(Rectangle.Empty, null, container); - tabs[(int)Tab.Crew].Padding = Vector4.One * 10.0f; - - //new GUITextBlock(new Rectangle(0, 0, 200, 25), "Crew:", Color.Transparent, Color.White, Alignment.Left, "", bottomPanel[(int)PanelTab.Crew]); - - int crewColumnWidth = Math.Min(300, (container.Rect.Width - 40) / 2); - - new GUITextBlock(new Rectangle(0, 0, 100, 20), TextManager.Get("Crew") + ":", "", tabs[(int)Tab.Crew], GUI.LargeFont); - characterList = new GUIListBox(new Rectangle(0, 40, crewColumnWidth, 0), "", tabs[(int)Tab.Crew]); - characterList.OnSelected = SelectCharacter; - - hireList = new GUIListBox(new Rectangle(0, 40, 300, 0), "", Alignment.Right, tabs[(int)Tab.Crew]); - new GUITextBlock(new Rectangle(0, 0, 300, 20), TextManager.Get("Hire") + ":", "", Alignment.Right, Alignment.Left, tabs[(int)Tab.Crew], false, GUI.LargeFont); - hireList.OnSelected = SelectCharacter; - - //--------------------------------------- - - tabs[(int)Tab.Map] = new GUIFrame(Rectangle.Empty, null, container); - tabs[(int)Tab.Map].Padding = Vector4.One * 10.0f; - - if (GameMain.Client == null) - { - startButton = new GUIButton(new Rectangle(0, 0, 100, 30), TextManager.Get("StartCampaignButton"), - Alignment.BottomRight, "", tabs[(int)Tab.Map]); - startButton.OnClicked = (GUIButton btn, object obj) => { StartRound?.Invoke(); return true; }; - startButton.Enabled = false; - } - - //--------------------------------------- - - tabs[(int)Tab.Store] = new GUIFrame(Rectangle.Empty, null, container); - tabs[(int)Tab.Store].Padding = Vector4.One * 10.0f; - - int sellColumnWidth = (tabs[(int)Tab.Store].Rect.Width - 40) / 2 - 20; - - selectedItemList = new GUIListBox(new Rectangle(0, 30, sellColumnWidth, tabs[(int)Tab.Store].Rect.Height - 80), Color.White * 0.7f, "", tabs[(int)Tab.Store]); - //selectedItemList.OnSelected = SellItem; - - storeItemList = new GUIListBox(new Rectangle(0, 30, sellColumnWidth, tabs[(int)Tab.Store].Rect.Height - 80), Color.White * 0.7f, Alignment.TopRight, "", tabs[(int)Tab.Store]); - storeItemList.OnSelected = BuyItem; - - int x = storeItemList.Rect.X - storeItemList.Parent.Rect.X; - - List itemCategories = Enum.GetValues(typeof(MapEntityCategory)).Cast().ToList(); - //don't show categories with no buyable items - itemCategories.RemoveAll(c => !MapEntityPrefab.List.Any(ep => ep.Price > 0.0f && ep.Category.HasFlag(c))); - - int buttonWidth = Math.Min(sellColumnWidth / itemCategories.Count, 100); - foreach (MapEntityCategory category in itemCategories) - { - var categoryButton = new GUIButton(new Rectangle(x, 0, buttonWidth, 20), category.ToString(), "", tabs[(int)Tab.Store]); - categoryButton.UserData = category; - categoryButton.OnClicked = SelectItemCategory; - - if (category == MapEntityCategory.Equipment) - { - SelectItemCategory(categoryButton, category); - } - x += buttonWidth; - } - - SelectTab(Tab.Map); - - UpdateLocationTab(campaign.Map.CurrentLocation); - - campaign.Map.OnLocationSelected += SelectLocation; - campaign.Map.OnLocationChanged += (location) => UpdateLocationTab(location); - campaign.CargoManager.OnItemsChanged += RefreshItemTab; - } - - private void UpdateLocationTab(Location location) - { - if (characterPreviewFrame != null) - { - characterPreviewFrame.Parent.RemoveChild(characterPreviewFrame); - characterPreviewFrame = null; - } - - if (location.HireManager == null) - { - hireList.ClearChildren(); - hireList.Enabled = false; - - new GUITextBlock(new Rectangle(0, 0, 0, 0), TextManager.Get("HireUnavailable"), Color.Transparent, Color.LightGray, Alignment.Center, Alignment.Center, "", hireList); - return; - } - - hireList.Enabled = true; - hireList.ClearChildren(); - - foreach (CharacterInfo c in location.HireManager.availableCharacters) - { - var frame = c.CreateCharacterFrame(hireList, c.Name + " (" + c.Job.Name + ")", c); - - new GUITextBlock( - new Rectangle(0, 0, 0, 25), - c.Salary.ToString(), - null, null, - Alignment.TopRight, "", frame); - } - - RefreshItemTab(); - } - - public void Update(float deltaTime) - { - mapZoom += PlayerInput.ScrollWheelSpeed / 1000.0f; - mapZoom = MathHelper.Clamp(mapZoom, 1.0f, 4.0f); - - if (GameMain.GameSession?.Map != null) - { - GameMain.GameSession.Map.Update(deltaTime, new Rectangle( - tabs[(int)selectedTab].Rect.X + 20, - tabs[(int)selectedTab].Rect.Y + 20, - tabs[(int)selectedTab].Rect.Width - 310, - tabs[(int)selectedTab].Rect.Height - 40), mapZoom); - } - } - - public void Draw(SpriteBatch spriteBatch) - { - if (selectedTab == Tab.Map && GameMain.GameSession?.Map != null) - { - GameMain.GameSession.Map.Draw(spriteBatch, new Rectangle( - tabs[(int)selectedTab].Rect.X + 20, - tabs[(int)selectedTab].Rect.Y + 20, - tabs[(int)selectedTab].Rect.Width - 310, - tabs[(int)selectedTab].Rect.Height - 40), mapZoom); - } - } - - public void UpdateCharacterLists() - { - characterList.ClearChildren(); - foreach (CharacterInfo c in GameMain.GameSession.CrewManager.GetCharacterInfos()) - { - c.CreateCharacterFrame(characterList, c.Name + " (" + c.Job.Name + ") ", c); - } - } - - public void SelectLocation(Location location, LocationConnection connection) - { - GUIComponent locationPanel = tabs[(int)Tab.Map].GetChild("selectedlocation"); - - if (locationPanel != null) tabs[(int)Tab.Map].RemoveChild(locationPanel); - - locationPanel = new GUIFrame(new Rectangle(0, 0, 250, 190), Color.Transparent, Alignment.TopRight, null, tabs[(int)Tab.Map]); - locationPanel.UserData = "selectedlocation"; - - if (location == null) return; - - var titleText = new GUITextBlock(new Rectangle(0, 0, 250, 0), location.Name, "", Alignment.TopLeft, Alignment.TopCenter, locationPanel, true, GUI.LargeFont); - - if (GameMain.GameSession.Map.SelectedConnection != null && GameMain.GameSession.Map.SelectedConnection.Mission != null) - { - var mission = GameMain.GameSession.Map.SelectedConnection.Mission; - - new GUITextBlock(new Rectangle(0, titleText.Rect.Height + 20, 0, 20), TextManager.Get("Mission") + ": " + mission.Name, "", locationPanel); - new GUITextBlock(new Rectangle(0, titleText.Rect.Height + 40, 0, 20), TextManager.Get("Reward") + ": " + mission.Reward + " " + TextManager.Get("Credits"), "", locationPanel); - new GUITextBlock(new Rectangle(0, titleText.Rect.Height + 70, 0, 0), mission.Description, "", Alignment.TopLeft, Alignment.TopLeft, locationPanel, true, GUI.SmallFont); - } - - if (startButton != null) startButton.Enabled = true; - - selectedLevel = connection.Level; - - OnLocationSelected?.Invoke(location, connection); - } - - private void CreateItemFrame(PurchasedItem pi, GUIListBox listBox, int width) - { - GUIFrame frame = new GUIFrame(new Rectangle(0, 0, 0, 50), "ListBoxElement", listBox); - frame.UserData = pi; - frame.Padding = new Vector4(5.0f, 5.0f, 5.0f, 5.0f); - - frame.ToolTip = pi.itemPrefab.Description; - - ScalableFont font = listBox.Rect.Width < 280 ? GUI.SmallFont : GUI.Font; - - GUITextBlock textBlock = new GUITextBlock( - new Rectangle(50, 0, 0, 25), - pi.itemPrefab.Name, - null, null, - Alignment.Left, Alignment.CenterX | Alignment.Left, - "", frame); - textBlock.Font = font; - textBlock.Padding = new Vector4(5.0f, 0.0f, 5.0f, 0.0f); - textBlock.ToolTip = pi.itemPrefab.Description; - - if (pi.itemPrefab.sprite != null) - { - GUIImage img = new GUIImage(new Rectangle(0, 0, 40, 40), pi.itemPrefab.sprite, Alignment.CenterLeft, frame); - img.Color = pi.itemPrefab.SpriteColor; - img.Scale = Math.Min(Math.Min(40.0f / img.SourceRect.Width, 40.0f / img.SourceRect.Height), 1.0f); - } - - textBlock = new GUITextBlock( - new Rectangle(width - 160, 0, 80, 25), - pi.itemPrefab.Price.ToString(), - null, null, Alignment.TopLeft, - Alignment.TopLeft, "", frame); - textBlock.Font = font; - textBlock.ToolTip = pi.itemPrefab.Description; - - //If its the store menu, quantity will always be 0 +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace Barotrauma +{ + class CampaignUI + { + public enum Tab { Crew = 0, Map = 1, Store = 2 } + + private GUIFrame[] tabs; + + private GUIButton startButton; + + private Tab selectedTab; + + private GUIListBox characterList, hireList; + + private GUIListBox selectedItemList; + private GUIListBox storeItemList; + + private CampaignMode campaign; + + private GUIFrame characterPreviewFrame; + + private Level selectedLevel; + + private float mapZoom = 3.0f; + + public Action StartRound; + public Action OnLocationSelected; + + public Level SelectedLevel + { + get { return selectedLevel; } + } + + public CampaignMode Campaign + { + get { return campaign; } + } + + public CampaignUI(CampaignMode campaign, GUIFrame container) + { + this.campaign = campaign; + + tabs = new GUIFrame[3]; + + tabs[(int)Tab.Crew] = new GUIFrame(Rectangle.Empty, null, container); + tabs[(int)Tab.Crew].Padding = Vector4.One * 10.0f; + + //new GUITextBlock(new Rectangle(0, 0, 200, 25), "Crew:", Color.Transparent, Color.White, Alignment.Left, "", bottomPanel[(int)PanelTab.Crew]); + + int crewColumnWidth = Math.Min(300, (container.Rect.Width - 40) / 2); + + new GUITextBlock(new Rectangle(0, 0, 100, 20), TextManager.Get("Crew") + ":", "", tabs[(int)Tab.Crew], GUI.LargeFont); + characterList = new GUIListBox(new Rectangle(0, 40, crewColumnWidth, 0), "", tabs[(int)Tab.Crew]); + characterList.OnSelected = SelectCharacter; + + hireList = new GUIListBox(new Rectangle(0, 40, 300, 0), "", Alignment.Right, tabs[(int)Tab.Crew]); + new GUITextBlock(new Rectangle(0, 0, 300, 20), TextManager.Get("Hire") + ":", "", Alignment.Right, Alignment.Left, tabs[(int)Tab.Crew], false, GUI.LargeFont); + hireList.OnSelected = SelectCharacter; + + //--------------------------------------- + + tabs[(int)Tab.Map] = new GUIFrame(Rectangle.Empty, null, container); + tabs[(int)Tab.Map].Padding = Vector4.One * 10.0f; + + if (GameMain.Client == null) + { + startButton = new GUIButton(new Rectangle(0, 0, 100, 30), TextManager.Get("StartCampaignButton"), + Alignment.BottomRight, "", tabs[(int)Tab.Map]); + startButton.OnClicked = (GUIButton btn, object obj) => { StartRound?.Invoke(); return true; }; + startButton.Enabled = false; + } + + //--------------------------------------- + + tabs[(int)Tab.Store] = new GUIFrame(Rectangle.Empty, null, container); + tabs[(int)Tab.Store].Padding = Vector4.One * 10.0f; + + int sellColumnWidth = (tabs[(int)Tab.Store].Rect.Width - 40) / 2 - 20; + + selectedItemList = new GUIListBox(new Rectangle(0, 30, sellColumnWidth, tabs[(int)Tab.Store].Rect.Height - 80), Color.White * 0.7f, "", tabs[(int)Tab.Store]); + //selectedItemList.OnSelected = SellItem; + + storeItemList = new GUIListBox(new Rectangle(0, 30, sellColumnWidth, tabs[(int)Tab.Store].Rect.Height - 80), Color.White * 0.7f, Alignment.TopRight, "", tabs[(int)Tab.Store]); + storeItemList.OnSelected = BuyItem; + + int x = storeItemList.Rect.X - storeItemList.Parent.Rect.X; + + List itemCategories = Enum.GetValues(typeof(MapEntityCategory)).Cast().ToList(); + //don't show categories with no buyable items + itemCategories.RemoveAll(c => !MapEntityPrefab.List.Any(ep => ep.Price > 0.0f && ep.Category.HasFlag(c))); + + int buttonWidth = Math.Min(sellColumnWidth / itemCategories.Count, 100); + foreach (MapEntityCategory category in itemCategories) + { + var categoryButton = new GUIButton(new Rectangle(x, 0, buttonWidth, 20), category.ToString(), "", tabs[(int)Tab.Store]); + categoryButton.UserData = category; + categoryButton.OnClicked = SelectItemCategory; + + if (category == MapEntityCategory.Equipment) + { + SelectItemCategory(categoryButton, category); + } + x += buttonWidth; + } + + SelectTab(Tab.Map); + + UpdateLocationTab(campaign.Map.CurrentLocation); + + campaign.Map.OnLocationSelected += SelectLocation; + campaign.Map.OnLocationChanged += (location) => UpdateLocationTab(location); + campaign.CargoManager.OnItemsChanged += RefreshItemTab; + } + + private void UpdateLocationTab(Location location) + { + if (characterPreviewFrame != null) + { + characterPreviewFrame.Parent.RemoveChild(characterPreviewFrame); + characterPreviewFrame = null; + } + + if (location.HireManager == null) + { + hireList.ClearChildren(); + hireList.Enabled = false; + + new GUITextBlock(new Rectangle(0, 0, 0, 0), TextManager.Get("HireUnavailable"), Color.Transparent, Color.LightGray, Alignment.Center, Alignment.Center, "", hireList); + return; + } + + hireList.Enabled = true; + hireList.ClearChildren(); + + foreach (CharacterInfo c in location.HireManager.availableCharacters) + { + var frame = c.CreateCharacterFrame(hireList, c.Name + " (" + c.Job.Name + ")", c); + + new GUITextBlock( + new Rectangle(0, 0, 0, 25), + c.Salary.ToString(), + null, null, + Alignment.TopRight, "", frame); + } + + RefreshItemTab(); + } + + public void Update(float deltaTime) + { + mapZoom += PlayerInput.ScrollWheelSpeed / 1000.0f; + mapZoom = MathHelper.Clamp(mapZoom, 1.0f, 4.0f); + + if (GameMain.GameSession?.Map != null) + { + GameMain.GameSession.Map.Update(deltaTime, new Rectangle( + tabs[(int)selectedTab].Rect.X + 20, + tabs[(int)selectedTab].Rect.Y + 20, + tabs[(int)selectedTab].Rect.Width - 310, + tabs[(int)selectedTab].Rect.Height - 40), mapZoom); + } + } + + public void Draw(SpriteBatch spriteBatch) + { + if (selectedTab == Tab.Map && GameMain.GameSession?.Map != null) + { + GameMain.GameSession.Map.Draw(spriteBatch, new Rectangle( + tabs[(int)selectedTab].Rect.X + 20, + tabs[(int)selectedTab].Rect.Y + 20, + tabs[(int)selectedTab].Rect.Width - 310, + tabs[(int)selectedTab].Rect.Height - 40), mapZoom); + } + } + + public void UpdateCharacterLists() + { + characterList.ClearChildren(); + foreach (CharacterInfo c in GameMain.GameSession.CrewManager.GetCharacterInfos()) + { + c.CreateCharacterFrame(characterList, c.Name + " (" + c.Job.Name + ") ", c); + } + } + + public void SelectLocation(Location location, LocationConnection connection) + { + GUIComponent locationPanel = tabs[(int)Tab.Map].GetChild("selectedlocation"); + + if (locationPanel != null) tabs[(int)Tab.Map].RemoveChild(locationPanel); + + locationPanel = new GUIFrame(new Rectangle(0, 0, 250, 190), Color.Transparent, Alignment.TopRight, null, tabs[(int)Tab.Map]); + locationPanel.UserData = "selectedlocation"; + + if (location == null) return; + + var titleText = new GUITextBlock(new Rectangle(0, 0, 250, 0), location.Name, "", Alignment.TopLeft, Alignment.TopCenter, locationPanel, true, GUI.LargeFont); + + if (GameMain.GameSession.Map.SelectedConnection != null && GameMain.GameSession.Map.SelectedConnection.Mission != null) + { + var mission = GameMain.GameSession.Map.SelectedConnection.Mission; + + new GUITextBlock(new Rectangle(0, titleText.Rect.Height + 20, 0, 20), TextManager.Get("Mission") + ": " + mission.Name, "", locationPanel); + new GUITextBlock(new Rectangle(0, titleText.Rect.Height + 40, 0, 20), TextManager.Get("Reward") + ": " + mission.Reward + " " + TextManager.Get("Credits"), "", locationPanel); + new GUITextBlock(new Rectangle(0, titleText.Rect.Height + 70, 0, 0), mission.Description, "", Alignment.TopLeft, Alignment.TopLeft, locationPanel, true, GUI.SmallFont); + } + + if (startButton != null) startButton.Enabled = true; + + selectedLevel = connection.Level; + + OnLocationSelected?.Invoke(location, connection); + } + + private void CreateItemFrame(PurchasedItem pi, GUIListBox listBox, int width) + { + GUIFrame frame = new GUIFrame(new Rectangle(0, 0, 0, 50), "ListBoxElement", listBox); + frame.UserData = pi; + frame.Padding = new Vector4(5.0f, 5.0f, 5.0f, 5.0f); + + frame.ToolTip = pi.itemPrefab.Description; + + ScalableFont font = listBox.Rect.Width < 280 ? GUI.SmallFont : GUI.Font; + + GUITextBlock textBlock = new GUITextBlock( + new Rectangle(50, 0, 0, 25), + pi.itemPrefab.Name, + null, null, + Alignment.Left, Alignment.CenterX | Alignment.Left, + "", frame); + textBlock.Font = font; + textBlock.Padding = new Vector4(5.0f, 0.0f, 5.0f, 0.0f); + textBlock.ToolTip = pi.itemPrefab.Description; + + if (pi.itemPrefab.sprite != null) + { + GUIImage img = new GUIImage(new Rectangle(0, 0, 40, 40), pi.itemPrefab.sprite, Alignment.CenterLeft, frame); + img.Color = pi.itemPrefab.SpriteColor; + img.Scale = Math.Min(Math.Min(40.0f / img.SourceRect.Width, 40.0f / img.SourceRect.Height), 1.0f); + } + + textBlock = new GUITextBlock( + new Rectangle(width - 160, 0, 80, 25), + pi.itemPrefab.Price.ToString(), + null, null, Alignment.TopLeft, + Alignment.TopLeft, "", frame); + textBlock.Font = font; + textBlock.ToolTip = pi.itemPrefab.Description; + + //If its the store menu, quantity will always be 0 if (pi.quantity > 0) { var amountInput = new GUINumberInput(new Rectangle(width - 80, 0, 50, 40), "", GUINumberInput.NumberType.Int, frame); @@ -261,193 +261,193 @@ namespace Barotrauma amountInput.MaxValueInt = 1000; amountInput.UserData = pi; amountInput.IntValue = pi.quantity; - amountInput.OnValueChanged += (numberInput) => - { - PurchasedItem purchasedItem = numberInput.UserData as PurchasedItem; - - //Attempting to buy - if (numberInput.IntValue > purchasedItem.quantity) - { - int quantity = numberInput.IntValue - purchasedItem.quantity; - //Cap the numberbox based on the amount we can afford. - quantity = campaign.Money <= 0 ? - 0 : Math.Min((int)(Campaign.Money / (float)purchasedItem.itemPrefab.Price), quantity); + amountInput.OnValueChanged += (numberInput) => + { + PurchasedItem purchasedItem = numberInput.UserData as PurchasedItem; + + //Attempting to buy + if (numberInput.IntValue > purchasedItem.quantity) + { + int quantity = numberInput.IntValue - purchasedItem.quantity; + //Cap the numberbox based on the amount we can afford. + quantity = campaign.Money <= 0 ? + 0 : Math.Min((int)(Campaign.Money / (float)purchasedItem.itemPrefab.Price), quantity); for (int i = 0; i < quantity; i++) { BuyItem(numberInput, purchasedItem); - } - numberInput.IntValue = purchasedItem.quantity; - } - //Attempting to sell - else + } + numberInput.IntValue = purchasedItem.quantity; + } + //Attempting to sell + else { - int quantity = purchasedItem.quantity - numberInput.IntValue; + int quantity = purchasedItem.quantity - numberInput.IntValue; for (int i = 0; i < quantity; i++) { SellItem(numberInput, purchasedItem); - } - } + } + } }; - } - } - - private bool BuyItem(GUIComponent component, object obj) - { - PurchasedItem pi = obj as PurchasedItem; - if (pi == null || pi.itemPrefab == null) return false; - - if (GameMain.Client != null && !GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign)) - { - return false; - } - - if (pi.itemPrefab.Price > campaign.Money) return false; - - campaign.CargoManager.PurchaseItem(pi.itemPrefab, 1); - GameMain.Client?.SendCampaignState(); - - return false; - } - - private bool SellItem(GUIComponent component, object obj) - { - PurchasedItem pi = obj as PurchasedItem; - if (pi == null || pi.itemPrefab == null) return false; - - if (GameMain.Client != null && !GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign)) - { - return false; - } - - campaign.CargoManager.SellItem(pi.itemPrefab,1); - GameMain.Client?.SendCampaignState(); - - return false; - } - - private void RefreshItemTab() - { - selectedItemList.ClearChildren(); - foreach (PurchasedItem pi in campaign.CargoManager.PurchasedItems) - { - CreateItemFrame(pi, selectedItemList, selectedItemList.Rect.Width); } - selectedItemList.children.Sort((x, y) => (x.UserData as PurchasedItem).itemPrefab.Name.CompareTo((y.UserData as PurchasedItem).itemPrefab.Name)); - selectedItemList.children.Sort((x, y) => (x.UserData as PurchasedItem).itemPrefab.Category.CompareTo((y.UserData as PurchasedItem).itemPrefab.Category)); - selectedItemList.UpdateScrollBarSize(); - } - - public void SelectTab(Tab tab) - { - selectedTab = tab; - for (int i = 0; i< tabs.Length; i++) - { - tabs[i].Visible = (int)selectedTab == i; - } - } - - private bool SelectItemCategory(GUIButton button, object selection) - { - if (!(selection is MapEntityCategory)) return false; - - storeItemList.ClearChildren(); - - MapEntityCategory category = (MapEntityCategory)selection; - var items = MapEntityPrefab.List.FindAll(ep => ep.Price > 0.0f && ep.Category.HasFlag(category) && ep is ItemPrefab); - - int width = storeItemList.Rect.Width; - - foreach (ItemPrefab ep in items) - { - CreateItemFrame(new PurchasedItem((ItemPrefab)ep,0), storeItemList, width); - } - - storeItemList.children.Sort((x, y) => (x.UserData as PurchasedItem).itemPrefab.Name.CompareTo((y.UserData as PurchasedItem).itemPrefab.Name)); - - foreach (GUIComponent child in button.Parent.children) - { - var otherButton = child as GUIButton; - if (child.UserData is MapEntityCategory && otherButton != button) - { - otherButton.Selected = false; - } - } - - button.Selected = true; - return true; - } - - public string GetMoney() - { - return TextManager.Get("Credits") + ": " + ((GameMain.GameSession == null) ? "0" : string.Format(CultureInfo.InvariantCulture, "{0:N0}", campaign.Money)); - } - - private bool SelectCharacter(GUIComponent component, object selection) - { - GUIComponent prevInfoFrame = null; - foreach (GUIComponent child in tabs[(int)selectedTab].children) - { - if (!(child.UserData is CharacterInfo)) continue; - - prevInfoFrame = child; - } - - if (prevInfoFrame != null) tabs[(int)selectedTab].RemoveChild(prevInfoFrame); - - CharacterInfo characterInfo = selection as CharacterInfo; - if (characterInfo == null) return false; - - characterList.Deselect(); - hireList.Deselect(); - - if (Character.Controlled != null && characterInfo == Character.Controlled.Info) return false; - - if (characterPreviewFrame == null || characterPreviewFrame.UserData != characterInfo) - { - int width = Math.Min(300, tabs[(int)Tab.Crew].Rect.Width - hireList.Rect.Width - characterList.Rect.Width - 50); - - characterPreviewFrame = new GUIFrame(new Rectangle(0, 60, width, 300), - new Color(0.0f, 0.0f, 0.0f, 0.8f), - Alignment.TopCenter, "", tabs[(int)selectedTab]); - characterPreviewFrame.Padding = new Vector4(20.0f, 20.0f, 20.0f, 20.0f); - characterPreviewFrame.UserData = characterInfo; - - characterInfo.CreateInfoFrame(characterPreviewFrame); - } - - if (component.Parent == hireList) - { - GUIButton hireButton = new GUIButton(new Rectangle(0, 0, 100, 20), TextManager.Get("HireButton"), Alignment.BottomCenter, "", characterPreviewFrame); - hireButton.Enabled = campaign.Money >= characterInfo.Salary; - hireButton.UserData = characterInfo; - hireButton.OnClicked = HireCharacter; - } - - return true; - } - - private bool HireCharacter(GUIButton button, object selection) - { - CharacterInfo characterInfo = selection as CharacterInfo; - if (characterInfo == null) return false; - - SinglePlayerCampaign spCampaign = campaign as SinglePlayerCampaign; - if (spCampaign == null) - { - DebugConsole.ThrowError("Characters can only be hired in the single player campaign.\n" + Environment.StackTrace); - return false; - } - - if (spCampaign.TryHireCharacter(GameMain.GameSession.Map.CurrentLocation.HireManager, characterInfo)) - { - UpdateLocationTab(GameMain.GameSession.Map.CurrentLocation); - SelectCharacter(null, null); - UpdateCharacterLists(); - } - - return false; - } - - - } -} + } + + private bool BuyItem(GUIComponent component, object obj) + { + PurchasedItem pi = obj as PurchasedItem; + if (pi == null || pi.itemPrefab == null) return false; + + if (GameMain.Client != null && !GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign)) + { + return false; + } + + if (pi.itemPrefab.Price > campaign.Money) return false; + + campaign.CargoManager.PurchaseItem(pi.itemPrefab, 1); + GameMain.Client?.SendCampaignState(); + + return false; + } + + private bool SellItem(GUIComponent component, object obj) + { + PurchasedItem pi = obj as PurchasedItem; + if (pi == null || pi.itemPrefab == null) return false; + + if (GameMain.Client != null && !GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign)) + { + return false; + } + + campaign.CargoManager.SellItem(pi.itemPrefab,1); + GameMain.Client?.SendCampaignState(); + + return false; + } + + private void RefreshItemTab() + { + selectedItemList.ClearChildren(); + foreach (PurchasedItem pi in campaign.CargoManager.PurchasedItems) + { + CreateItemFrame(pi, selectedItemList, selectedItemList.Rect.Width); + } + selectedItemList.children.Sort((x, y) => (x.UserData as PurchasedItem).itemPrefab.Name.CompareTo((y.UserData as PurchasedItem).itemPrefab.Name)); + selectedItemList.children.Sort((x, y) => (x.UserData as PurchasedItem).itemPrefab.Category.CompareTo((y.UserData as PurchasedItem).itemPrefab.Category)); + selectedItemList.UpdateScrollBarSize(); + } + + public void SelectTab(Tab tab) + { + selectedTab = tab; + for (int i = 0; i< tabs.Length; i++) + { + tabs[i].Visible = (int)selectedTab == i; + } + } + + private bool SelectItemCategory(GUIButton button, object selection) + { + if (!(selection is MapEntityCategory)) return false; + + storeItemList.ClearChildren(); + + MapEntityCategory category = (MapEntityCategory)selection; + var items = MapEntityPrefab.List.FindAll(ep => ep.Price > 0.0f && ep.Category.HasFlag(category) && ep is ItemPrefab); + + int width = storeItemList.Rect.Width; + + foreach (ItemPrefab ep in items) + { + CreateItemFrame(new PurchasedItem((ItemPrefab)ep,0), storeItemList, width); + } + + storeItemList.children.Sort((x, y) => (x.UserData as PurchasedItem).itemPrefab.Name.CompareTo((y.UserData as PurchasedItem).itemPrefab.Name)); + + foreach (GUIComponent child in button.Parent.children) + { + var otherButton = child as GUIButton; + if (child.UserData is MapEntityCategory && otherButton != button) + { + otherButton.Selected = false; + } + } + + button.Selected = true; + return true; + } + + public string GetMoney() + { + return TextManager.Get("Credits") + ": " + ((GameMain.GameSession == null) ? "0" : string.Format(CultureInfo.InvariantCulture, "{0:N0}", campaign.Money)); + } + + private bool SelectCharacter(GUIComponent component, object selection) + { + GUIComponent prevInfoFrame = null; + foreach (GUIComponent child in tabs[(int)selectedTab].children) + { + if (!(child.UserData is CharacterInfo)) continue; + + prevInfoFrame = child; + } + + if (prevInfoFrame != null) tabs[(int)selectedTab].RemoveChild(prevInfoFrame); + + CharacterInfo characterInfo = selection as CharacterInfo; + if (characterInfo == null) return false; + + characterList.Deselect(); + hireList.Deselect(); + + if (Character.Controlled != null && characterInfo == Character.Controlled.Info) return false; + + if (characterPreviewFrame == null || characterPreviewFrame.UserData != characterInfo) + { + int width = Math.Min(300, tabs[(int)Tab.Crew].Rect.Width - hireList.Rect.Width - characterList.Rect.Width - 50); + + characterPreviewFrame = new GUIFrame(new Rectangle(0, 60, width, 300), + new Color(0.0f, 0.0f, 0.0f, 0.8f), + Alignment.TopCenter, "", tabs[(int)selectedTab]); + characterPreviewFrame.Padding = new Vector4(20.0f, 20.0f, 20.0f, 20.0f); + characterPreviewFrame.UserData = characterInfo; + + characterInfo.CreateInfoFrame(characterPreviewFrame); + } + + if (component.Parent == hireList) + { + GUIButton hireButton = new GUIButton(new Rectangle(0, 0, 100, 20), TextManager.Get("HireButton"), Alignment.BottomCenter, "", characterPreviewFrame); + hireButton.Enabled = campaign.Money >= characterInfo.Salary; + hireButton.UserData = characterInfo; + hireButton.OnClicked = HireCharacter; + } + + return true; + } + + private bool HireCharacter(GUIButton button, object selection) + { + CharacterInfo characterInfo = selection as CharacterInfo; + if (characterInfo == null) return false; + + SinglePlayerCampaign spCampaign = campaign as SinglePlayerCampaign; + if (spCampaign == null) + { + DebugConsole.ThrowError("Characters can only be hired in the single player campaign.\n" + Environment.StackTrace); + return false; + } + + if (spCampaign.TryHireCharacter(GameMain.GameSession.Map.CurrentLocation.HireManager, characterInfo)) + { + UpdateLocationTab(GameMain.GameSession.Map.CurrentLocation); + SelectCharacter(null, null); + UpdateCharacterLists(); + } + + return false; + } + + + } +} diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs index ef3bf9cc6..21aaadf33 100644 --- a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs @@ -1,1921 +1,1921 @@ -using Barotrauma.Items.Components; -using Barotrauma.Networking; -using FarseerPhysics; -using Microsoft.Xna.Framework; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.IO; -using System.Linq; - -namespace Barotrauma -{ - struct ColoredText - { - public string Text; - public Color Color; - public bool IsCommand; - - public readonly string Time; - - public ColoredText(string text, Color color, bool isCommand) - { - this.Text = text; - this.Color = color; - this.IsCommand = isCommand; - - Time = DateTime.Now.ToString(); - } - } - - static partial class DebugConsole - { - public class Command - { - public readonly string[] names; - public readonly string help; - - private Action onExecute; - - /// - /// 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 Func GetValidArgs; - - 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, Func getValidArgs = null) - { - names = name.Split('|'); - this.help = help; - - this.onExecute = onExecute; - this.onClientExecute = onClientExecute; - this.onClientRequestExecute = onClientRequestExecute; - - this.GetValidArgs = getValidArgs; - } - - - /// - /// 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, Func getValidArgs = null) - { - names = name.Split('|'); - this.help = help; - - this.onExecute = onExecute; - this.onClientExecute = onExecute; - - this.GetValidArgs = getValidArgs; - } - - public void Execute(string[] args) - { - if (onExecute == null) return; - onExecute(args); - } - - public void ClientExecute(string[] args) - { - onClientExecute(args); - } - - public void ServerExecuteOnClientRequest(Client client, Vector2 cursorWorldPos, string[] args) - { - if (onClientRequestExecute == null) - { - if (onExecute == null) return; - onExecute(args); - } - else - { - onClientRequestExecute(client, cursorWorldPos, args); - } - } - } - - const int MaxMessages = 200; - - public static List Messages = new List(); - - public delegate void QuestionCallback(string answer); - private static QuestionCallback activeQuestionCallback; - - private static List commands = new List(); - public static List Commands - { - get { return commands; } - } - - private static string currentAutoCompletedCommand; - private static int currentAutoCompletedIndex; - - //used for keeping track of the message entered when pressing up/down - static int selectedIndex; - - private static List unsavedMessages = new List(); - private static int messagesPerFile = 800; - public const string SavePath = "ConsoleLogs"; - - static DebugConsole() - { - commands.Add(new Command("help", "", (string[] args) => - { - if (args.Length == 0) - { - foreach (Command c in commands) - { - if (string.IsNullOrEmpty(c.help)) continue; - NewMessage(c.help, Color.Cyan); - } - } - else - { - var matchingCommand = commands.Find(c => c.names.Any(name => name == args[0])); - if (matchingCommand == null) - { - NewMessage("Command " + args[0] + " not found.", Color.Red); - } - else - { - NewMessage(matchingCommand.help, Color.Cyan); - } - } - })); - - commands.Add(new Command("clientlist", "clientlist: List all the clients connected to the server.", (string[] args) => - { - if (GameMain.Server == null) return; - NewMessage("***************", Color.Cyan); - foreach (Client c in GameMain.Server.ConnectedClients) - { - NewMessage("- " + c.ID.ToString() + ": " + c.Name + (c.Character != null ? " playing " + c.Character.LogName : "") + ", " + c.Connection.RemoteEndPoint.Address.ToString(), Color.Cyan); - } - NewMessage("***************", Color.Cyan); - }, null, - (Client client, Vector2 cursorWorldPos, string[] args) => - { - GameMain.Server.SendConsoleMessage("***************", client); - foreach (Client c in GameMain.Server.ConnectedClients) - { - GameMain.Server.SendConsoleMessage("- " + c.ID.ToString() + ": " + c.Name + ", " + c.Connection.RemoteEndPoint.Address.ToString(), client); - } - GameMain.Server.SendConsoleMessage("***************", client); - })); - - commands.Add(new Command("traitorlist", "traitorlist: List all the traitors and their targets.", (string[] args) => - { - if (GameMain.Server == null) return; - TraitorManager traitorManager = GameMain.Server.TraitorManager; - if (traitorManager == null) return; - foreach (Traitor t in traitorManager.TraitorList) - { - NewMessage("- Traitor " + t.Character.Name + "'s target is " + t.TargetCharacter.Name + ".", Color.Cyan); - } - NewMessage("The code words are: " + traitorManager.codeWords + ", response: " + traitorManager.codeResponse + ".", Color.Cyan); - }, - null, - (Client client, Vector2 cursorPos, string[] args) => - { - TraitorManager traitorManager = GameMain.Server.TraitorManager; - if (traitorManager == null) return; - foreach (Traitor t in traitorManager.TraitorList) - { - GameMain.Server.SendConsoleMessage("- Traitor " + t.Character.Name + "'s target is " + t.TargetCharacter.Name + ".", client); - } +using Barotrauma.Items.Components; +using Barotrauma.Networking; +using FarseerPhysics; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; + +namespace Barotrauma +{ + struct ColoredText + { + public string Text; + public Color Color; + public bool IsCommand; + + public readonly string Time; + + public ColoredText(string text, Color color, bool isCommand) + { + this.Text = text; + this.Color = color; + this.IsCommand = isCommand; + + Time = DateTime.Now.ToString(); + } + } + + static partial class DebugConsole + { + public class Command + { + public readonly string[] names; + public readonly string help; + + private Action onExecute; + + /// + /// 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 Func GetValidArgs; + + 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, Func getValidArgs = null) + { + names = name.Split('|'); + this.help = help; + + this.onExecute = onExecute; + this.onClientExecute = onClientExecute; + this.onClientRequestExecute = onClientRequestExecute; + + this.GetValidArgs = getValidArgs; + } + + + /// + /// 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, Func getValidArgs = null) + { + names = name.Split('|'); + this.help = help; + + this.onExecute = onExecute; + this.onClientExecute = onExecute; + + this.GetValidArgs = getValidArgs; + } + + public void Execute(string[] args) + { + if (onExecute == null) return; + onExecute(args); + } + + public void ClientExecute(string[] args) + { + onClientExecute(args); + } + + public void ServerExecuteOnClientRequest(Client client, Vector2 cursorWorldPos, string[] args) + { + if (onClientRequestExecute == null) + { + if (onExecute == null) return; + onExecute(args); + } + else + { + onClientRequestExecute(client, cursorWorldPos, args); + } + } + } + + const int MaxMessages = 200; + + public static List Messages = new List(); + + public delegate void QuestionCallback(string answer); + private static QuestionCallback activeQuestionCallback; + + private static List commands = new List(); + public static List Commands + { + get { return commands; } + } + + private static string currentAutoCompletedCommand; + private static int currentAutoCompletedIndex; + + //used for keeping track of the message entered when pressing up/down + static int selectedIndex; + + private static List unsavedMessages = new List(); + private static int messagesPerFile = 800; + public const string SavePath = "ConsoleLogs"; + + static DebugConsole() + { + commands.Add(new Command("help", "", (string[] args) => + { + if (args.Length == 0) + { + foreach (Command c in commands) + { + if (string.IsNullOrEmpty(c.help)) continue; + NewMessage(c.help, Color.Cyan); + } + } + else + { + var matchingCommand = commands.Find(c => c.names.Any(name => name == args[0])); + if (matchingCommand == null) + { + NewMessage("Command " + args[0] + " not found.", Color.Red); + } + else + { + NewMessage(matchingCommand.help, Color.Cyan); + } + } + })); + + commands.Add(new Command("clientlist", "clientlist: List all the clients connected to the server.", (string[] args) => + { + if (GameMain.Server == null) return; + NewMessage("***************", Color.Cyan); + foreach (Client c in GameMain.Server.ConnectedClients) + { + NewMessage("- " + c.ID.ToString() + ": " + c.Name + (c.Character != null ? " playing " + c.Character.LogName : "") + ", " + c.Connection.RemoteEndPoint.Address.ToString(), Color.Cyan); + } + NewMessage("***************", Color.Cyan); + }, null, + (Client client, Vector2 cursorWorldPos, string[] args) => + { + GameMain.Server.SendConsoleMessage("***************", client); + foreach (Client c in GameMain.Server.ConnectedClients) + { + GameMain.Server.SendConsoleMessage("- " + c.ID.ToString() + ": " + c.Name + ", " + c.Connection.RemoteEndPoint.Address.ToString(), client); + } + GameMain.Server.SendConsoleMessage("***************", client); + })); + + commands.Add(new Command("traitorlist", "traitorlist: List all the traitors and their targets.", (string[] args) => + { + if (GameMain.Server == null) return; + TraitorManager traitorManager = GameMain.Server.TraitorManager; + if (traitorManager == null) return; + foreach (Traitor t in traitorManager.TraitorList) + { + NewMessage("- Traitor " + t.Character.Name + "'s target is " + t.TargetCharacter.Name + ".", Color.Cyan); + } + NewMessage("The code words are: " + traitorManager.codeWords + ", response: " + traitorManager.codeResponse + ".", Color.Cyan); + }, + null, + (Client client, Vector2 cursorPos, string[] args) => + { + TraitorManager traitorManager = GameMain.Server.TraitorManager; + if (traitorManager == null) return; + foreach (Traitor t in traitorManager.TraitorList) + { + GameMain.Server.SendConsoleMessage("- Traitor " + t.Character.Name + "'s target is " + t.TargetCharacter.Name + ".", client); + } GameMain.Server.SendConsoleMessage("The code words are: " + traitorManager.codeWords + ", response: " + traitorManager.codeResponse + ".", client); - })); - - commands.Add(new Command("itemlist", "itemlist: List all the item prefabs available for spawning.", (string[] args) => - { - NewMessage("***************", Color.Cyan); - foreach (MapEntityPrefab ep in MapEntityPrefab.List) - { - var itemPrefab = ep as ItemPrefab; - if (itemPrefab == null || itemPrefab.Name == null) continue; - NewMessage("- " + itemPrefab.Name, Color.Cyan); - } - NewMessage("***************", Color.Cyan); - })); - - commands.Add(new Command("setpassword|setserverpassword", "setpassword [password]: Changes the password of the server that's being hosted.", (string[] args) => - { - if (GameMain.Server == null || args.Length == 0) return; - GameMain.Server.SetPassword(args[0]); - })); - - commands.Add(new Command("createfilelist", "", (string[] args) => - { - UpdaterUtil.SaveFileList("filelist.xml"); - })); - - commands.Add(new Command("spawn|spawncharacter", "spawn [creaturename] [near/inside/outside/cursor]: Spawn a creature at a random spawnpoint (use the second parameter to only select spawnpoints near/inside/outside the submarine).", (string[] args) => - { - string errorMsg; - SpawnCharacter(args, GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), out errorMsg); - if (!string.IsNullOrWhiteSpace(errorMsg)) - { - ThrowError(errorMsg); - } - }, - null, - (Client client, Vector2 cursorPos, string[] args) => - { - string errorMsg; - SpawnCharacter(args, cursorPos, out errorMsg); - if (!string.IsNullOrWhiteSpace(errorMsg)) - { - ThrowError(errorMsg); - } - }, - () => - { - List characterFiles = GameMain.Config.SelectedContentPackage.GetFilesOfType(ContentType.Character); - for (int i = 0; i < characterFiles.Count; i++) - { - characterFiles[i] = Path.GetFileNameWithoutExtension(characterFiles[i]).ToLowerInvariant(); - } - - return new string[][] - { - characterFiles.ToArray(), - new string[] { "near", "inside", "outside", "cursor" } - }; - })); - - commands.Add(new Command("spawnitem", "spawnitem [itemname] [cursor/inventory/cargo/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) => - { - SpawnItem(args, GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), Character.Controlled, out string errorMsg); - if (!string.IsNullOrWhiteSpace(errorMsg)) - { - ThrowError(errorMsg); - } - }, - null, - (Client client, Vector2 cursorWorldPos, string[] args) => - { - SpawnItem(args, cursorWorldPos, client.Character, out string errorMsg); - if (!string.IsNullOrWhiteSpace(errorMsg)) - { - GameMain.Server.SendConsoleMessage(errorMsg, client); - } - }, - () => - { - List itemNames = new List(); - foreach (MapEntityPrefab prefab in MapEntityPrefab.List) - { - if (prefab is ItemPrefab itemPrefab) itemNames.Add(itemPrefab.Name); - } - - return new string[][] - { - itemNames.ToArray(), - new string[] { "cursor", "inventory" } - }; - })); - - - commands.Add(new Command("disablecrewai", "disablecrewai: Disable the AI of the NPCs in the crew.", (string[] args) => - { - 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.SendConsoleMessage("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.SendConsoleMessage("Crew AI enabled", client); - })); - - commands.Add(new Command("autorestart", "autorestart [true/false]: Enable or disable round auto-restart.", (string[] args) => - { - if (GameMain.Server == null) return; - bool enabled = GameMain.Server.AutoRestart; - if (args.Length > 0) - { - bool.TryParse(args[0], out enabled); - } - else - { - enabled = !enabled; - } - if (enabled != GameMain.Server.AutoRestart) - { - if (GameMain.Server.AutoRestartInterval <= 0) GameMain.Server.AutoRestartInterval = 10; - GameMain.Server.AutoRestartTimer = GameMain.Server.AutoRestartInterval; - GameMain.Server.AutoRestart = enabled; -#if CLIENT - GameMain.NetLobbyScreen.SetAutoRestart(enabled, GameMain.Server.AutoRestartTimer); -#endif - 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) => - { - if (GameMain.Server == null) return; - if (args.Length > 0) - { - int parsedInt = 0; - if (int.TryParse(args[0], out parsedInt)) - { - if (parsedInt >= 0) - { - GameMain.Server.AutoRestart = true; - GameMain.Server.AutoRestartInterval = parsedInt; - if (GameMain.Server.AutoRestartTimer >= GameMain.Server.AutoRestartInterval) GameMain.Server.AutoRestartTimer = GameMain.Server.AutoRestartInterval; - NewMessage("Autorestart interval set to " + GameMain.Server.AutoRestartInterval + " seconds.", Color.White); - } - else - { - GameMain.Server.AutoRestart = false; - NewMessage("Autorestart disabled.", Color.White); - } -#if CLIENT - GameMain.NetLobbyScreen.SetAutoRestart(GameMain.Server.AutoRestart, GameMain.Server.AutoRestartTimer); -#endif - GameMain.NetLobbyScreen.LastUpdateID++; - } - } - }, null, null)); - - commands.Add(new Command("autorestarttimer", "autorestarttimer [seconds]: Set the current autorestart countdown to the specified value.", (string[] args) => - { - if (GameMain.Server == null) return; - if (args.Length > 0) - { - int parsedInt = 0; - if (int.TryParse(args[0], out parsedInt)) - { - if (parsedInt >= 0) - { - GameMain.Server.AutoRestart = true; - GameMain.Server.AutoRestartTimer = parsedInt; - if (GameMain.Server.AutoRestartInterval <= GameMain.Server.AutoRestartTimer) GameMain.Server.AutoRestartInterval = GameMain.Server.AutoRestartTimer; - GameMain.NetLobbyScreen.LastUpdateID++; - NewMessage("Autorestart timer set to " + GameMain.Server.AutoRestartTimer + " seconds.", Color.White); - } - else - { - GameMain.Server.AutoRestart = false; - NewMessage("Autorestart disabled.", Color.White); - } -#if CLIENT - GameMain.NetLobbyScreen.SetAutoRestart(GameMain.Server.AutoRestart, GameMain.Server.AutoRestartTimer); -#endif - 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) => - { - if (GameMain.Server == null) return; - if (args.Length < 1) - { - NewMessage("giveperm [id]: Grants administrative permissions to the player with the specified client ID.", Color.Cyan); - return; - } - - int id; - int.TryParse(args[0], out id); - var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); - if (client == null) - { - ThrowError("Client id \"" + id + "\" not found."); - return; - } - - NewMessage("Valid permissions are:",Color.White); - NewMessage(" - all",Color.White); - foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) - { - NewMessage(" - " + permission.ToString(),Color.White); - } - ShowQuestionPrompt("Permission to grant to \"" + client.Name + "\"?", (perm) => - { - 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)) - { - 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; - } - - NewMessage("Valid permissions are:", Color.White); - NewMessage(" - all", Color.White); - foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) - { - NewMessage(" - " + permission.ToString(), Color.White); - } - 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.SendConsoleMessage("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.SendConsoleMessage(perm + " is not a valid permission!", senderClient); - return; - } - } - client.GivePermission(permission); - GameMain.Server.UpdateClientPermissions(client); - GameMain.Server.SendConsoleMessage("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) => - { - if (GameMain.Server == null) return; - if (args.Length < 1) - { - NewMessage("revokeperm [id]: Revokes administrative permissions to the player with the specified client ID.", Color.Cyan); - return; - } - - int id; - int.TryParse(args[0], out id); - var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); - if (client == null) - { - ThrowError("Client id \"" + id + "\" not found."); - return; - } - - NewMessage("Valid permissions are:", Color.White); - NewMessage(" - all", Color.White); - foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) - { - NewMessage(" - " + permission.ToString(), Color.White); - } - ShowQuestionPrompt("Permission to revoke from \"" + client.Name + "\"?", (perm) => - { - 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)) - { - 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; - } - - NewMessage("Valid permissions are:", Color.White); - NewMessage(" - all", Color.White); - foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) - { - NewMessage(" - " + permission.ToString(), Color.White); - } - - 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.SendConsoleMessage("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.SendConsoleMessage(perm + " is not a valid permission!", senderClient); - return; - } - } - client.RemovePermission(permission); - GameMain.Server.UpdateClientPermissions(client); - GameMain.Server.SendConsoleMessage("Revoked " + perm + " permissions from " + client.Name + ".", senderClient); - NewMessage(senderClient.Name + " revoked " + perm + " permissions from " + client.Name + ".", Color.White); - })); - - - commands.Add(new Command("giverank", "giverank [id]: Assigns a specific rank (= a set of administrative permissions) to the player with the specified client ID.", (string[] args) => - { - if (GameMain.Server == null) return; - if (args.Length < 1) - { - NewMessage("giverank [id]: Assigns a specific rank(= a set of administrative permissions) to the player with the specified client ID.", Color.Cyan); - return; - } - - int id; - int.TryParse(args[0], out id); - var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); - if (client == null) - { - ThrowError("Client id \"" + id + "\" not found."); - return; - } - - NewMessage("Valid ranks are:", Color.White); - foreach (PermissionPreset permissionPreset in PermissionPreset.List) - { - NewMessage(" - " + permissionPreset.Name, Color.White); - } - - ShowQuestionPrompt("Rank to grant to \"" + client.Name + "\"?", (rank) => - { - PermissionPreset preset = PermissionPreset.List.Find(p => p.Name.ToLowerInvariant() == rank.ToLowerInvariant()); - if (preset == null) - { - ThrowError("Rank \"" + rank + "\" not found."); - return; - } - - client.SetPermissions(preset.Permissions, preset.PermittedCommands); - GameMain.Server.UpdateClientPermissions(client); - NewMessage("Assigned the rank \"" + preset.Name + "\" 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; - } - - NewMessage("Valid ranks are:", Color.White); - foreach (PermissionPreset permissionPreset in PermissionPreset.List) - { - NewMessage(" - " + permissionPreset.Name, Color.White); - } - ShowQuestionPrompt("Rank to grant to client #" + id + "?", (rank) => - { - GameMain.Client.SendConsoleCommand("giverank " + id + " " + rank); - }); -#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.SendConsoleMessage("Client id \"" + id + "\" not found.", senderClient); - return; - } - - string rank = string.Join("", args.Skip(1)); - PermissionPreset preset = PermissionPreset.List.Find(p => p.Name.ToLowerInvariant() == rank.ToLowerInvariant()); - if (preset == null) - { - GameMain.Server.SendConsoleMessage("Rank \"" + rank + "\" not found.", senderClient); - return; - } - - client.SetPermissions(preset.Permissions, preset.PermittedCommands); - GameMain.Server.UpdateClientPermissions(client); - GameMain.Server.SendConsoleMessage("Assigned the rank \"" + preset.Name + "\" to " + client.Name + ".", senderClient); - NewMessage(senderClient.Name + " granted the rank \"" + preset.Name + "\" to " + client.Name + ".", Color.White); - })); - - commands.Add(new Command("givecommandperm", "givecommandperm [id]: Gives the player with the specified client ID the permission to use the specified console commands.", (string[] args) => - { - if (GameMain.Server == null) return; - if (args.Length < 1) - { - NewMessage("givecommandperm [id]: Gives the player with the specified client ID the permission to use the specified console commands.", Color.Cyan); - return; - } - - int id; - int.TryParse(args[0], out id); - var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); - if (client == null) - { - ThrowError("Client id \"" + id + "\" not found."); - return; - } - - ShowQuestionPrompt("Console command permissions to grant to \"" + client.Name + "\"? You may enter multiple commands separated with a space.", (commandsStr) => - { - string[] splitCommands = commandsStr.Split(' '); - List grantedCommands = new List(); - for (int i = 0; i < splitCommands.Length; i++) - { - splitCommands[i] = splitCommands[i].Trim().ToLowerInvariant(); - Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i])); - if (matchingCommand == null) - { - ThrowError("Could not find the command \"" + splitCommands[i] + "\"!"); - } - else - { - grantedCommands.Add(matchingCommand); - } - } - - client.GivePermission(ClientPermissions.ConsoleCommands); - client.SetPermissions(client.Permissions, client.PermittedConsoleCommands.Union(grantedCommands).Distinct().ToList()); - GameMain.Server.UpdateClientPermissions(client); - NewMessage("Gave the client \"" + client.Name + "\" the permission to use console commands " + string.Join(", ", grantedCommands.Select(c => c.names[0])) + ".", 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("Console command permissions to grant to client #" + id + "? You may enter multiple commands separated with a space.", (commandNames) => - { - GameMain.Client.SendConsoleCommand("givecommandperm " + id + " " + commandNames); - }); -#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.SendConsoleMessage("Client id \"" + id + "\" not found.", senderClient); - return; - } - - string[] splitCommands = args.Skip(1).ToArray(); - List grantedCommands = new List(); - for (int i = 0; i < splitCommands.Length; i++) - { - splitCommands[i] = splitCommands[i].Trim().ToLowerInvariant(); - Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i])); - if (matchingCommand == null) - { - GameMain.Server.SendConsoleMessage("Could not find the command \"" + splitCommands[i] + "\"!", senderClient); - } - else - { - grantedCommands.Add(matchingCommand); - } - } - - client.GivePermission(ClientPermissions.ConsoleCommands); - client.SetPermissions(client.Permissions, client.PermittedConsoleCommands.Union(grantedCommands).Distinct().ToList()); - GameMain.Server.UpdateClientPermissions(client); - GameMain.Server.SendConsoleMessage("Gave the client \"" + client.Name + "\" the permission to use the console commands " + string.Join(", ", grantedCommands.Select(c => c.names[0])) + ".", senderClient); - NewMessage("Gave the client \"" + client.Name + "\" the permission to use the console commands " + string.Join(", ", grantedCommands.Select(c => c.names[0])) + ".", Color.White); - })); - - - commands.Add(new Command("revokecommandperm", "revokecommandperm [id]: Revokes permission to use the specified console commands from the player with the specified client ID.", (string[] args) => - { - if (GameMain.Server == null) return; - if (args.Length < 1) - { - NewMessage("revokecommandperm [id]: Revokes permission to use the specified console commands from the player with the specified client ID.", Color.Cyan); - return; - } - - int id; - int.TryParse(args[0], out id); - var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); - if (client == null) - { - ThrowError("Client id \"" + id + "\" not found."); - return; - } - - ShowQuestionPrompt("Console command permissions to revoke from \"" + client.Name + "\"? You may enter multiple commands separated with a space.", (commandsStr) => - { - string[] splitCommands = commandsStr.Split(' '); - List revokedCommands = new List(); - for (int i = 0; i < splitCommands.Length; i++) - { - splitCommands[i] = splitCommands[i].Trim().ToLowerInvariant(); - Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i])); - if (matchingCommand == null) - { - ThrowError("Could not find the command \"" + splitCommands[i] + "\"!"); - } - else - { - revokedCommands.Add(matchingCommand); - } - } - - client.SetPermissions(client.Permissions, client.PermittedConsoleCommands.Except(revokedCommands).ToList()); - GameMain.Server.UpdateClientPermissions(client); - NewMessage("Revoked \"" + client.Name + "\"'s permission to use the console commands " + string.Join(", ", revokedCommands.Select(c => c.names[0])) + ".", 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("Console command permissions to grant to client #" + id + "? You may enter multiple commands separated with a space.", (commandNames) => - { - GameMain.Client.SendConsoleCommand("givecommandperm " + id + " " + commandNames); - }); -#endif - }, - (Client senderClient, Vector2 cursorWorldPos, string[] args) => - { - if (args.Length < 2) return; - - int.TryParse(args[0], out int id); - var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); - if (client == null) - { - GameMain.Server.SendConsoleMessage("Client id \"" + id + "\" not found.", senderClient); - return; - } - - string[] splitCommands = args.Skip(1).ToArray(); - List revokedCommands = new List(); - for (int i = 0; i < splitCommands.Length; i++) - { - splitCommands[i] = splitCommands[i].Trim().ToLowerInvariant(); - Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i])); - if (matchingCommand == null) - { - GameMain.Server.SendConsoleMessage("Could not find the command \"" + splitCommands[i] + "\"!", senderClient); - } - else - { - revokedCommands.Add(matchingCommand); - } - } - - client.GivePermission(ClientPermissions.ConsoleCommands); - client.SetPermissions(client.Permissions, client.PermittedConsoleCommands.Except(revokedCommands).ToList()); - GameMain.Server.UpdateClientPermissions(client); - GameMain.Server.SendConsoleMessage("Revoked \"" + client.Name + "\"'s permission to use the console commands " + string.Join(", ", revokedCommands.Select(c => c.names[0])) + ".", senderClient); - NewMessage(senderClient.Name + " revoked \"" + client.Name + "\"'s permission to use the console commands " + string.Join(", ", revokedCommands.Select(c => c.names[0])) + ".", Color.White); - })); - - - commands.Add(new Command("showperm", "showperm [id]: Shows the current administrative permissions of the client with the specified client ID.", (string[] args) => - { - if (GameMain.Server == null) return; - if (args.Length < 1) - { - NewMessage("showperm [id]: Shows the current administrative permissions of the client with the specified client ID.", Color.Cyan); - return; - } - - int.TryParse(args[0], out int id); - var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); - if (client == null) - { - ThrowError("Client id \"" + id + "\" not found."); - return; - } - - if (client.Permissions == ClientPermissions.None) - { - NewMessage(client.Name + " has no special permissions.", Color.White); - return; - } - - NewMessage(client.Name + " has the following permissions:", Color.White); - foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) - { - if (permission == ClientPermissions.None || !client.HasPermission(permission)) continue; - System.Reflection.FieldInfo fi = typeof(ClientPermissions).GetField(permission.ToString()); - DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); - NewMessage(" - " + attributes[0].Description, Color.White); - } - if (client.HasPermission(ClientPermissions.ConsoleCommands)) - { - if (client.PermittedConsoleCommands.Count == 0) - { - NewMessage("No permitted console commands:", Color.White); - } - else - { - NewMessage("Permitted console commands:", Color.White); - foreach (Command permittedCommand in client.PermittedConsoleCommands) - { - NewMessage(" - " + permittedCommand.names[0], 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; - } - - GameMain.Client.SendConsoleCommand("showperm " + id); -#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.SendConsoleMessage("Client id \"" + id + "\" not found.", senderClient); - return; - } - - if (client.Permissions == ClientPermissions.None) - { - GameMain.Server.SendConsoleMessage(client.Name + " has no special permissions.", senderClient); - return; - } - - GameMain.Server.SendConsoleMessage(client.Name + " has the following permissions:", senderClient); - foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) - { - if (permission == ClientPermissions.None || !client.HasPermission(permission)) continue; - System.Reflection.FieldInfo fi = typeof(ClientPermissions).GetField(permission.ToString()); - DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); - GameMain.Server.SendConsoleMessage(" - " + attributes[0].Description, senderClient); - } - if (client.HasPermission(ClientPermissions.ConsoleCommands)) - { - if (client.PermittedConsoleCommands.Count == 0) - { - GameMain.Server.SendConsoleMessage("No permitted console commands:", senderClient); - } - else - { - GameMain.Server.SendConsoleMessage("Permitted console commands:", senderClient); - foreach (Command permittedCommand in client.PermittedConsoleCommands) - { - GameMain.Server.SendConsoleMessage(" - " + permittedCommand.names[0], senderClient); - } - } - } - })); - - commands.Add(new Command("togglekarma", "togglekarma: Toggles the karma system.", (string[] args) => - { - throw new NotImplementedException(); - 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; - - string playerName = string.Join(" ", args); - - ShowQuestionPrompt("Reason for kicking \"" + playerName + "\"?", (reason) => - { - GameMain.NetworkMember.KickPlayer(playerName, reason); - }); - }, - () => - { - if (GameMain.NetworkMember == null) return null; - - return new string[][] - { - GameMain.NetworkMember.ConnectedClients.Select(c => c.Name).ToArray() - }; - })); - - commands.Add(new Command("kickid", "kickid [id]: Kick the player with the specified client ID out of the server.", (string[] args) => - { - if (GameMain.NetworkMember == null || args.Length == 0) return; - - 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 kicking \"" + client.Name + "\"?", (reason) => - { - GameMain.NetworkMember.KickPlayer(client.Name, reason); - }); - })); - - commands.Add(new Command("ban", "ban [name]: Kick and ban the player from the server.", (string[] args) => - { - if (GameMain.NetworkMember == null || args.Length == 0) return; - - string clientName = string.Join(" ", args); - ShowQuestionPrompt("Reason for banning \"" + clientName + "\"?", (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)) - { - TimeSpan parsedBanDuration; - if (!TryParseTimeSpan(duration, out 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.NetworkMember.BanPlayer(clientName, reason, false, banDuration); - }); - }); - }, - () => - { - if (GameMain.NetworkMember == null) return null; - - return new string[][] - { - GameMain.NetworkMember.ConnectedClients.Select(c => c.Name).ToArray() - }; - })); - - commands.Add(new Command("banid", "banid [id]: Kick and ban the player with the specified client ID from the server.", (string[] args) => - { - if (GameMain.NetworkMember == null || args.Length == 0) return; - - 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) => - { - 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.NetworkMember.BanPlayer(client.Name, reason, false, banDuration); - }); - }); - })); - - - commands.Add(new Command("banip", "banip [ip]: Ban the IP address from the server.", (string[] args) => - { - if (GameMain.Server == 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; - } - - var clients = GameMain.Server.ConnectedClients.FindAll(c => c.Connection.RemoteEndPoint.Address.ToString() == args[0]); - if (clients.Count == 0) - { - GameMain.Server.BanList.BanPlayer("Unnamed", args[0], reason, banDuration); - } - else - { - foreach (Client cl in clients) - { - GameMain.Server.BanClient(cl, reason, false, banDuration); - } - } - }); - }); - }, - (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 = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args, false); - if (tpCharacter == null) return; - - var cam = GameMain.GameScreen.Cam; - tpCharacter.AnimController.CurrentHull = null; - 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 = (args.Length == 0) ? client.Character : 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); - }, - () => - { - return new string[][] - { - Character.CharacterList.Select(c => c.Name).Distinct().ToArray() - }; - })); - - commands.Add(new Command("godmode", "godmode: Toggle submarine godmode. Makes the main submarine invulnerable to damage.", (string[] args) => - { - if (Submarine.MainSub == null) return; - - 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.SendConsoleMessage(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", "locky: Lock vertical movement of the main submarine.", (string[] args) => - { - Submarine.LockY = !Submarine.LockY; - }, null, null)); - - commands.Add(new Command("dumpids", "", (string[] args) => - { - try - { - int count = args.Length == 0 ? 10 : int.Parse(args[0]); - Entity.DumpIds(count); - } - catch (Exception e) - { - ThrowError("Failed to dump ids", e); - } - })); - - 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 = (args.Length == 0) ? Character.Controlled : 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 = (args.Length == 0) ? client.Character : 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); - } - }, - () => - { - return new string[][] - { - Character.CharacterList.Select(c => c.Name).Distinct().ToArray() - }; - })); - - 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 = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args); - if (revivedCharacter == null) return; - - revivedCharacter.Revive(); - 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; - } - } - }, - null, - (Client client, Vector2 cursorWorldPos, string[] args) => - { - Character revivedCharacter = (args.Length == 0) ? client.Character : FindMatchingCharacter(args); - if (revivedCharacter == null) return; - - revivedCharacter.Revive(); - 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; - } - } - }, - () => - { - return new string[][] - { - Character.CharacterList.Select(c => c.Name).Distinct().ToArray() - }; - })); - - 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("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 = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args); - if (ragdolledCharacter != null) - { - ragdolledCharacter.IsForceRagdolled = !ragdolledCharacter.IsForceRagdolled; - } - }, - null, - (Client client, Vector2 cursorWorldPos, string[] args) => - { - Character ragdolledCharacter = (args.Length == 0) ? client.Character : FindMatchingCharacter(args); - if (ragdolledCharacter != null) - { - ragdolledCharacter.IsForceRagdolled = !ragdolledCharacter.IsForceRagdolled; - } - }, - () => - { - return new string[][] - { - Character.CharacterList.Select(c => c.Name).Distinct().ToArray() - }; - })); - - commands.Add(new Command("freecamera|freecam", "freecam: Detach the camera from the controlled character.", (string[] args) => - { - Character.Controlled = null; - GameMain.GameScreen.Cam.TargetPos = Vector2.Zero; - })); - - commands.Add(new Command("water|editwater", "water/editwater: Toggle water editing. Allows adding water into rooms by holding the left mouse button and removing it by holding the right mouse button.", (string[] args) => - { - if (GameMain.Client == null) - { - Hull.EditWater = !Hull.EditWater; - NewMessage(Hull.EditWater ? "Water editing on" : "Water editing off", Color.White); - } - })); - - commands.Add(new Command("fire|editfire", "fire/editfire: Allows putting up fires by left clicking.", (string[] args) => - { - if (GameMain.Client == null) - { - Hull.EditFire = !Hull.EditFire; - NewMessage(Hull.EditFire ? "Fire spawning on" : "Fire spawning off", Color.White); - } - })); - - commands.Add(new Command("explosion", "explosion [range] [force] [damage] [structuredamage] [emp strength]: Creates an explosion at the position of the cursor.", (string[] args) => - { - Vector2 explosionPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition); - float range = 500, force = 10, damage = 50, structureDamage = 10, empStrength = 0.0f; - 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); - if (args.Length > 4) float.TryParse(args[4], out empStrength); - new Explosion(range, force, damage, structureDamage, empStrength).Explode(explosionPos); - }, - null, - (Client client, Vector2 cursorWorldPos, string[] args) => - { - Vector2 explosionPos = cursorWorldPos; - float range = 500, force = 10, damage = 50, structureDamage = 10, empStrength = 0.0f; ; - 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); - if (args.Length > 4) float.TryParse(args[4], out empStrength); - new Explosion(range, force, damage, structureDamage, empStrength).Explode(explosionPos); - })); - - commands.Add(new Command("fixitems", "fixitems: Repairs all items and restores them to full condition.", (string[] args) => - { - foreach (Item it in Item.ItemList) - { - it.Condition = it.Prefab.Health; - } - }, null, null)); - - commands.Add(new Command("fixhulls|fixwalls", "fixwalls/fixhulls: Fixes all walls.", (string[] args) => - { - foreach (Structure w in Structure.WallList) - { - for (int i = 0; i < w.SectionCount; i++) - { - 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) => - { - Item reactorItem = Item.ItemList.Find(i => i.GetComponent() != null); - if (reactorItem == null) return; - - float power = 5000.0f; - if (args.Length > 0) float.TryParse(args[0], out power); - - var reactor = reactorItem.GetComponent(); - reactor.ShutDownTemp = power == 0 ? 0 : 7000.0f; - reactor.AutoTemp = true; - reactor.Temperature = power; - - if (GameMain.Server != null) - { - reactorItem.CreateServerEvent(reactor); - } - }, null, null)); - - commands.Add(new Command("oxygen|air", "oxygen/air: Replenishes the oxygen levels in every room to 100%.", (string[] args) => - { - foreach (Hull hull in Hull.hullList) - { - hull.OxygenPercentage = 100.0f; - } - }, null, null)); - - commands.Add(new Command("kill", "kill [character]: Immediately kills the specified character.", (string[] args) => - { - 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); - }, - () => - { - return new string[][] - { - Character.CharacterList.Select(c => c.Name).Distinct().ToArray() - }; - })); - - commands.Add(new Command("killmonsters", "killmonsters: Immediately kills all AI-controlled enemies in the level.", (string[] args) => - { - foreach (Character c in Character.CharacterList) - { - if (!(c.AIController is EnemyAIController)) continue; - c.AddDamage(CauseOfDeath.Damage, c.MaxHealth * 2, null); - } - }, null, null)); - - commands.Add(new Command("netstats", "netstats: Toggles the visibility of the network statistics UI.", (string[] args) => - { - if (GameMain.Server == null) return; - GameMain.Server.ShowNetStats = !GameMain.Server.ShowNetStats; - })); - - commands.Add(new Command("setclientcharacter", "setclientcharacter [client name] ; [character name]: Gives the client control of the specified character.", (string[] args) => - { - 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]\""); - 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) - { - 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.SendConsoleMessage("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.SendConsoleMessage("Client \"" + clientName + "\" not found.", senderClient); - } - - var character = FindMatchingCharacter(argsRight, false); - GameMain.Server.SetClientCharacter(client, character); - }, - () => - { - if (GameMain.NetworkMember == null) return null; - - return new string[][] - { - GameMain.NetworkMember.ConnectedClients.Select(c => c.Name).ToArray(), - Character.CharacterList.Select(c => c.Name).Distinct().ToArray() - }; - })); - - commands.Add(new Command("campaigninfo|campaignstatus", "campaigninfo: Display information about the state of the currently active campaign.", (string[] args) => - { - var campaign = GameMain.GameSession?.GameMode as CampaignMode; - if (campaign == null) - { - ThrowError("No campaign active!"); - return; - } - - campaign.LogState(); - })); - - commands.Add(new Command("campaigndestination|setcampaigndestination", "campaigndestination [index]: Set the location to head towards in the currently active campaign.", (string[] args) => - { - 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; - } - Location location = campaign.Map.CurrentLocation.Connections[destinationIndex].OtherLocation(campaign.Map.CurrentLocation); - campaign.Map.SelectLocation(location); - NewMessage(location.Name+" selected.", Color.White); - }); - } - 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; - } - Location location = campaign.Map.CurrentLocation.Connections[destinationIndex].OtherLocation(campaign.Map.CurrentLocation); - 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.SendConsoleMessage("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.SendConsoleMessage("Index out of bounds!", senderClient); - return; - } - Location location = campaign.Map.CurrentLocation.Connections[destinationIndex].OtherLocation(campaign.Map.CurrentLocation); - campaign.Map.SelectLocation(location); - GameMain.Server.SendConsoleMessage(location.Name + " selected.", senderClient); - })); - -#if DEBUG - commands.Add(new Command("spamevents", "A debug command that immediately creates entity events for all items, characters and structures.", (string[] args) => - { - foreach (Item item in Item.ItemList) - { - for (int i = 0; i < item.components.Count; i++) - { - if (item.components[i] is IServerSerializable) - { - GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ComponentState, i }); - } - var itemContainer = item.GetComponent(); - if (itemContainer != null) - { - GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.InventoryState, 0 }); - } - - GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.Status }); - - item.NeedsPositionUpdate = true; - } - } - - foreach (Character c in Character.CharacterList) - { - GameMain.Server.CreateEntityEvent(c, new object[] { NetEntityEvent.Type.Status }); - } - - foreach (Structure wall in Structure.WallList) - { - GameMain.Server.CreateEntityEvent(wall); - } - }, null, null)); - - commands.Add(new Command("flipx", "flipx: mirror the main submarine horizontally", (string[] args) => - { - Submarine.MainSub?.FlipX(); - })); -#endif - InitProjectSpecific(); - - commands.Sort((c1, c2) => c1.names[0].CompareTo(c2.names[0])); - } - - private static string[] SplitCommand(string command) - { - command = command.Trim(); - - List commands = new List(); - int escape = 0; - bool inQuotes = false; - string piece = ""; - - for (int i = 0; i < command.Length; i++) - { - if (command[i] == '\\') - { - if (escape == 0) escape = 2; - else piece += '\\'; - } - else if (command[i] == '"') - { - if (escape == 0) inQuotes = !inQuotes; - else piece += '"'; - } - else if (command[i] == ' ' && !inQuotes) - { - if (!string.IsNullOrWhiteSpace(piece)) commands.Add(piece); - piece = ""; - } - else if (escape == 0) piece += command[i]; - - if (escape > 0) escape--; - } - - if (!string.IsNullOrWhiteSpace(piece)) commands.Add(piece); //add final piece - - return commands.ToArray(); - } - - public static string AutoComplete(string command) - { - string[] splitCommand = SplitCommand(command); - string[] args = splitCommand.Skip(1).ToArray(); - - //if an argument is given or the last character is a space, attempt to autocomplete the argument - if (args.Length > 0 || (command.Length > 0 && command.Last() == ' ')) - { - Command matchingCommand = commands.Find(c => c.names.Contains(splitCommand[0])); - if (matchingCommand == null || matchingCommand.GetValidArgs == null) return command; - - int autoCompletedArgIndex = args.Length > 0 && command.Last() != ' ' ? args.Length - 1 : args.Length; - - //get all valid arguments for the given command - string[][] allArgs = matchingCommand.GetValidArgs(); - if (allArgs == null || allArgs.GetLength(0) < autoCompletedArgIndex + 1) return command; - - if (string.IsNullOrEmpty(currentAutoCompletedCommand)) - { - currentAutoCompletedCommand = autoCompletedArgIndex > args.Length - 1 ? " " : args.Last(); - } - - //find all valid autocompletions for the given argument - string[] validArgs = allArgs[autoCompletedArgIndex].Where(arg => - currentAutoCompletedCommand.Trim().Length <= arg.Length && - arg.Substring(0, currentAutoCompletedCommand.Trim().Length).ToLower() == currentAutoCompletedCommand.Trim().ToLower()).ToArray(); - - if (validArgs.Length == 0) return command; - - currentAutoCompletedIndex = currentAutoCompletedIndex % validArgs.Length; - string autoCompletedArg = validArgs[currentAutoCompletedIndex++]; - - //add quotation marks to args that contain spaces - if (autoCompletedArg.Contains(' ')) autoCompletedArg = '"' + autoCompletedArg + '"'; - for (int i = 0; i < splitCommand.Length; i++) - { - if (splitCommand[i].Contains(' ')) splitCommand[i] = '"' + splitCommand[i] + '"'; - } - - return string.Join(" ", autoCompletedArgIndex >= args.Length ? splitCommand : splitCommand.Take(splitCommand.Length - 1)) + " " + autoCompletedArg; - } - else - { - if (string.IsNullOrWhiteSpace(currentAutoCompletedCommand)) - { - currentAutoCompletedCommand = command; - } - - List matchingCommands = new List(); - foreach (Command c in commands) - { - foreach (string name in c.names) - { - if (currentAutoCompletedCommand.Length > name.Length) continue; - if (currentAutoCompletedCommand == name.Substring(0, currentAutoCompletedCommand.Length)) - { - matchingCommands.Add(name); - } - } - } - - if (matchingCommands.Count == 0) return command; - - currentAutoCompletedIndex = currentAutoCompletedIndex % matchingCommands.Count; - return matchingCommands[currentAutoCompletedIndex++]; - } - } - - private static string AutoCompleteStr(string str, IEnumerable validStrings) - { - if (string.IsNullOrEmpty(str)) return str; - foreach (string validStr in validStrings) - { - if (validStr.Length > str.Length && validStr.Substring(0, str.Length) == str) return validStr; - } - return str; - } - - public static void ResetAutoComplete() - { - currentAutoCompletedCommand = ""; - currentAutoCompletedIndex = 0; - } - - public static string SelectMessage(int direction) - { - if (Messages.Count == 0) return ""; - - direction = MathHelper.Clamp(direction, -1, 1); - - int i = 0; - do - { - selectedIndex += direction; - if (selectedIndex < 0) selectedIndex = Messages.Count - 1; - selectedIndex = selectedIndex % Messages.Count; - if (++i >= Messages.Count) break; - } while (!Messages[selectedIndex].IsCommand); - - return Messages[selectedIndex].Text; - } - - public static void ExecuteCommand(string command) - { - if (activeQuestionCallback != null) - { -#if CLIENT - activeQuestionText = null; -#endif - NewMessage(command, Color.White, true); - //reset the variable before invoking the delegate because the method may need to activate another question - var temp = activeQuestionCallback; - activeQuestionCallback = null; - temp(command); - return; - } - - if (string.IsNullOrWhiteSpace(command) || command == "\\" || command == "\n") return; - - string[] splitCommand = SplitCommand(command); + })); + + commands.Add(new Command("itemlist", "itemlist: List all the item prefabs available for spawning.", (string[] args) => + { + NewMessage("***************", Color.Cyan); + foreach (MapEntityPrefab ep in MapEntityPrefab.List) + { + var itemPrefab = ep as ItemPrefab; + if (itemPrefab == null || itemPrefab.Name == null) continue; + NewMessage("- " + itemPrefab.Name, Color.Cyan); + } + NewMessage("***************", Color.Cyan); + })); + + commands.Add(new Command("setpassword|setserverpassword", "setpassword [password]: Changes the password of the server that's being hosted.", (string[] args) => + { + if (GameMain.Server == null || args.Length == 0) return; + GameMain.Server.SetPassword(args[0]); + })); + + commands.Add(new Command("createfilelist", "", (string[] args) => + { + UpdaterUtil.SaveFileList("filelist.xml"); + })); + + commands.Add(new Command("spawn|spawncharacter", "spawn [creaturename] [near/inside/outside/cursor]: Spawn a creature at a random spawnpoint (use the second parameter to only select spawnpoints near/inside/outside the submarine).", (string[] args) => + { + string errorMsg; + SpawnCharacter(args, GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), out errorMsg); + if (!string.IsNullOrWhiteSpace(errorMsg)) + { + ThrowError(errorMsg); + } + }, + null, + (Client client, Vector2 cursorPos, string[] args) => + { + string errorMsg; + SpawnCharacter(args, cursorPos, out errorMsg); + if (!string.IsNullOrWhiteSpace(errorMsg)) + { + ThrowError(errorMsg); + } + }, + () => + { + List characterFiles = GameMain.Config.SelectedContentPackage.GetFilesOfType(ContentType.Character); + for (int i = 0; i < characterFiles.Count; i++) + { + characterFiles[i] = Path.GetFileNameWithoutExtension(characterFiles[i]).ToLowerInvariant(); + } + + return new string[][] + { + characterFiles.ToArray(), + new string[] { "near", "inside", "outside", "cursor" } + }; + })); + + commands.Add(new Command("spawnitem", "spawnitem [itemname] [cursor/inventory/cargo/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) => + { + SpawnItem(args, GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), Character.Controlled, out string errorMsg); + if (!string.IsNullOrWhiteSpace(errorMsg)) + { + ThrowError(errorMsg); + } + }, + null, + (Client client, Vector2 cursorWorldPos, string[] args) => + { + SpawnItem(args, cursorWorldPos, client.Character, out string errorMsg); + if (!string.IsNullOrWhiteSpace(errorMsg)) + { + GameMain.Server.SendConsoleMessage(errorMsg, client); + } + }, + () => + { + List itemNames = new List(); + foreach (MapEntityPrefab prefab in MapEntityPrefab.List) + { + if (prefab is ItemPrefab itemPrefab) itemNames.Add(itemPrefab.Name); + } + + return new string[][] + { + itemNames.ToArray(), + new string[] { "cursor", "inventory" } + }; + })); + + + commands.Add(new Command("disablecrewai", "disablecrewai: Disable the AI of the NPCs in the crew.", (string[] args) => + { + 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.SendConsoleMessage("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.SendConsoleMessage("Crew AI enabled", client); + })); + + commands.Add(new Command("autorestart", "autorestart [true/false]: Enable or disable round auto-restart.", (string[] args) => + { + if (GameMain.Server == null) return; + bool enabled = GameMain.Server.AutoRestart; + if (args.Length > 0) + { + bool.TryParse(args[0], out enabled); + } + else + { + enabled = !enabled; + } + if (enabled != GameMain.Server.AutoRestart) + { + if (GameMain.Server.AutoRestartInterval <= 0) GameMain.Server.AutoRestartInterval = 10; + GameMain.Server.AutoRestartTimer = GameMain.Server.AutoRestartInterval; + GameMain.Server.AutoRestart = enabled; +#if CLIENT + GameMain.NetLobbyScreen.SetAutoRestart(enabled, GameMain.Server.AutoRestartTimer); +#endif + 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) => + { + if (GameMain.Server == null) return; + if (args.Length > 0) + { + int parsedInt = 0; + if (int.TryParse(args[0], out parsedInt)) + { + if (parsedInt >= 0) + { + GameMain.Server.AutoRestart = true; + GameMain.Server.AutoRestartInterval = parsedInt; + if (GameMain.Server.AutoRestartTimer >= GameMain.Server.AutoRestartInterval) GameMain.Server.AutoRestartTimer = GameMain.Server.AutoRestartInterval; + NewMessage("Autorestart interval set to " + GameMain.Server.AutoRestartInterval + " seconds.", Color.White); + } + else + { + GameMain.Server.AutoRestart = false; + NewMessage("Autorestart disabled.", Color.White); + } +#if CLIENT + GameMain.NetLobbyScreen.SetAutoRestart(GameMain.Server.AutoRestart, GameMain.Server.AutoRestartTimer); +#endif + GameMain.NetLobbyScreen.LastUpdateID++; + } + } + }, null, null)); + + commands.Add(new Command("autorestarttimer", "autorestarttimer [seconds]: Set the current autorestart countdown to the specified value.", (string[] args) => + { + if (GameMain.Server == null) return; + if (args.Length > 0) + { + int parsedInt = 0; + if (int.TryParse(args[0], out parsedInt)) + { + if (parsedInt >= 0) + { + GameMain.Server.AutoRestart = true; + GameMain.Server.AutoRestartTimer = parsedInt; + if (GameMain.Server.AutoRestartInterval <= GameMain.Server.AutoRestartTimer) GameMain.Server.AutoRestartInterval = GameMain.Server.AutoRestartTimer; + GameMain.NetLobbyScreen.LastUpdateID++; + NewMessage("Autorestart timer set to " + GameMain.Server.AutoRestartTimer + " seconds.", Color.White); + } + else + { + GameMain.Server.AutoRestart = false; + NewMessage("Autorestart disabled.", Color.White); + } +#if CLIENT + GameMain.NetLobbyScreen.SetAutoRestart(GameMain.Server.AutoRestart, GameMain.Server.AutoRestartTimer); +#endif + 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) => + { + if (GameMain.Server == null) return; + if (args.Length < 1) + { + NewMessage("giveperm [id]: Grants administrative permissions to the player with the specified client ID.", Color.Cyan); + return; + } + + int id; + int.TryParse(args[0], out id); + var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); + if (client == null) + { + ThrowError("Client id \"" + id + "\" not found."); + return; + } + + NewMessage("Valid permissions are:",Color.White); + NewMessage(" - all",Color.White); + foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) + { + NewMessage(" - " + permission.ToString(),Color.White); + } + ShowQuestionPrompt("Permission to grant to \"" + client.Name + "\"?", (perm) => + { + 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)) + { + 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; + } + + NewMessage("Valid permissions are:", Color.White); + NewMessage(" - all", Color.White); + foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) + { + NewMessage(" - " + permission.ToString(), Color.White); + } + 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.SendConsoleMessage("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.SendConsoleMessage(perm + " is not a valid permission!", senderClient); + return; + } + } + client.GivePermission(permission); + GameMain.Server.UpdateClientPermissions(client); + GameMain.Server.SendConsoleMessage("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) => + { + if (GameMain.Server == null) return; + if (args.Length < 1) + { + NewMessage("revokeperm [id]: Revokes administrative permissions to the player with the specified client ID.", Color.Cyan); + return; + } + + int id; + int.TryParse(args[0], out id); + var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); + if (client == null) + { + ThrowError("Client id \"" + id + "\" not found."); + return; + } + + NewMessage("Valid permissions are:", Color.White); + NewMessage(" - all", Color.White); + foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) + { + NewMessage(" - " + permission.ToString(), Color.White); + } + ShowQuestionPrompt("Permission to revoke from \"" + client.Name + "\"?", (perm) => + { + 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)) + { + 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; + } + + NewMessage("Valid permissions are:", Color.White); + NewMessage(" - all", Color.White); + foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) + { + NewMessage(" - " + permission.ToString(), Color.White); + } + + 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.SendConsoleMessage("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.SendConsoleMessage(perm + " is not a valid permission!", senderClient); + return; + } + } + client.RemovePermission(permission); + GameMain.Server.UpdateClientPermissions(client); + GameMain.Server.SendConsoleMessage("Revoked " + perm + " permissions from " + client.Name + ".", senderClient); + NewMessage(senderClient.Name + " revoked " + perm + " permissions from " + client.Name + ".", Color.White); + })); + + + commands.Add(new Command("giverank", "giverank [id]: Assigns a specific rank (= a set of administrative permissions) to the player with the specified client ID.", (string[] args) => + { + if (GameMain.Server == null) return; + if (args.Length < 1) + { + NewMessage("giverank [id]: Assigns a specific rank(= a set of administrative permissions) to the player with the specified client ID.", Color.Cyan); + return; + } + + int id; + int.TryParse(args[0], out id); + var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); + if (client == null) + { + ThrowError("Client id \"" + id + "\" not found."); + return; + } + + NewMessage("Valid ranks are:", Color.White); + foreach (PermissionPreset permissionPreset in PermissionPreset.List) + { + NewMessage(" - " + permissionPreset.Name, Color.White); + } + + ShowQuestionPrompt("Rank to grant to \"" + client.Name + "\"?", (rank) => + { + PermissionPreset preset = PermissionPreset.List.Find(p => p.Name.ToLowerInvariant() == rank.ToLowerInvariant()); + if (preset == null) + { + ThrowError("Rank \"" + rank + "\" not found."); + return; + } + + client.SetPermissions(preset.Permissions, preset.PermittedCommands); + GameMain.Server.UpdateClientPermissions(client); + NewMessage("Assigned the rank \"" + preset.Name + "\" 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; + } + + NewMessage("Valid ranks are:", Color.White); + foreach (PermissionPreset permissionPreset in PermissionPreset.List) + { + NewMessage(" - " + permissionPreset.Name, Color.White); + } + ShowQuestionPrompt("Rank to grant to client #" + id + "?", (rank) => + { + GameMain.Client.SendConsoleCommand("giverank " + id + " " + rank); + }); +#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.SendConsoleMessage("Client id \"" + id + "\" not found.", senderClient); + return; + } + + string rank = string.Join("", args.Skip(1)); + PermissionPreset preset = PermissionPreset.List.Find(p => p.Name.ToLowerInvariant() == rank.ToLowerInvariant()); + if (preset == null) + { + GameMain.Server.SendConsoleMessage("Rank \"" + rank + "\" not found.", senderClient); + return; + } + + client.SetPermissions(preset.Permissions, preset.PermittedCommands); + GameMain.Server.UpdateClientPermissions(client); + GameMain.Server.SendConsoleMessage("Assigned the rank \"" + preset.Name + "\" to " + client.Name + ".", senderClient); + NewMessage(senderClient.Name + " granted the rank \"" + preset.Name + "\" to " + client.Name + ".", Color.White); + })); + + commands.Add(new Command("givecommandperm", "givecommandperm [id]: Gives the player with the specified client ID the permission to use the specified console commands.", (string[] args) => + { + if (GameMain.Server == null) return; + if (args.Length < 1) + { + NewMessage("givecommandperm [id]: Gives the player with the specified client ID the permission to use the specified console commands.", Color.Cyan); + return; + } + + int id; + int.TryParse(args[0], out id); + var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); + if (client == null) + { + ThrowError("Client id \"" + id + "\" not found."); + return; + } + + ShowQuestionPrompt("Console command permissions to grant to \"" + client.Name + "\"? You may enter multiple commands separated with a space.", (commandsStr) => + { + string[] splitCommands = commandsStr.Split(' '); + List grantedCommands = new List(); + for (int i = 0; i < splitCommands.Length; i++) + { + splitCommands[i] = splitCommands[i].Trim().ToLowerInvariant(); + Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i])); + if (matchingCommand == null) + { + ThrowError("Could not find the command \"" + splitCommands[i] + "\"!"); + } + else + { + grantedCommands.Add(matchingCommand); + } + } + + client.GivePermission(ClientPermissions.ConsoleCommands); + client.SetPermissions(client.Permissions, client.PermittedConsoleCommands.Union(grantedCommands).Distinct().ToList()); + GameMain.Server.UpdateClientPermissions(client); + NewMessage("Gave the client \"" + client.Name + "\" the permission to use console commands " + string.Join(", ", grantedCommands.Select(c => c.names[0])) + ".", 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("Console command permissions to grant to client #" + id + "? You may enter multiple commands separated with a space.", (commandNames) => + { + GameMain.Client.SendConsoleCommand("givecommandperm " + id + " " + commandNames); + }); +#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.SendConsoleMessage("Client id \"" + id + "\" not found.", senderClient); + return; + } + + string[] splitCommands = args.Skip(1).ToArray(); + List grantedCommands = new List(); + for (int i = 0; i < splitCommands.Length; i++) + { + splitCommands[i] = splitCommands[i].Trim().ToLowerInvariant(); + Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i])); + if (matchingCommand == null) + { + GameMain.Server.SendConsoleMessage("Could not find the command \"" + splitCommands[i] + "\"!", senderClient); + } + else + { + grantedCommands.Add(matchingCommand); + } + } + + client.GivePermission(ClientPermissions.ConsoleCommands); + client.SetPermissions(client.Permissions, client.PermittedConsoleCommands.Union(grantedCommands).Distinct().ToList()); + GameMain.Server.UpdateClientPermissions(client); + GameMain.Server.SendConsoleMessage("Gave the client \"" + client.Name + "\" the permission to use the console commands " + string.Join(", ", grantedCommands.Select(c => c.names[0])) + ".", senderClient); + NewMessage("Gave the client \"" + client.Name + "\" the permission to use the console commands " + string.Join(", ", grantedCommands.Select(c => c.names[0])) + ".", Color.White); + })); + + + commands.Add(new Command("revokecommandperm", "revokecommandperm [id]: Revokes permission to use the specified console commands from the player with the specified client ID.", (string[] args) => + { + if (GameMain.Server == null) return; + if (args.Length < 1) + { + NewMessage("revokecommandperm [id]: Revokes permission to use the specified console commands from the player with the specified client ID.", Color.Cyan); + return; + } + + int id; + int.TryParse(args[0], out id); + var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); + if (client == null) + { + ThrowError("Client id \"" + id + "\" not found."); + return; + } + + ShowQuestionPrompt("Console command permissions to revoke from \"" + client.Name + "\"? You may enter multiple commands separated with a space.", (commandsStr) => + { + string[] splitCommands = commandsStr.Split(' '); + List revokedCommands = new List(); + for (int i = 0; i < splitCommands.Length; i++) + { + splitCommands[i] = splitCommands[i].Trim().ToLowerInvariant(); + Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i])); + if (matchingCommand == null) + { + ThrowError("Could not find the command \"" + splitCommands[i] + "\"!"); + } + else + { + revokedCommands.Add(matchingCommand); + } + } + + client.SetPermissions(client.Permissions, client.PermittedConsoleCommands.Except(revokedCommands).ToList()); + GameMain.Server.UpdateClientPermissions(client); + NewMessage("Revoked \"" + client.Name + "\"'s permission to use the console commands " + string.Join(", ", revokedCommands.Select(c => c.names[0])) + ".", 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("Console command permissions to grant to client #" + id + "? You may enter multiple commands separated with a space.", (commandNames) => + { + GameMain.Client.SendConsoleCommand("givecommandperm " + id + " " + commandNames); + }); +#endif + }, + (Client senderClient, Vector2 cursorWorldPos, string[] args) => + { + if (args.Length < 2) return; + + int.TryParse(args[0], out int id); + var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); + if (client == null) + { + GameMain.Server.SendConsoleMessage("Client id \"" + id + "\" not found.", senderClient); + return; + } + + string[] splitCommands = args.Skip(1).ToArray(); + List revokedCommands = new List(); + for (int i = 0; i < splitCommands.Length; i++) + { + splitCommands[i] = splitCommands[i].Trim().ToLowerInvariant(); + Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i])); + if (matchingCommand == null) + { + GameMain.Server.SendConsoleMessage("Could not find the command \"" + splitCommands[i] + "\"!", senderClient); + } + else + { + revokedCommands.Add(matchingCommand); + } + } + + client.GivePermission(ClientPermissions.ConsoleCommands); + client.SetPermissions(client.Permissions, client.PermittedConsoleCommands.Except(revokedCommands).ToList()); + GameMain.Server.UpdateClientPermissions(client); + GameMain.Server.SendConsoleMessage("Revoked \"" + client.Name + "\"'s permission to use the console commands " + string.Join(", ", revokedCommands.Select(c => c.names[0])) + ".", senderClient); + NewMessage(senderClient.Name + " revoked \"" + client.Name + "\"'s permission to use the console commands " + string.Join(", ", revokedCommands.Select(c => c.names[0])) + ".", Color.White); + })); + + + commands.Add(new Command("showperm", "showperm [id]: Shows the current administrative permissions of the client with the specified client ID.", (string[] args) => + { + if (GameMain.Server == null) return; + if (args.Length < 1) + { + NewMessage("showperm [id]: Shows the current administrative permissions of the client with the specified client ID.", Color.Cyan); + return; + } + + int.TryParse(args[0], out int id); + var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); + if (client == null) + { + ThrowError("Client id \"" + id + "\" not found."); + return; + } + + if (client.Permissions == ClientPermissions.None) + { + NewMessage(client.Name + " has no special permissions.", Color.White); + return; + } + + NewMessage(client.Name + " has the following permissions:", Color.White); + foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) + { + if (permission == ClientPermissions.None || !client.HasPermission(permission)) continue; + System.Reflection.FieldInfo fi = typeof(ClientPermissions).GetField(permission.ToString()); + DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); + NewMessage(" - " + attributes[0].Description, Color.White); + } + if (client.HasPermission(ClientPermissions.ConsoleCommands)) + { + if (client.PermittedConsoleCommands.Count == 0) + { + NewMessage("No permitted console commands:", Color.White); + } + else + { + NewMessage("Permitted console commands:", Color.White); + foreach (Command permittedCommand in client.PermittedConsoleCommands) + { + NewMessage(" - " + permittedCommand.names[0], 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; + } + + GameMain.Client.SendConsoleCommand("showperm " + id); +#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.SendConsoleMessage("Client id \"" + id + "\" not found.", senderClient); + return; + } + + if (client.Permissions == ClientPermissions.None) + { + GameMain.Server.SendConsoleMessage(client.Name + " has no special permissions.", senderClient); + return; + } + + GameMain.Server.SendConsoleMessage(client.Name + " has the following permissions:", senderClient); + foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) + { + if (permission == ClientPermissions.None || !client.HasPermission(permission)) continue; + System.Reflection.FieldInfo fi = typeof(ClientPermissions).GetField(permission.ToString()); + DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); + GameMain.Server.SendConsoleMessage(" - " + attributes[0].Description, senderClient); + } + if (client.HasPermission(ClientPermissions.ConsoleCommands)) + { + if (client.PermittedConsoleCommands.Count == 0) + { + GameMain.Server.SendConsoleMessage("No permitted console commands:", senderClient); + } + else + { + GameMain.Server.SendConsoleMessage("Permitted console commands:", senderClient); + foreach (Command permittedCommand in client.PermittedConsoleCommands) + { + GameMain.Server.SendConsoleMessage(" - " + permittedCommand.names[0], senderClient); + } + } + } + })); + + commands.Add(new Command("togglekarma", "togglekarma: Toggles the karma system.", (string[] args) => + { + throw new NotImplementedException(); + 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; + + string playerName = string.Join(" ", args); + + ShowQuestionPrompt("Reason for kicking \"" + playerName + "\"?", (reason) => + { + GameMain.NetworkMember.KickPlayer(playerName, reason); + }); + }, + () => + { + if (GameMain.NetworkMember == null) return null; + + return new string[][] + { + GameMain.NetworkMember.ConnectedClients.Select(c => c.Name).ToArray() + }; + })); + + commands.Add(new Command("kickid", "kickid [id]: Kick the player with the specified client ID out of the server.", (string[] args) => + { + if (GameMain.NetworkMember == null || args.Length == 0) return; + + 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 kicking \"" + client.Name + "\"?", (reason) => + { + GameMain.NetworkMember.KickPlayer(client.Name, reason); + }); + })); + + commands.Add(new Command("ban", "ban [name]: Kick and ban the player from the server.", (string[] args) => + { + if (GameMain.NetworkMember == null || args.Length == 0) return; + + string clientName = string.Join(" ", args); + ShowQuestionPrompt("Reason for banning \"" + clientName + "\"?", (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)) + { + TimeSpan parsedBanDuration; + if (!TryParseTimeSpan(duration, out 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.NetworkMember.BanPlayer(clientName, reason, false, banDuration); + }); + }); + }, + () => + { + if (GameMain.NetworkMember == null) return null; + + return new string[][] + { + GameMain.NetworkMember.ConnectedClients.Select(c => c.Name).ToArray() + }; + })); + + commands.Add(new Command("banid", "banid [id]: Kick and ban the player with the specified client ID from the server.", (string[] args) => + { + if (GameMain.NetworkMember == null || args.Length == 0) return; + + 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) => + { + 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.NetworkMember.BanPlayer(client.Name, reason, false, banDuration); + }); + }); + })); + + + commands.Add(new Command("banip", "banip [ip]: Ban the IP address from the server.", (string[] args) => + { + if (GameMain.Server == 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; + } + + var clients = GameMain.Server.ConnectedClients.FindAll(c => c.Connection.RemoteEndPoint.Address.ToString() == args[0]); + if (clients.Count == 0) + { + GameMain.Server.BanList.BanPlayer("Unnamed", args[0], reason, banDuration); + } + else + { + foreach (Client cl in clients) + { + GameMain.Server.BanClient(cl, reason, false, banDuration); + } + } + }); + }); + }, + (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 = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args, false); + if (tpCharacter == null) return; + + var cam = GameMain.GameScreen.Cam; + tpCharacter.AnimController.CurrentHull = null; + 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 = (args.Length == 0) ? client.Character : 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); + }, + () => + { + return new string[][] + { + Character.CharacterList.Select(c => c.Name).Distinct().ToArray() + }; + })); + + commands.Add(new Command("godmode", "godmode: Toggle submarine godmode. Makes the main submarine invulnerable to damage.", (string[] args) => + { + if (Submarine.MainSub == null) return; + + 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.SendConsoleMessage(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", "locky: Lock vertical movement of the main submarine.", (string[] args) => + { + Submarine.LockY = !Submarine.LockY; + }, null, null)); + + commands.Add(new Command("dumpids", "", (string[] args) => + { + try + { + int count = args.Length == 0 ? 10 : int.Parse(args[0]); + Entity.DumpIds(count); + } + catch (Exception e) + { + ThrowError("Failed to dump ids", e); + } + })); + + 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 = (args.Length == 0) ? Character.Controlled : 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 = (args.Length == 0) ? client.Character : 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); + } + }, + () => + { + return new string[][] + { + Character.CharacterList.Select(c => c.Name).Distinct().ToArray() + }; + })); + + 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 = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args); + if (revivedCharacter == null) return; + + revivedCharacter.Revive(); + 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; + } + } + }, + null, + (Client client, Vector2 cursorWorldPos, string[] args) => + { + Character revivedCharacter = (args.Length == 0) ? client.Character : FindMatchingCharacter(args); + if (revivedCharacter == null) return; + + revivedCharacter.Revive(); + 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; + } + } + }, + () => + { + return new string[][] + { + Character.CharacterList.Select(c => c.Name).Distinct().ToArray() + }; + })); + + 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("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 = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args); + if (ragdolledCharacter != null) + { + ragdolledCharacter.IsForceRagdolled = !ragdolledCharacter.IsForceRagdolled; + } + }, + null, + (Client client, Vector2 cursorWorldPos, string[] args) => + { + Character ragdolledCharacter = (args.Length == 0) ? client.Character : FindMatchingCharacter(args); + if (ragdolledCharacter != null) + { + ragdolledCharacter.IsForceRagdolled = !ragdolledCharacter.IsForceRagdolled; + } + }, + () => + { + return new string[][] + { + Character.CharacterList.Select(c => c.Name).Distinct().ToArray() + }; + })); + + commands.Add(new Command("freecamera|freecam", "freecam: Detach the camera from the controlled character.", (string[] args) => + { + Character.Controlled = null; + GameMain.GameScreen.Cam.TargetPos = Vector2.Zero; + })); + + commands.Add(new Command("water|editwater", "water/editwater: Toggle water editing. Allows adding water into rooms by holding the left mouse button and removing it by holding the right mouse button.", (string[] args) => + { + if (GameMain.Client == null) + { + Hull.EditWater = !Hull.EditWater; + NewMessage(Hull.EditWater ? "Water editing on" : "Water editing off", Color.White); + } + })); + + commands.Add(new Command("fire|editfire", "fire/editfire: Allows putting up fires by left clicking.", (string[] args) => + { + if (GameMain.Client == null) + { + Hull.EditFire = !Hull.EditFire; + NewMessage(Hull.EditFire ? "Fire spawning on" : "Fire spawning off", Color.White); + } + })); + + commands.Add(new Command("explosion", "explosion [range] [force] [damage] [structuredamage] [emp strength]: Creates an explosion at the position of the cursor.", (string[] args) => + { + Vector2 explosionPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition); + float range = 500, force = 10, damage = 50, structureDamage = 10, empStrength = 0.0f; + 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); + if (args.Length > 4) float.TryParse(args[4], out empStrength); + new Explosion(range, force, damage, structureDamage, empStrength).Explode(explosionPos); + }, + null, + (Client client, Vector2 cursorWorldPos, string[] args) => + { + Vector2 explosionPos = cursorWorldPos; + float range = 500, force = 10, damage = 50, structureDamage = 10, empStrength = 0.0f; ; + 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); + if (args.Length > 4) float.TryParse(args[4], out empStrength); + new Explosion(range, force, damage, structureDamage, empStrength).Explode(explosionPos); + })); + + commands.Add(new Command("fixitems", "fixitems: Repairs all items and restores them to full condition.", (string[] args) => + { + foreach (Item it in Item.ItemList) + { + it.Condition = it.Prefab.Health; + } + }, null, null)); + + commands.Add(new Command("fixhulls|fixwalls", "fixwalls/fixhulls: Fixes all walls.", (string[] args) => + { + foreach (Structure w in Structure.WallList) + { + for (int i = 0; i < w.SectionCount; i++) + { + 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) => + { + Item reactorItem = Item.ItemList.Find(i => i.GetComponent() != null); + if (reactorItem == null) return; + + float power = 5000.0f; + if (args.Length > 0) float.TryParse(args[0], out power); + + var reactor = reactorItem.GetComponent(); + reactor.ShutDownTemp = power == 0 ? 0 : 7000.0f; + reactor.AutoTemp = true; + reactor.Temperature = power; + + if (GameMain.Server != null) + { + reactorItem.CreateServerEvent(reactor); + } + }, null, null)); + + commands.Add(new Command("oxygen|air", "oxygen/air: Replenishes the oxygen levels in every room to 100%.", (string[] args) => + { + foreach (Hull hull in Hull.hullList) + { + hull.OxygenPercentage = 100.0f; + } + }, null, null)); + + commands.Add(new Command("kill", "kill [character]: Immediately kills the specified character.", (string[] args) => + { + 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); + }, + () => + { + return new string[][] + { + Character.CharacterList.Select(c => c.Name).Distinct().ToArray() + }; + })); + + commands.Add(new Command("killmonsters", "killmonsters: Immediately kills all AI-controlled enemies in the level.", (string[] args) => + { + foreach (Character c in Character.CharacterList) + { + if (!(c.AIController is EnemyAIController)) continue; + c.AddDamage(CauseOfDeath.Damage, c.MaxHealth * 2, null); + } + }, null, null)); + + commands.Add(new Command("netstats", "netstats: Toggles the visibility of the network statistics UI.", (string[] args) => + { + if (GameMain.Server == null) return; + GameMain.Server.ShowNetStats = !GameMain.Server.ShowNetStats; + })); + + commands.Add(new Command("setclientcharacter", "setclientcharacter [client name] ; [character name]: Gives the client control of the specified character.", (string[] args) => + { + 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]\""); + 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) + { + 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.SendConsoleMessage("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.SendConsoleMessage("Client \"" + clientName + "\" not found.", senderClient); + } + + var character = FindMatchingCharacter(argsRight, false); + GameMain.Server.SetClientCharacter(client, character); + }, + () => + { + if (GameMain.NetworkMember == null) return null; + + return new string[][] + { + GameMain.NetworkMember.ConnectedClients.Select(c => c.Name).ToArray(), + Character.CharacterList.Select(c => c.Name).Distinct().ToArray() + }; + })); + + commands.Add(new Command("campaigninfo|campaignstatus", "campaigninfo: Display information about the state of the currently active campaign.", (string[] args) => + { + var campaign = GameMain.GameSession?.GameMode as CampaignMode; + if (campaign == null) + { + ThrowError("No campaign active!"); + return; + } + + campaign.LogState(); + })); + + commands.Add(new Command("campaigndestination|setcampaigndestination", "campaigndestination [index]: Set the location to head towards in the currently active campaign.", (string[] args) => + { + 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; + } + Location location = campaign.Map.CurrentLocation.Connections[destinationIndex].OtherLocation(campaign.Map.CurrentLocation); + campaign.Map.SelectLocation(location); + NewMessage(location.Name+" selected.", Color.White); + }); + } + 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; + } + Location location = campaign.Map.CurrentLocation.Connections[destinationIndex].OtherLocation(campaign.Map.CurrentLocation); + 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.SendConsoleMessage("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.SendConsoleMessage("Index out of bounds!", senderClient); + return; + } + Location location = campaign.Map.CurrentLocation.Connections[destinationIndex].OtherLocation(campaign.Map.CurrentLocation); + campaign.Map.SelectLocation(location); + GameMain.Server.SendConsoleMessage(location.Name + " selected.", senderClient); + })); + +#if DEBUG + commands.Add(new Command("spamevents", "A debug command that immediately creates entity events for all items, characters and structures.", (string[] args) => + { + foreach (Item item in Item.ItemList) + { + for (int i = 0; i < item.components.Count; i++) + { + if (item.components[i] is IServerSerializable) + { + GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ComponentState, i }); + } + var itemContainer = item.GetComponent(); + if (itemContainer != null) + { + GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.InventoryState, 0 }); + } + + GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.Status }); + + item.NeedsPositionUpdate = true; + } + } + + foreach (Character c in Character.CharacterList) + { + GameMain.Server.CreateEntityEvent(c, new object[] { NetEntityEvent.Type.Status }); + } + + foreach (Structure wall in Structure.WallList) + { + GameMain.Server.CreateEntityEvent(wall); + } + }, null, null)); + + commands.Add(new Command("flipx", "flipx: mirror the main submarine horizontally", (string[] args) => + { + Submarine.MainSub?.FlipX(); + })); +#endif + InitProjectSpecific(); + + commands.Sort((c1, c2) => c1.names[0].CompareTo(c2.names[0])); + } + + private static string[] SplitCommand(string command) + { + command = command.Trim(); + + List commands = new List(); + int escape = 0; + bool inQuotes = false; + string piece = ""; + + for (int i = 0; i < command.Length; i++) + { + if (command[i] == '\\') + { + if (escape == 0) escape = 2; + else piece += '\\'; + } + else if (command[i] == '"') + { + if (escape == 0) inQuotes = !inQuotes; + else piece += '"'; + } + else if (command[i] == ' ' && !inQuotes) + { + if (!string.IsNullOrWhiteSpace(piece)) commands.Add(piece); + piece = ""; + } + else if (escape == 0) piece += command[i]; + + if (escape > 0) escape--; + } + + if (!string.IsNullOrWhiteSpace(piece)) commands.Add(piece); //add final piece + + return commands.ToArray(); + } + + public static string AutoComplete(string command) + { + string[] splitCommand = SplitCommand(command); + string[] args = splitCommand.Skip(1).ToArray(); + + //if an argument is given or the last character is a space, attempt to autocomplete the argument + if (args.Length > 0 || (command.Length > 0 && command.Last() == ' ')) + { + Command matchingCommand = commands.Find(c => c.names.Contains(splitCommand[0])); + if (matchingCommand == null || matchingCommand.GetValidArgs == null) return command; + + int autoCompletedArgIndex = args.Length > 0 && command.Last() != ' ' ? args.Length - 1 : args.Length; + + //get all valid arguments for the given command + string[][] allArgs = matchingCommand.GetValidArgs(); + if (allArgs == null || allArgs.GetLength(0) < autoCompletedArgIndex + 1) return command; + + if (string.IsNullOrEmpty(currentAutoCompletedCommand)) + { + currentAutoCompletedCommand = autoCompletedArgIndex > args.Length - 1 ? " " : args.Last(); + } + + //find all valid autocompletions for the given argument + string[] validArgs = allArgs[autoCompletedArgIndex].Where(arg => + currentAutoCompletedCommand.Trim().Length <= arg.Length && + arg.Substring(0, currentAutoCompletedCommand.Trim().Length).ToLower() == currentAutoCompletedCommand.Trim().ToLower()).ToArray(); + + if (validArgs.Length == 0) return command; + + currentAutoCompletedIndex = currentAutoCompletedIndex % validArgs.Length; + string autoCompletedArg = validArgs[currentAutoCompletedIndex++]; + + //add quotation marks to args that contain spaces + if (autoCompletedArg.Contains(' ')) autoCompletedArg = '"' + autoCompletedArg + '"'; + for (int i = 0; i < splitCommand.Length; i++) + { + if (splitCommand[i].Contains(' ')) splitCommand[i] = '"' + splitCommand[i] + '"'; + } + + return string.Join(" ", autoCompletedArgIndex >= args.Length ? splitCommand : splitCommand.Take(splitCommand.Length - 1)) + " " + autoCompletedArg; + } + else + { + if (string.IsNullOrWhiteSpace(currentAutoCompletedCommand)) + { + currentAutoCompletedCommand = command; + } + + List matchingCommands = new List(); + foreach (Command c in commands) + { + foreach (string name in c.names) + { + if (currentAutoCompletedCommand.Length > name.Length) continue; + if (currentAutoCompletedCommand == name.Substring(0, currentAutoCompletedCommand.Length)) + { + matchingCommands.Add(name); + } + } + } + + if (matchingCommands.Count == 0) return command; + + currentAutoCompletedIndex = currentAutoCompletedIndex % matchingCommands.Count; + return matchingCommands[currentAutoCompletedIndex++]; + } + } + + private static string AutoCompleteStr(string str, IEnumerable validStrings) + { + if (string.IsNullOrEmpty(str)) return str; + foreach (string validStr in validStrings) + { + if (validStr.Length > str.Length && validStr.Substring(0, str.Length) == str) return validStr; + } + return str; + } + + public static void ResetAutoComplete() + { + currentAutoCompletedCommand = ""; + currentAutoCompletedIndex = 0; + } + + public static string SelectMessage(int direction) + { + if (Messages.Count == 0) return ""; + + direction = MathHelper.Clamp(direction, -1, 1); + + int i = 0; + do + { + selectedIndex += direction; + if (selectedIndex < 0) selectedIndex = Messages.Count - 1; + selectedIndex = selectedIndex % Messages.Count; + if (++i >= Messages.Count) break; + } while (!Messages[selectedIndex].IsCommand); + + return Messages[selectedIndex].Text; + } + + public static void ExecuteCommand(string command) + { + if (activeQuestionCallback != null) + { +#if CLIENT + activeQuestionText = null; +#endif + NewMessage(command, Color.White, true); + //reset the variable before invoking the delegate because the method may need to activate another question + var temp = activeQuestionCallback; + activeQuestionCallback = null; + temp(command); + return; + } + + if (string.IsNullOrWhiteSpace(command) || command == "\\" || command == "\n") return; + + string[] splitCommand = SplitCommand(command); if (splitCommand.Length == 0) { ThrowError("Failed to execute command \"" + command + "\"!"); @@ -1926,81 +1926,81 @@ namespace Barotrauma return; } - if (!splitCommand[0].ToLowerInvariant().Equals("admin")) - { - NewMessage(command, Color.White, true); - } - -#if CLIENT - if (GameMain.Client != null) - { - if (GameMain.Client.HasConsoleCommandPermission(splitCommand[0].ToLowerInvariant())) - { - 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 !DEBUG - if (!IsCommandPermitted(splitCommand[0].ToLowerInvariant(), GameMain.Client)) - { - ThrowError("You're not permitted to use the command \"" + splitCommand[0].ToLowerInvariant() + "\"!"); - return; - } -#endif - } -#endif - - bool commandFound = false; - foreach (Command c in commands) - { - if (c.names.Contains(splitCommand[0].ToLowerInvariant())) - { - c.Execute(splitCommand.Skip(1).ToArray()); - commandFound = true; - break; - } - } - - if (!commandFound) - { - 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.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; - } - - string[] splitCommand = SplitCommand(command); - Command matchingCommand = commands.Find(c => c.names.Contains(splitCommand[0].ToLowerInvariant())); - if (matchingCommand != null && !client.PermittedConsoleCommands.Contains(matchingCommand)) - { - 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.SendConsoleMessage("Command \"" + splitCommand[0] + "\" not found.", client); - return; + if (!splitCommand[0].ToLowerInvariant().Equals("admin")) + { + NewMessage(command, Color.White, true); + } + +#if CLIENT + if (GameMain.Client != null) + { + if (GameMain.Client.HasConsoleCommandPermission(splitCommand[0].ToLowerInvariant())) + { + 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 !DEBUG + if (!IsCommandPermitted(splitCommand[0].ToLowerInvariant(), GameMain.Client)) + { + ThrowError("You're not permitted to use the command \"" + splitCommand[0].ToLowerInvariant() + "\"!"); + return; + } +#endif + } +#endif + + bool commandFound = false; + foreach (Command c in commands) + { + if (c.names.Contains(splitCommand[0].ToLowerInvariant())) + { + c.Execute(splitCommand.Skip(1).ToArray()); + commandFound = true; + break; + } + } + + if (!commandFound) + { + 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.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; + } + + string[] splitCommand = SplitCommand(command); + Command matchingCommand = commands.Find(c => c.names.Contains(splitCommand[0].ToLowerInvariant())); + if (matchingCommand != null && !client.PermittedConsoleCommands.Contains(matchingCommand)) + { + 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.SendConsoleMessage("Command \"" + splitCommand[0] + "\" not found.", client); + return; } if (!MathUtils.IsValid(cursorWorldPos)) @@ -2008,200 +2008,200 @@ namespace Barotrauma GameMain.Server.SendConsoleMessage("Could not execute command \"" + command + "\" - invalid cursor position.", client); NewMessage(client.Name + " attempted to execute the console command \"" + command + "\" with invalid cursor position.", Color.White); 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); - } - } - - - private static Character FindMatchingCharacter(string[] args, bool ignoreRemotePlayers = false) - { - if (args.Length == 0) return null; - - int characterIndex; - string characterName; - if (int.TryParse(args.Last(), out characterIndex) && args.Length > 1) - { - characterName = string.Join(" ", args.Take(args.Length - 1)).ToLowerInvariant(); - } - else - { - characterName = string.Join(" ", args).ToLowerInvariant(); - characterIndex = -1; - } - - var matchingCharacters = Character.CharacterList.FindAll(c => (!ignoreRemotePlayers || !c.IsRemotePlayer) && c.Name.ToLowerInvariant() == characterName); - - if (!matchingCharacters.Any()) - { - NewMessage("Character \""+ characterName + "\" not found", Color.Red); - return null; - } - - if (characterIndex == -1) - { - if (matchingCharacters.Count > 1) - { - NewMessage( - "Found multiple matching characters. " + - "Use \"[charactername] [0-" + (matchingCharacters.Count - 1) + "]\" to choose a specific character.", - Color.LightGray); - } - return matchingCharacters[0]; - } - else if (characterIndex < 0 || characterIndex >= matchingCharacters.Count) - { - ThrowError("Character index out of range. Select an index between 0 and " + (matchingCharacters.Count - 1)); - } - else - { - return matchingCharacters[characterIndex]; - } - - 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); - } - } - + } + + 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); + } + } + + + private static Character FindMatchingCharacter(string[] args, bool ignoreRemotePlayers = false) + { + if (args.Length == 0) return null; + + int characterIndex; + string characterName; + if (int.TryParse(args.Last(), out characterIndex) && args.Length > 1) + { + characterName = string.Join(" ", args.Take(args.Length - 1)).ToLowerInvariant(); + } + else + { + characterName = string.Join(" ", args).ToLowerInvariant(); + characterIndex = -1; + } + + var matchingCharacters = Character.CharacterList.FindAll(c => (!ignoreRemotePlayers || !c.IsRemotePlayer) && c.Name.ToLowerInvariant() == characterName); + + if (!matchingCharacters.Any()) + { + NewMessage("Character \""+ characterName + "\" not found", Color.Red); + return null; + } + + if (characterIndex == -1) + { + if (matchingCharacters.Count > 1) + { + NewMessage( + "Found multiple matching characters. " + + "Use \"[charactername] [0-" + (matchingCharacters.Count - 1) + "]\" to choose a specific character.", + Color.LightGray); + } + return matchingCharacters[0]; + } + else if (characterIndex < 0 || characterIndex >= matchingCharacters.Count) + { + ThrowError("Character index out of range. Select an index between 0 and " + (matchingCharacters.Count - 1)); + } + else + { + return matchingCharacters[characterIndex]; + } + + 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, Character controlledCharacter, out string errorMsg) { - errorMsg = ""; - if (args.Length < 1) return; - - Vector2? spawnPos = null; - Inventory spawnInventory = null; - - int extraParams = 0; - switch (args.Last().ToLowerInvariant()) - { - case "cursor": - extraParams = 1; - spawnPos = cursorPos; + errorMsg = ""; + if (args.Length < 1) return; + + Vector2? spawnPos = null; + Inventory spawnInventory = null; + + int extraParams = 0; + switch (args.Last().ToLowerInvariant()) + { + case "cursor": + extraParams = 1; + spawnPos = cursorPos; break; - case "inventory": + case "inventory": extraParams = 1; spawnInventory = controlledCharacter == null ? null : controlledCharacter.Inventory; break; - case "cargo": - var wp = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub); + case "cargo": + var wp = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub); spawnPos = wp == null ? Vector2.Zero : wp.WorldPosition; break; //Dont do a thing, random is basically Human points anyways - its in the help description. - case "random": - extraParams = 1; - return; - default: - extraParams = 0; - break; - } - - string itemName = string.Join(" ", args.Take(args.Length - extraParams)).ToLowerInvariant(); - - ItemPrefab itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab; - if (itemPrefab == null && extraParams == 0) - { + case "random": + extraParams = 1; + return; + default: + extraParams = 0; + break; + } + + string itemName = string.Join(" ", args.Take(args.Length - extraParams)).ToLowerInvariant(); + + ItemPrefab itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab; + if (itemPrefab == null && extraParams == 0) + { if (GameMain.Server != null) { var client = GameMain.Server.ConnectedClients.Find(c => c.Name.ToLower() == args.Last().ToLower()); @@ -2211,199 +2211,199 @@ namespace Barotrauma itemName = string.Join(" ", args.Take(args.Length - extraParams)).ToLowerInvariant(); if (client.Character != null && client.Character.Name == args.Last().ToLower()) spawnInventory = client.Character.Inventory; itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab; - } - } - } - //Check again if the item can be found again after having checked for a character + } + } + } + //Check again if the item can be found again after having checked for a character if (itemPrefab == null) { - errorMsg = "Item \"" + itemName + "\" not found!"; + errorMsg = "Item \"" + itemName + "\" not found!"; return; - } - - if ((spawnPos == null || spawnPos == Vector2.Zero) && 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, bool isCommand = false) - { - if (string.IsNullOrEmpty((msg))) return; - -#if SERVER - var newMsg = new ColoredText(msg, color, isCommand); - Messages.Add(newMsg); - - //TODO: REMOVE - Console.ForegroundColor = XnaToConsoleColor.Convert(color); - Console.WriteLine(msg); - Console.ForegroundColor = ConsoleColor.White; - - if (GameSettings.SaveDebugConsoleLogs) - { - unsavedMessages.Add(newMsg); - if (unsavedMessages.Count >= messagesPerFile) - { - SaveLogs(); - unsavedMessages.Clear(); - } - } - - if (Messages.Count > MaxMessages) - { - Messages.RemoveRange(0, Messages.Count - MaxMessages); - } -#elif CLIENT - lock (queuedMessages) - { - queuedMessages.Enqueue(new ColoredText(msg, color, isCommand)); - } -#endif - } - - public static void ShowQuestionPrompt(string question, QuestionCallback onAnswered) - { - -#if CLIENT - 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) - { - timeSpan = new TimeSpan(); - if (string.IsNullOrWhiteSpace(s)) return false; - - string currNum = ""; - foreach (char c in s) - { - if (char.IsDigit(c)) - { - currNum += c; - } - else if (char.IsWhiteSpace(c)) - { - continue; - } - else - { - int parsedNum = 0; - if (!int.TryParse(currNum, out parsedNum)) - { - return false; - } - - switch (c) - { - case 'd': - timeSpan += new TimeSpan(parsedNum, 0, 0, 0, 0); - break; - case 'h': - timeSpan += new TimeSpan(0, parsedNum, 0, 0, 0); - break; - case 'm': - timeSpan += new TimeSpan(0, 0, parsedNum, 0, 0); - break; - case 's': - timeSpan += new TimeSpan(0, 0, 0, parsedNum, 0); - break; - default: - return false; - } - - currNum = ""; - } - } - - 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) - { - if (GameSettings.VerboseLogging) NewMessage(message, Color.Gray); - } - - public static void ThrowError(string error, Exception e = null) - { - if (e != null) - { - error += " {" + e.Message + "}\n" + e.StackTrace; - } - System.Diagnostics.Debug.WriteLine(error); - NewMessage(error, Color.Red); -#if CLIENT - isOpen = true; -#endif - } - - - public static void SaveLogs() - { - if (unsavedMessages.Count == 0) return; - if (!Directory.Exists(SavePath)) - { - try - { - Directory.CreateDirectory(SavePath); - } - catch (Exception e) - { - ThrowError("Failed to create a folder for debug console logs", e); - return; - } - } - - string fileName = "DebugConsoleLog_" + DateTime.Now.ToShortDateString() + "_" + DateTime.Now.ToShortTimeString() + ".txt"; - var invalidChars = Path.GetInvalidFileNameChars(); - foreach (char invalidChar in invalidChars) - { - fileName = fileName.Replace(invalidChar.ToString(), ""); - } - - string filePath = Path.Combine(SavePath, fileName); - if (File.Exists(filePath)) - { - int fileNum = 2; - while (File.Exists(filePath + " (" + fileNum + ")")) - { - fileNum++; - } - filePath = filePath + " (" + fileNum + ")"; - } - - try - { - File.WriteAllLines(filePath, unsavedMessages.Select(l => "[" + l.Time + "] " + l.Text)); - } - catch (Exception e) - { - unsavedMessages.Clear(); - ThrowError("Saving debug console log to " + filePath + " failed", e); - } - } - } -} + } + + if ((spawnPos == null || spawnPos == Vector2.Zero) && 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, bool isCommand = false) + { + if (string.IsNullOrEmpty((msg))) return; + +#if SERVER + var newMsg = new ColoredText(msg, color, isCommand); + Messages.Add(newMsg); + + //TODO: REMOVE + Console.ForegroundColor = XnaToConsoleColor.Convert(color); + Console.WriteLine(msg); + Console.ForegroundColor = ConsoleColor.White; + + if (GameSettings.SaveDebugConsoleLogs) + { + unsavedMessages.Add(newMsg); + if (unsavedMessages.Count >= messagesPerFile) + { + SaveLogs(); + unsavedMessages.Clear(); + } + } + + if (Messages.Count > MaxMessages) + { + Messages.RemoveRange(0, Messages.Count - MaxMessages); + } +#elif CLIENT + lock (queuedMessages) + { + queuedMessages.Enqueue(new ColoredText(msg, color, isCommand)); + } +#endif + } + + public static void ShowQuestionPrompt(string question, QuestionCallback onAnswered) + { + +#if CLIENT + 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) + { + timeSpan = new TimeSpan(); + if (string.IsNullOrWhiteSpace(s)) return false; + + string currNum = ""; + foreach (char c in s) + { + if (char.IsDigit(c)) + { + currNum += c; + } + else if (char.IsWhiteSpace(c)) + { + continue; + } + else + { + int parsedNum = 0; + if (!int.TryParse(currNum, out parsedNum)) + { + return false; + } + + switch (c) + { + case 'd': + timeSpan += new TimeSpan(parsedNum, 0, 0, 0, 0); + break; + case 'h': + timeSpan += new TimeSpan(0, parsedNum, 0, 0, 0); + break; + case 'm': + timeSpan += new TimeSpan(0, 0, parsedNum, 0, 0); + break; + case 's': + timeSpan += new TimeSpan(0, 0, 0, parsedNum, 0); + break; + default: + return false; + } + + currNum = ""; + } + } + + 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) + { + if (GameSettings.VerboseLogging) NewMessage(message, Color.Gray); + } + + public static void ThrowError(string error, Exception e = null) + { + if (e != null) + { + error += " {" + e.Message + "}\n" + e.StackTrace; + } + System.Diagnostics.Debug.WriteLine(error); + NewMessage(error, Color.Red); +#if CLIENT + isOpen = true; +#endif + } + + + public static void SaveLogs() + { + if (unsavedMessages.Count == 0) return; + if (!Directory.Exists(SavePath)) + { + try + { + Directory.CreateDirectory(SavePath); + } + catch (Exception e) + { + ThrowError("Failed to create a folder for debug console logs", e); + return; + } + } + + string fileName = "DebugConsoleLog_" + DateTime.Now.ToShortDateString() + "_" + DateTime.Now.ToShortTimeString() + ".txt"; + var invalidChars = Path.GetInvalidFileNameChars(); + foreach (char invalidChar in invalidChars) + { + fileName = fileName.Replace(invalidChar.ToString(), ""); + } + + string filePath = Path.Combine(SavePath, fileName); + if (File.Exists(filePath)) + { + int fileNum = 2; + while (File.Exists(filePath + " (" + fileNum + ")")) + { + fileNum++; + } + filePath = filePath + " (" + fileNum + ")"; + } + + try + { + File.WriteAllLines(filePath, unsavedMessages.Select(l => "[" + l.Time + "] " + l.Text)); + } + catch (Exception e) + { + unsavedMessages.Clear(); + ThrowError("Saving debug console log to " + filePath + " failed", e); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs b/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs index d1918e153..85fccbbde 100644 --- a/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs +++ b/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs @@ -1,12 +1,12 @@ -using Barotrauma.Items.Components; -using Microsoft.Xna.Framework; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Barotrauma -{ - class PurchasedItem +using Barotrauma.Items.Components; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Barotrauma +{ + class PurchasedItem { public ItemPrefab itemPrefab; public int quantity; @@ -16,140 +16,140 @@ namespace Barotrauma this.itemPrefab = itemPrefab; this.quantity = quantity; } - } - - class CargoManager - { - private readonly List purchasedItems; - - private readonly CampaignMode campaign; - - public Action OnItemsChanged; - - public List PurchasedItems - { - get { return purchasedItems; } - } - - public CargoManager(CampaignMode campaign) - { - purchasedItems = new List(); - this.campaign = campaign; - } - - public void SetPurchasedItems(List items) - { - purchasedItems.Clear(); - purchasedItems.AddRange(items); - - OnItemsChanged?.Invoke(); - } - - public void PurchaseItem(ItemPrefab item, int Quantity = 1) - { - PurchasedItem purchasedItem = PurchasedItems.Find(pi => pi.itemPrefab == item); - + } + + class CargoManager + { + private readonly List purchasedItems; + + private readonly CampaignMode campaign; + + public Action OnItemsChanged; + + public List PurchasedItems + { + get { return purchasedItems; } + } + + public CargoManager(CampaignMode campaign) + { + purchasedItems = new List(); + this.campaign = campaign; + } + + public void SetPurchasedItems(List items) + { + purchasedItems.Clear(); + purchasedItems.AddRange(items); + + OnItemsChanged?.Invoke(); + } + + public void PurchaseItem(ItemPrefab item, int Quantity = 1) + { + PurchasedItem purchasedItem = PurchasedItems.Find(pi => pi.itemPrefab == item); + if(purchasedItem != null && Quantity == 1) { campaign.Money -= item.Price; purchasedItem.quantity += 1; - } + } else { campaign.Money -= (item.Price * Quantity); purchasedItem = new PurchasedItem(item, Quantity); purchasedItems.Add(purchasedItem); - } - - OnItemsChanged?.Invoke(); - } - - public void SellItem(ItemPrefab item, int quantity = 1) - { - campaign.Money += (item.Price * quantity); - PurchasedItem purchasedItem = PurchasedItems.Find(pi => pi.itemPrefab == item); + } + + OnItemsChanged?.Invoke(); + } + + public void SellItem(ItemPrefab item, int quantity = 1) + { + campaign.Money += (item.Price * quantity); + PurchasedItem purchasedItem = PurchasedItems.Find(pi => pi.itemPrefab == item); if (purchasedItem != null && purchasedItem.quantity - quantity > 0) { purchasedItem.quantity -= quantity; - } + } else { PurchasedItems.Remove(purchasedItem); - } - - OnItemsChanged?.Invoke(); - } - - public int GetTotalItemCost() - { - return purchasedItems.Sum(i => (i.itemPrefab.Price * i.quantity)); - } - - public void CreateItems() - { - CreateItems(purchasedItems); - OnItemsChanged?.Invoke(); - } - - public static void CreateItems(List itemsToSpawn) - { - WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub); - - if (wp == null) - { - DebugConsole.ThrowError("The submarine must have a waypoint marked as Cargo for bought items to be placed correctly!"); - return; - } - - Hull cargoRoom = Hull.FindHull(wp.WorldPosition); - - if (cargoRoom == null) - { - DebugConsole.ThrowError("A waypoint marked as Cargo must be placed inside a room!"); - return; - } - - Dictionary availableContainers = new Dictionary(); - ItemPrefab containerPrefab = null; - foreach (PurchasedItem pi in itemsToSpawn) - { - Vector2 position = new Vector2( - Rand.Range(cargoRoom.Rect.X + 20, cargoRoom.Rect.Right - 20), - cargoRoom.Rect.Y - cargoRoom.Rect.Height + pi.itemPrefab.Size.Y / 2); - - ItemContainer itemContainer = null; - if (!string.IsNullOrEmpty(pi.itemPrefab.CargoContainerName)) - { - itemContainer = availableContainers.Keys.ToList().Find(ac => - ac.Item.Prefab.NameMatches(pi.itemPrefab.CargoContainerName) || - ac.Item.Prefab.Tags.Contains(pi.itemPrefab.CargoContainerName.ToLowerInvariant())); - - if (itemContainer == null) - { - containerPrefab = MapEntityPrefab.List.Find(ep => - ep.NameMatches(pi.itemPrefab.CargoContainerName) || - (ep.Tags != null && ep.Tags.Contains(pi.itemPrefab.CargoContainerName.ToLowerInvariant()))) as ItemPrefab; - - if (containerPrefab == null) - { - DebugConsole.ThrowError("Cargo spawning failed - could not find the item prefab for container \"" + containerPrefab.Name + "\"!"); - continue; - } - - Item containerItem = new Item(containerPrefab, position, wp.Submarine); - itemContainer = containerItem.GetComponent(); - if (itemContainer == null) - { - DebugConsole.ThrowError("Cargo spawning failed - container \"" + containerItem.Name + "\" does not have an ItemContainer component!"); - continue; - } - availableContainers.Add(itemContainer, itemContainer.Capacity); - if (GameMain.Server != null) - { - Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false); - } - } - } + } + + OnItemsChanged?.Invoke(); + } + + public int GetTotalItemCost() + { + return purchasedItems.Sum(i => (i.itemPrefab.Price * i.quantity)); + } + + public void CreateItems() + { + CreateItems(purchasedItems); + OnItemsChanged?.Invoke(); + } + + public static void CreateItems(List itemsToSpawn) + { + WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub); + + if (wp == null) + { + DebugConsole.ThrowError("The submarine must have a waypoint marked as Cargo for bought items to be placed correctly!"); + return; + } + + Hull cargoRoom = Hull.FindHull(wp.WorldPosition); + + if (cargoRoom == null) + { + DebugConsole.ThrowError("A waypoint marked as Cargo must be placed inside a room!"); + return; + } + + Dictionary availableContainers = new Dictionary(); + ItemPrefab containerPrefab = null; + foreach (PurchasedItem pi in itemsToSpawn) + { + Vector2 position = new Vector2( + Rand.Range(cargoRoom.Rect.X + 20, cargoRoom.Rect.Right - 20), + cargoRoom.Rect.Y - cargoRoom.Rect.Height + pi.itemPrefab.Size.Y / 2); + + ItemContainer itemContainer = null; + if (!string.IsNullOrEmpty(pi.itemPrefab.CargoContainerName)) + { + itemContainer = availableContainers.Keys.ToList().Find(ac => + ac.Item.Prefab.NameMatches(pi.itemPrefab.CargoContainerName) || + ac.Item.Prefab.Tags.Contains(pi.itemPrefab.CargoContainerName.ToLowerInvariant())); + + if (itemContainer == null) + { + containerPrefab = MapEntityPrefab.List.Find(ep => + ep.NameMatches(pi.itemPrefab.CargoContainerName) || + (ep.Tags != null && ep.Tags.Contains(pi.itemPrefab.CargoContainerName.ToLowerInvariant()))) as ItemPrefab; + + if (containerPrefab == null) + { + DebugConsole.ThrowError("Cargo spawning failed - could not find the item prefab for container \"" + containerPrefab.Name + "\"!"); + continue; + } + + Item containerItem = new Item(containerPrefab, position, wp.Submarine); + itemContainer = containerItem.GetComponent(); + if (itemContainer == null) + { + DebugConsole.ThrowError("Cargo spawning failed - container \"" + containerItem.Name + "\" does not have an ItemContainer component!"); + continue; + } + availableContainers.Add(itemContainer, itemContainer.Capacity); + if (GameMain.Server != null) + { + Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false); + } + } + } for (int i = 0; i < pi.quantity; i++) { if (itemContainer == null) @@ -199,9 +199,9 @@ namespace Barotrauma { availableContainers.Remove(itemContainer); } - } - } - itemsToSpawn.Clear(); - } - } -} + } + } + itemsToSpawn.Clear(); + } + } +} From 87002f8e0060d0ee7f72b56b3135eb17214e2cb3 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 30 Aug 2018 13:55:22 +0300 Subject: [PATCH 186/198] Added a gitattributes file that defines CRLF as the line ending for code files (cherry-picked from 1ebfc9e) --- .gitattributes | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..56f8e94c2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +# Declare files that will always have CRLF line endings on checkout. +*.sln text eol=crlf +*.cs text eol=crlf +*.xml text eol=crlf \ No newline at end of file From 4e1f6c56ca38c2d1cf0b4f48e96ad32c536cd497 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Sun, 2 Sep 2018 20:41:22 +0300 Subject: [PATCH 187/198] v0.8.2.0 --- .../Properties/AssemblyInfo.cs | 4 +- .../Properties/AssemblyInfo.cs | 4 +- Barotrauma/BarotraumaShared/changelog.txt | 60 +++++++++++++++++++ 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs index ee0f1eff0..48e397370 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.12")] -[assembly: AssemblyFileVersion("0.8.1.12")] +[assembly: AssemblyVersion("0.8.2.0")] +[assembly: AssemblyFileVersion("0.8.2.0")] diff --git a/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs index 145e7eaa6..07a711c56 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.12")] -[assembly: AssemblyFileVersion("0.8.1.12")] +[assembly: AssemblyVersion("0.8.2.0")] +[assembly: AssemblyFileVersion("0.8.2.0")] diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 18dc1244e..02c5daaaa 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,63 @@ +--------------------------------------------------------------------------------------------------------- +v0.8.2.0 +--------------------------------------------------------------------------------------------------------- + +Networking additions: + - Added a server setting for selecting which symbols are allowed in client names (see + AllowedClientNameChars in the server settings file). + - Custom servers can modify all editable item properties mid-round, not just in-game editable ones. + - Clients can be given access to server logs. + - Respawn durations can be changed mid-round. + - Servers have the option to disable the disguise feature. + - Increased midround syncing timeout. + +Misc changes: + - Levels are mirrored when travelling through the backwards in the campaign mode. + - Added colliders to railguns (so they cannot go through walls or enemy subs anymore). + - Melee weapons can hit multiple targets on one swing. Fixes weapons occasionally not hitting + the target in tight spaces due to touching the ceiling/walls first. + - The voltage required for a PowerTransfer item to take damage and the probability for a fire can be + configured in the item xmls. + - Docking ports and hatches aren't damaged by excess voltage. + - Added more color variants of wires. + - Characters point the harpoon gun down when not aiming. + - Added parameter autocompletion to the kill command. + - Added a property that can be used to lock connection panels but still keep the panel rewireable + in the submarine editor. + - Items outside the sub cannot be deattached from walls. + - Fabricators show the list of required items even if the character does not have the skills to craft + the item. + +Networking bugfixes: + - Fixed file transfers failing if the client disconnects during an active transfer, rejoins and + attempts to receive the same file. + - Fixed a bug in door syncing that caused the door states to differ between the server and clients + in some subs with more complex door wiring setups. + - Fixed clients being able to spam kick votes (duplicate votes were not counted but caused unnecessary + chat messages to be sent). + - Fixed item conditions occasionally not matching exactly between the server and clients, causing + issues such as not clients not being able to fabricate items due to the condition being slightly + below the minimum condition at their end. + +Bugfixes: + - Added a workaround to a MonoGame bug that makes the screen turn white when alt-tabbing out of fullscreen. + - Fixed docking ports flooding for no reason in some custom subs. + - Fixed LightComponents staying active on broken items. + - Fixed railguns and depth charge tubes being directly usable by characters (= they could be launched + simply by selecting them and left clicking, without the need to use a railgun controller). + - Fixed items salvaged from ruins not being saved in the campaign mode. + - Fixed LOS effect being brighter than the ambient light in some of the darker levels, causing + the player to see obstructed areas better than unobstructed ones. + - Fixed severed limbs staying disabled when a dismembered character is revived using console commands. + - Fixed characters holding non-aimable two-handed items such as railgun shells in one hand when aiming. + - Fixed stereo sounds not being loaded correctly. + - Fixed modified sprite colors not working correctly on worn items. + - Fixed modified maximum recharge speeds of PowerContainers resetting to the default value after + saving and reloading. + - Fixed handcuffed players being able to perform CPR and grab/drag bodies. + - Fixed diving suit's damage modifiers being bypassed if the character gets hit in the waist. + - Fixed clown costumes not hiding the torso of the wearer. + --------------------------------------------------------------------------------------------------------- v0.8.1.12 --------------------------------------------------------------------------------------------------------- From 5d2a193471b3496212ccef22e36030f518336987 Mon Sep 17 00:00:00 2001 From: itchyOwl Date: Mon, 3 Sep 2018 10:23:36 +0300 Subject: [PATCH 188/198] Line endings. --- .../Source/Networking/Voting.cs | 376 +- .../Source/Screens/CampaignUI.cs | 906 ++-- .../BarotraumaShared/Source/DebugConsole.cs | 4818 ++++++++--------- .../Source/GameSession/CargoManager.cs | 414 +- 4 files changed, 3257 insertions(+), 3257 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Networking/Voting.cs b/Barotrauma/BarotraumaClient/Source/Networking/Voting.cs index 98413efac..b80f73379 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/Voting.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/Voting.cs @@ -1,188 +1,188 @@ -using Barotrauma.Networking; -using Lidgren.Network; -using Microsoft.Xna.Framework; -using System.Collections.Generic; -using System.Linq; - -namespace Barotrauma -{ - partial class Voting - { - public bool AllowSubVoting - { - get { return allowSubVoting; } - set - { - if (value == allowSubVoting) return; - allowSubVoting = value; - GameMain.NetLobbyScreen.SubList.Enabled = value || GameMain.Server != null || - (GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.SelectSub)); - GameMain.NetLobbyScreen.InfoFrame.FindChild("subvotes", true).Visible = value; - - if (GameMain.Server != null) - { - UpdateVoteTexts(value ? GameMain.Server.ConnectedClients : null, VoteType.Sub); - GameMain.Server.UpdateVoteStatus(); - } - else - { - UpdateVoteTexts(null, VoteType.Sub); - GameMain.NetLobbyScreen.SubList.Deselect(); - } - } - } - public bool AllowModeVoting - { - get { return allowModeVoting; } - set - { - if (value == allowModeVoting) return; - allowModeVoting = value; - GameMain.NetLobbyScreen.ModeList.Enabled = - value || GameMain.Server != null || - (GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.SelectMode)); - - GameMain.NetLobbyScreen.InfoFrame.FindChild("modevotes", true).Visible = value; - - //gray out modes that can't be voted - foreach (GUITextBlock comp in GameMain.NetLobbyScreen.ModeList.children) - { - comp.TextColor = - new Color(comp.TextColor.R, comp.TextColor.G, comp.TextColor.B, - !allowModeVoting || ((GameModePreset)comp.UserData).Votable ? (byte)255 : (byte)100); - } - - if (GameMain.Server != null) - { - UpdateVoteTexts(value ? GameMain.Server.ConnectedClients : null, VoteType.Mode); - GameMain.Server.UpdateVoteStatus(); - } - else - { - UpdateVoteTexts(null, VoteType.Mode); - GameMain.NetLobbyScreen.ModeList.Deselect(); - } - } - } - - public void UpdateVoteTexts(List clients, VoteType voteType) - { - GUIListBox listBox = (voteType == VoteType.Sub) ? - GameMain.NetLobbyScreen.SubList : GameMain.NetLobbyScreen.ModeList; - - foreach (GUIComponent comp in listBox.children) - { - GUITextBlock voteText = comp.FindChild("votes") as GUITextBlock; - if (voteText != null) comp.RemoveChild(voteText); - } - - if (clients != null) - { - List> voteList = GetVoteList(voteType, clients); - foreach (Pair votable in voteList) - { - SetVoteText(listBox, votable.First, votable.Second); - } - } - } - - private void SetVoteText(GUIListBox listBox, object userData, int votes) - { - if (userData == null) return; - foreach (GUIComponent comp in listBox.children) - { - if (comp.UserData != userData) continue; - GUITextBlock voteText = comp.FindChild("votes") as GUITextBlock; - if (voteText == null) - { - voteText = new GUITextBlock(new Rectangle(0, 0, 30, 0), "", "", Alignment.Right, Alignment.Right, comp); - voteText.UserData = "votes"; - } - - voteText.Text = votes == 0 ? "" : votes.ToString(); - } - } - - public void ClientWrite(NetBuffer msg, VoteType voteType, object data) - { - if (GameMain.Server != null) return; - - msg.Write((byte)voteType); - - switch (voteType) - { - case VoteType.Sub: - Submarine sub = data as Submarine; - if (sub == null) return; - - msg.Write(sub.Name); - break; - case VoteType.Mode: - GameModePreset gameMode = data as GameModePreset; - if (gameMode == null) return; - - msg.Write(gameMode.Name); - break; - case VoteType.EndRound: - if (!(data is bool)) return; - - msg.Write((bool)data); - break; - case VoteType.Kick: - Client votedClient = data as Client; - if (votedClient == null) return; - - msg.Write(votedClient.ID); - break; - } - - msg.WritePadBits(); - } - - public void ClientRead(NetIncomingMessage inc) - { - if (GameMain.Server != null) return; - - AllowSubVoting = inc.ReadBoolean(); - if (allowSubVoting) - { - UpdateVoteTexts(null, VoteType.Sub); - int votableCount = inc.ReadByte(); - for (int i = 0; i < votableCount; i++) - { - int votes = inc.ReadByte(); - string subName = inc.ReadString(); - List serversubs = new List(); - foreach (GUIComponent item in GameMain.NetLobbyScreen?.SubList?.children) - { - if (item.UserData != null && item.UserData is Submarine) serversubs.Add(item.UserData as Submarine); - } - Submarine sub = serversubs.FirstOrDefault(sm => sm.Name == subName); - SetVoteText(GameMain.NetLobbyScreen.SubList, sub, votes); - } - } - AllowModeVoting = inc.ReadBoolean(); - if (allowModeVoting) - { - UpdateVoteTexts(null, VoteType.Mode); - int votableCount = inc.ReadByte(); - for (int i = 0; i < votableCount; i++) - { - int votes = inc.ReadByte(); - string modeName = inc.ReadString(); - GameModePreset mode = GameModePreset.list.Find(m => m.Name == modeName); - SetVoteText(GameMain.NetLobbyScreen.ModeList, mode, votes); - } - } - AllowEndVoting = inc.ReadBoolean(); - if (AllowEndVoting) - { - GameMain.NetworkMember.EndVoteCount = inc.ReadByte(); - GameMain.NetworkMember.EndVoteMax = inc.ReadByte(); - } - AllowVoteKick = inc.ReadBoolean(); - - inc.ReadPadBits(); - } - } -} +using Barotrauma.Networking; +using Lidgren.Network; +using Microsoft.Xna.Framework; +using System.Collections.Generic; +using System.Linq; + +namespace Barotrauma +{ + partial class Voting + { + public bool AllowSubVoting + { + get { return allowSubVoting; } + set + { + if (value == allowSubVoting) return; + allowSubVoting = value; + GameMain.NetLobbyScreen.SubList.Enabled = value || GameMain.Server != null || + (GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.SelectSub)); + GameMain.NetLobbyScreen.InfoFrame.FindChild("subvotes", true).Visible = value; + + if (GameMain.Server != null) + { + UpdateVoteTexts(value ? GameMain.Server.ConnectedClients : null, VoteType.Sub); + GameMain.Server.UpdateVoteStatus(); + } + else + { + UpdateVoteTexts(null, VoteType.Sub); + GameMain.NetLobbyScreen.SubList.Deselect(); + } + } + } + public bool AllowModeVoting + { + get { return allowModeVoting; } + set + { + if (value == allowModeVoting) return; + allowModeVoting = value; + GameMain.NetLobbyScreen.ModeList.Enabled = + value || GameMain.Server != null || + (GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.SelectMode)); + + GameMain.NetLobbyScreen.InfoFrame.FindChild("modevotes", true).Visible = value; + + //gray out modes that can't be voted + foreach (GUITextBlock comp in GameMain.NetLobbyScreen.ModeList.children) + { + comp.TextColor = + new Color(comp.TextColor.R, comp.TextColor.G, comp.TextColor.B, + !allowModeVoting || ((GameModePreset)comp.UserData).Votable ? (byte)255 : (byte)100); + } + + if (GameMain.Server != null) + { + UpdateVoteTexts(value ? GameMain.Server.ConnectedClients : null, VoteType.Mode); + GameMain.Server.UpdateVoteStatus(); + } + else + { + UpdateVoteTexts(null, VoteType.Mode); + GameMain.NetLobbyScreen.ModeList.Deselect(); + } + } + } + + public void UpdateVoteTexts(List clients, VoteType voteType) + { + GUIListBox listBox = (voteType == VoteType.Sub) ? + GameMain.NetLobbyScreen.SubList : GameMain.NetLobbyScreen.ModeList; + + foreach (GUIComponent comp in listBox.children) + { + GUITextBlock voteText = comp.FindChild("votes") as GUITextBlock; + if (voteText != null) comp.RemoveChild(voteText); + } + + if (clients != null) + { + List> voteList = GetVoteList(voteType, clients); + foreach (Pair votable in voteList) + { + SetVoteText(listBox, votable.First, votable.Second); + } + } + } + + private void SetVoteText(GUIListBox listBox, object userData, int votes) + { + if (userData == null) return; + foreach (GUIComponent comp in listBox.children) + { + if (comp.UserData != userData) continue; + GUITextBlock voteText = comp.FindChild("votes") as GUITextBlock; + if (voteText == null) + { + voteText = new GUITextBlock(new Rectangle(0, 0, 30, 0), "", "", Alignment.Right, Alignment.Right, comp); + voteText.UserData = "votes"; + } + + voteText.Text = votes == 0 ? "" : votes.ToString(); + } + } + + public void ClientWrite(NetBuffer msg, VoteType voteType, object data) + { + if (GameMain.Server != null) return; + + msg.Write((byte)voteType); + + switch (voteType) + { + case VoteType.Sub: + Submarine sub = data as Submarine; + if (sub == null) return; + + msg.Write(sub.Name); + break; + case VoteType.Mode: + GameModePreset gameMode = data as GameModePreset; + if (gameMode == null) return; + + msg.Write(gameMode.Name); + break; + case VoteType.EndRound: + if (!(data is bool)) return; + + msg.Write((bool)data); + break; + case VoteType.Kick: + Client votedClient = data as Client; + if (votedClient == null) return; + + msg.Write(votedClient.ID); + break; + } + + msg.WritePadBits(); + } + + public void ClientRead(NetIncomingMessage inc) + { + if (GameMain.Server != null) return; + + AllowSubVoting = inc.ReadBoolean(); + if (allowSubVoting) + { + UpdateVoteTexts(null, VoteType.Sub); + int votableCount = inc.ReadByte(); + for (int i = 0; i < votableCount; i++) + { + int votes = inc.ReadByte(); + string subName = inc.ReadString(); + List serversubs = new List(); + foreach (GUIComponent item in GameMain.NetLobbyScreen?.SubList?.children) + { + if (item.UserData != null && item.UserData is Submarine) serversubs.Add(item.UserData as Submarine); + } + Submarine sub = serversubs.FirstOrDefault(sm => sm.Name == subName); + SetVoteText(GameMain.NetLobbyScreen.SubList, sub, votes); + } + } + AllowModeVoting = inc.ReadBoolean(); + if (allowModeVoting) + { + UpdateVoteTexts(null, VoteType.Mode); + int votableCount = inc.ReadByte(); + for (int i = 0; i < votableCount; i++) + { + int votes = inc.ReadByte(); + string modeName = inc.ReadString(); + GameModePreset mode = GameModePreset.list.Find(m => m.Name == modeName); + SetVoteText(GameMain.NetLobbyScreen.ModeList, mode, votes); + } + } + AllowEndVoting = inc.ReadBoolean(); + if (AllowEndVoting) + { + GameMain.NetworkMember.EndVoteCount = inc.ReadByte(); + GameMain.NetworkMember.EndVoteMax = inc.ReadByte(); + } + AllowVoteKick = inc.ReadBoolean(); + + inc.ReadPadBits(); + } + } +} diff --git a/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs b/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs index d3b6ea59a..a9e6f6cfc 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs @@ -1,453 +1,453 @@ -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; - -namespace Barotrauma -{ - class CampaignUI - { - public enum Tab { Crew = 0, Map = 1, Store = 2 } - - private GUIFrame[] tabs; - - private GUIButton startButton; - - private Tab selectedTab; - - private GUIListBox characterList, hireList; - - private GUIListBox selectedItemList; - private GUIListBox storeItemList; - - private CampaignMode campaign; - - private GUIFrame characterPreviewFrame; - - private Level selectedLevel; - - private float mapZoom = 3.0f; - - public Action StartRound; - public Action OnLocationSelected; - - public Level SelectedLevel - { - get { return selectedLevel; } - } - - public CampaignMode Campaign - { - get { return campaign; } - } - - public CampaignUI(CampaignMode campaign, GUIFrame container) - { - this.campaign = campaign; - - tabs = new GUIFrame[3]; - - tabs[(int)Tab.Crew] = new GUIFrame(Rectangle.Empty, null, container); - tabs[(int)Tab.Crew].Padding = Vector4.One * 10.0f; - - //new GUITextBlock(new Rectangle(0, 0, 200, 25), "Crew:", Color.Transparent, Color.White, Alignment.Left, "", bottomPanel[(int)PanelTab.Crew]); - - int crewColumnWidth = Math.Min(300, (container.Rect.Width - 40) / 2); - - new GUITextBlock(new Rectangle(0, 0, 100, 20), TextManager.Get("Crew") + ":", "", tabs[(int)Tab.Crew], GUI.LargeFont); - characterList = new GUIListBox(new Rectangle(0, 40, crewColumnWidth, 0), "", tabs[(int)Tab.Crew]); - characterList.OnSelected = SelectCharacter; - - hireList = new GUIListBox(new Rectangle(0, 40, 300, 0), "", Alignment.Right, tabs[(int)Tab.Crew]); - new GUITextBlock(new Rectangle(0, 0, 300, 20), TextManager.Get("Hire") + ":", "", Alignment.Right, Alignment.Left, tabs[(int)Tab.Crew], false, GUI.LargeFont); - hireList.OnSelected = SelectCharacter; - - //--------------------------------------- - - tabs[(int)Tab.Map] = new GUIFrame(Rectangle.Empty, null, container); - tabs[(int)Tab.Map].Padding = Vector4.One * 10.0f; - - if (GameMain.Client == null) - { - startButton = new GUIButton(new Rectangle(0, 0, 100, 30), TextManager.Get("StartCampaignButton"), - Alignment.BottomRight, "", tabs[(int)Tab.Map]); - startButton.OnClicked = (GUIButton btn, object obj) => { StartRound?.Invoke(); return true; }; - startButton.Enabled = false; - } - - //--------------------------------------- - - tabs[(int)Tab.Store] = new GUIFrame(Rectangle.Empty, null, container); - tabs[(int)Tab.Store].Padding = Vector4.One * 10.0f; - - int sellColumnWidth = (tabs[(int)Tab.Store].Rect.Width - 40) / 2 - 20; - - selectedItemList = new GUIListBox(new Rectangle(0, 30, sellColumnWidth, tabs[(int)Tab.Store].Rect.Height - 80), Color.White * 0.7f, "", tabs[(int)Tab.Store]); - //selectedItemList.OnSelected = SellItem; - - storeItemList = new GUIListBox(new Rectangle(0, 30, sellColumnWidth, tabs[(int)Tab.Store].Rect.Height - 80), Color.White * 0.7f, Alignment.TopRight, "", tabs[(int)Tab.Store]); - storeItemList.OnSelected = BuyItem; - - int x = storeItemList.Rect.X - storeItemList.Parent.Rect.X; - - List itemCategories = Enum.GetValues(typeof(MapEntityCategory)).Cast().ToList(); - //don't show categories with no buyable items - itemCategories.RemoveAll(c => !MapEntityPrefab.List.Any(ep => ep.Price > 0.0f && ep.Category.HasFlag(c))); - - int buttonWidth = Math.Min(sellColumnWidth / itemCategories.Count, 100); - foreach (MapEntityCategory category in itemCategories) - { - var categoryButton = new GUIButton(new Rectangle(x, 0, buttonWidth, 20), category.ToString(), "", tabs[(int)Tab.Store]); - categoryButton.UserData = category; - categoryButton.OnClicked = SelectItemCategory; - - if (category == MapEntityCategory.Equipment) - { - SelectItemCategory(categoryButton, category); - } - x += buttonWidth; - } - - SelectTab(Tab.Map); - - UpdateLocationTab(campaign.Map.CurrentLocation); - - campaign.Map.OnLocationSelected += SelectLocation; - campaign.Map.OnLocationChanged += (location) => UpdateLocationTab(location); - campaign.CargoManager.OnItemsChanged += RefreshItemTab; - } - - private void UpdateLocationTab(Location location) - { - if (characterPreviewFrame != null) - { - characterPreviewFrame.Parent.RemoveChild(characterPreviewFrame); - characterPreviewFrame = null; - } - - if (location.HireManager == null) - { - hireList.ClearChildren(); - hireList.Enabled = false; - - new GUITextBlock(new Rectangle(0, 0, 0, 0), TextManager.Get("HireUnavailable"), Color.Transparent, Color.LightGray, Alignment.Center, Alignment.Center, "", hireList); - return; - } - - hireList.Enabled = true; - hireList.ClearChildren(); - - foreach (CharacterInfo c in location.HireManager.availableCharacters) - { - var frame = c.CreateCharacterFrame(hireList, c.Name + " (" + c.Job.Name + ")", c); - - new GUITextBlock( - new Rectangle(0, 0, 0, 25), - c.Salary.ToString(), - null, null, - Alignment.TopRight, "", frame); - } - - RefreshItemTab(); - } - - public void Update(float deltaTime) - { - mapZoom += PlayerInput.ScrollWheelSpeed / 1000.0f; - mapZoom = MathHelper.Clamp(mapZoom, 1.0f, 4.0f); - - if (GameMain.GameSession?.Map != null) - { - GameMain.GameSession.Map.Update(deltaTime, new Rectangle( - tabs[(int)selectedTab].Rect.X + 20, - tabs[(int)selectedTab].Rect.Y + 20, - tabs[(int)selectedTab].Rect.Width - 310, - tabs[(int)selectedTab].Rect.Height - 40), mapZoom); - } - } - - public void Draw(SpriteBatch spriteBatch) - { - if (selectedTab == Tab.Map && GameMain.GameSession?.Map != null) - { - GameMain.GameSession.Map.Draw(spriteBatch, new Rectangle( - tabs[(int)selectedTab].Rect.X + 20, - tabs[(int)selectedTab].Rect.Y + 20, - tabs[(int)selectedTab].Rect.Width - 310, - tabs[(int)selectedTab].Rect.Height - 40), mapZoom); - } - } - - public void UpdateCharacterLists() - { - characterList.ClearChildren(); - foreach (CharacterInfo c in GameMain.GameSession.CrewManager.GetCharacterInfos()) - { - c.CreateCharacterFrame(characterList, c.Name + " (" + c.Job.Name + ") ", c); - } - } - - public void SelectLocation(Location location, LocationConnection connection) - { - GUIComponent locationPanel = tabs[(int)Tab.Map].GetChild("selectedlocation"); - - if (locationPanel != null) tabs[(int)Tab.Map].RemoveChild(locationPanel); - - locationPanel = new GUIFrame(new Rectangle(0, 0, 250, 190), Color.Transparent, Alignment.TopRight, null, tabs[(int)Tab.Map]); - locationPanel.UserData = "selectedlocation"; - - if (location == null) return; - - var titleText = new GUITextBlock(new Rectangle(0, 0, 250, 0), location.Name, "", Alignment.TopLeft, Alignment.TopCenter, locationPanel, true, GUI.LargeFont); - - if (GameMain.GameSession.Map.SelectedConnection != null && GameMain.GameSession.Map.SelectedConnection.Mission != null) - { - var mission = GameMain.GameSession.Map.SelectedConnection.Mission; - - new GUITextBlock(new Rectangle(0, titleText.Rect.Height + 20, 0, 20), TextManager.Get("Mission") + ": " + mission.Name, "", locationPanel); - new GUITextBlock(new Rectangle(0, titleText.Rect.Height + 40, 0, 20), TextManager.Get("Reward") + ": " + mission.Reward + " " + TextManager.Get("Credits"), "", locationPanel); - new GUITextBlock(new Rectangle(0, titleText.Rect.Height + 70, 0, 0), mission.Description, "", Alignment.TopLeft, Alignment.TopLeft, locationPanel, true, GUI.SmallFont); - } - - if (startButton != null) startButton.Enabled = true; - - selectedLevel = connection.Level; - - OnLocationSelected?.Invoke(location, connection); - } - - private void CreateItemFrame(PurchasedItem pi, GUIListBox listBox, int width) - { - GUIFrame frame = new GUIFrame(new Rectangle(0, 0, 0, 50), "ListBoxElement", listBox); - frame.UserData = pi; - frame.Padding = new Vector4(5.0f, 5.0f, 5.0f, 5.0f); - - frame.ToolTip = pi.itemPrefab.Description; - - ScalableFont font = listBox.Rect.Width < 280 ? GUI.SmallFont : GUI.Font; - - GUITextBlock textBlock = new GUITextBlock( - new Rectangle(50, 0, 0, 25), - pi.itemPrefab.Name, - null, null, - Alignment.Left, Alignment.CenterX | Alignment.Left, - "", frame); - textBlock.Font = font; - textBlock.Padding = new Vector4(5.0f, 0.0f, 5.0f, 0.0f); - textBlock.ToolTip = pi.itemPrefab.Description; - - if (pi.itemPrefab.sprite != null) - { - GUIImage img = new GUIImage(new Rectangle(0, 0, 40, 40), pi.itemPrefab.sprite, Alignment.CenterLeft, frame); - img.Color = pi.itemPrefab.SpriteColor; - img.Scale = Math.Min(Math.Min(40.0f / img.SourceRect.Width, 40.0f / img.SourceRect.Height), 1.0f); - } - - textBlock = new GUITextBlock( - new Rectangle(width - 160, 0, 80, 25), - pi.itemPrefab.Price.ToString(), - null, null, Alignment.TopLeft, - Alignment.TopLeft, "", frame); - textBlock.Font = font; - textBlock.ToolTip = pi.itemPrefab.Description; - - //If its the store menu, quantity will always be 0 - if (pi.quantity > 0) - { - var amountInput = new GUINumberInput(new Rectangle(width - 80, 0, 50, 40), "", GUINumberInput.NumberType.Int, frame); - amountInput.MinValueInt = 0; - amountInput.MaxValueInt = 1000; - amountInput.UserData = pi; - amountInput.IntValue = pi.quantity; - amountInput.OnValueChanged += (numberInput) => - { - PurchasedItem purchasedItem = numberInput.UserData as PurchasedItem; - - //Attempting to buy - if (numberInput.IntValue > purchasedItem.quantity) - { - int quantity = numberInput.IntValue - purchasedItem.quantity; - //Cap the numberbox based on the amount we can afford. - quantity = campaign.Money <= 0 ? - 0 : Math.Min((int)(Campaign.Money / (float)purchasedItem.itemPrefab.Price), quantity); - for (int i = 0; i < quantity; i++) - { - BuyItem(numberInput, purchasedItem); - } - numberInput.IntValue = purchasedItem.quantity; - } - //Attempting to sell - else - { - int quantity = purchasedItem.quantity - numberInput.IntValue; - for (int i = 0; i < quantity; i++) - { - SellItem(numberInput, purchasedItem); - } - } - }; - } - } - - private bool BuyItem(GUIComponent component, object obj) - { - PurchasedItem pi = obj as PurchasedItem; - if (pi == null || pi.itemPrefab == null) return false; - - if (GameMain.Client != null && !GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign)) - { - return false; - } - - if (pi.itemPrefab.Price > campaign.Money) return false; - - campaign.CargoManager.PurchaseItem(pi.itemPrefab, 1); - GameMain.Client?.SendCampaignState(); - - return false; - } - - private bool SellItem(GUIComponent component, object obj) - { - PurchasedItem pi = obj as PurchasedItem; - if (pi == null || pi.itemPrefab == null) return false; - - if (GameMain.Client != null && !GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign)) - { - return false; - } - - campaign.CargoManager.SellItem(pi.itemPrefab,1); - GameMain.Client?.SendCampaignState(); - - return false; - } - - private void RefreshItemTab() - { - selectedItemList.ClearChildren(); - foreach (PurchasedItem pi in campaign.CargoManager.PurchasedItems) - { - CreateItemFrame(pi, selectedItemList, selectedItemList.Rect.Width); - } - selectedItemList.children.Sort((x, y) => (x.UserData as PurchasedItem).itemPrefab.Name.CompareTo((y.UserData as PurchasedItem).itemPrefab.Name)); - selectedItemList.children.Sort((x, y) => (x.UserData as PurchasedItem).itemPrefab.Category.CompareTo((y.UserData as PurchasedItem).itemPrefab.Category)); - selectedItemList.UpdateScrollBarSize(); - } - - public void SelectTab(Tab tab) - { - selectedTab = tab; - for (int i = 0; i< tabs.Length; i++) - { - tabs[i].Visible = (int)selectedTab == i; - } - } - - private bool SelectItemCategory(GUIButton button, object selection) - { - if (!(selection is MapEntityCategory)) return false; - - storeItemList.ClearChildren(); - - MapEntityCategory category = (MapEntityCategory)selection; - var items = MapEntityPrefab.List.FindAll(ep => ep.Price > 0.0f && ep.Category.HasFlag(category) && ep is ItemPrefab); - - int width = storeItemList.Rect.Width; - - foreach (ItemPrefab ep in items) - { - CreateItemFrame(new PurchasedItem((ItemPrefab)ep,0), storeItemList, width); - } - - storeItemList.children.Sort((x, y) => (x.UserData as PurchasedItem).itemPrefab.Name.CompareTo((y.UserData as PurchasedItem).itemPrefab.Name)); - - foreach (GUIComponent child in button.Parent.children) - { - var otherButton = child as GUIButton; - if (child.UserData is MapEntityCategory && otherButton != button) - { - otherButton.Selected = false; - } - } - - button.Selected = true; - return true; - } - - public string GetMoney() - { - return TextManager.Get("Credits") + ": " + ((GameMain.GameSession == null) ? "0" : string.Format(CultureInfo.InvariantCulture, "{0:N0}", campaign.Money)); - } - - private bool SelectCharacter(GUIComponent component, object selection) - { - GUIComponent prevInfoFrame = null; - foreach (GUIComponent child in tabs[(int)selectedTab].children) - { - if (!(child.UserData is CharacterInfo)) continue; - - prevInfoFrame = child; - } - - if (prevInfoFrame != null) tabs[(int)selectedTab].RemoveChild(prevInfoFrame); - - CharacterInfo characterInfo = selection as CharacterInfo; - if (characterInfo == null) return false; - - characterList.Deselect(); - hireList.Deselect(); - - if (Character.Controlled != null && characterInfo == Character.Controlled.Info) return false; - - if (characterPreviewFrame == null || characterPreviewFrame.UserData != characterInfo) - { - int width = Math.Min(300, tabs[(int)Tab.Crew].Rect.Width - hireList.Rect.Width - characterList.Rect.Width - 50); - - characterPreviewFrame = new GUIFrame(new Rectangle(0, 60, width, 300), - new Color(0.0f, 0.0f, 0.0f, 0.8f), - Alignment.TopCenter, "", tabs[(int)selectedTab]); - characterPreviewFrame.Padding = new Vector4(20.0f, 20.0f, 20.0f, 20.0f); - characterPreviewFrame.UserData = characterInfo; - - characterInfo.CreateInfoFrame(characterPreviewFrame); - } - - if (component.Parent == hireList) - { - GUIButton hireButton = new GUIButton(new Rectangle(0, 0, 100, 20), TextManager.Get("HireButton"), Alignment.BottomCenter, "", characterPreviewFrame); - hireButton.Enabled = campaign.Money >= characterInfo.Salary; - hireButton.UserData = characterInfo; - hireButton.OnClicked = HireCharacter; - } - - return true; - } - - private bool HireCharacter(GUIButton button, object selection) - { - CharacterInfo characterInfo = selection as CharacterInfo; - if (characterInfo == null) return false; - - SinglePlayerCampaign spCampaign = campaign as SinglePlayerCampaign; - if (spCampaign == null) - { - DebugConsole.ThrowError("Characters can only be hired in the single player campaign.\n" + Environment.StackTrace); - return false; - } - - if (spCampaign.TryHireCharacter(GameMain.GameSession.Map.CurrentLocation.HireManager, characterInfo)) - { - UpdateLocationTab(GameMain.GameSession.Map.CurrentLocation); - SelectCharacter(null, null); - UpdateCharacterLists(); - } - - return false; - } - - - } -} +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace Barotrauma +{ + class CampaignUI + { + public enum Tab { Crew = 0, Map = 1, Store = 2 } + + private GUIFrame[] tabs; + + private GUIButton startButton; + + private Tab selectedTab; + + private GUIListBox characterList, hireList; + + private GUIListBox selectedItemList; + private GUIListBox storeItemList; + + private CampaignMode campaign; + + private GUIFrame characterPreviewFrame; + + private Level selectedLevel; + + private float mapZoom = 3.0f; + + public Action StartRound; + public Action OnLocationSelected; + + public Level SelectedLevel + { + get { return selectedLevel; } + } + + public CampaignMode Campaign + { + get { return campaign; } + } + + public CampaignUI(CampaignMode campaign, GUIFrame container) + { + this.campaign = campaign; + + tabs = new GUIFrame[3]; + + tabs[(int)Tab.Crew] = new GUIFrame(Rectangle.Empty, null, container); + tabs[(int)Tab.Crew].Padding = Vector4.One * 10.0f; + + //new GUITextBlock(new Rectangle(0, 0, 200, 25), "Crew:", Color.Transparent, Color.White, Alignment.Left, "", bottomPanel[(int)PanelTab.Crew]); + + int crewColumnWidth = Math.Min(300, (container.Rect.Width - 40) / 2); + + new GUITextBlock(new Rectangle(0, 0, 100, 20), TextManager.Get("Crew") + ":", "", tabs[(int)Tab.Crew], GUI.LargeFont); + characterList = new GUIListBox(new Rectangle(0, 40, crewColumnWidth, 0), "", tabs[(int)Tab.Crew]); + characterList.OnSelected = SelectCharacter; + + hireList = new GUIListBox(new Rectangle(0, 40, 300, 0), "", Alignment.Right, tabs[(int)Tab.Crew]); + new GUITextBlock(new Rectangle(0, 0, 300, 20), TextManager.Get("Hire") + ":", "", Alignment.Right, Alignment.Left, tabs[(int)Tab.Crew], false, GUI.LargeFont); + hireList.OnSelected = SelectCharacter; + + //--------------------------------------- + + tabs[(int)Tab.Map] = new GUIFrame(Rectangle.Empty, null, container); + tabs[(int)Tab.Map].Padding = Vector4.One * 10.0f; + + if (GameMain.Client == null) + { + startButton = new GUIButton(new Rectangle(0, 0, 100, 30), TextManager.Get("StartCampaignButton"), + Alignment.BottomRight, "", tabs[(int)Tab.Map]); + startButton.OnClicked = (GUIButton btn, object obj) => { StartRound?.Invoke(); return true; }; + startButton.Enabled = false; + } + + //--------------------------------------- + + tabs[(int)Tab.Store] = new GUIFrame(Rectangle.Empty, null, container); + tabs[(int)Tab.Store].Padding = Vector4.One * 10.0f; + + int sellColumnWidth = (tabs[(int)Tab.Store].Rect.Width - 40) / 2 - 20; + + selectedItemList = new GUIListBox(new Rectangle(0, 30, sellColumnWidth, tabs[(int)Tab.Store].Rect.Height - 80), Color.White * 0.7f, "", tabs[(int)Tab.Store]); + //selectedItemList.OnSelected = SellItem; + + storeItemList = new GUIListBox(new Rectangle(0, 30, sellColumnWidth, tabs[(int)Tab.Store].Rect.Height - 80), Color.White * 0.7f, Alignment.TopRight, "", tabs[(int)Tab.Store]); + storeItemList.OnSelected = BuyItem; + + int x = storeItemList.Rect.X - storeItemList.Parent.Rect.X; + + List itemCategories = Enum.GetValues(typeof(MapEntityCategory)).Cast().ToList(); + //don't show categories with no buyable items + itemCategories.RemoveAll(c => !MapEntityPrefab.List.Any(ep => ep.Price > 0.0f && ep.Category.HasFlag(c))); + + int buttonWidth = Math.Min(sellColumnWidth / itemCategories.Count, 100); + foreach (MapEntityCategory category in itemCategories) + { + var categoryButton = new GUIButton(new Rectangle(x, 0, buttonWidth, 20), category.ToString(), "", tabs[(int)Tab.Store]); + categoryButton.UserData = category; + categoryButton.OnClicked = SelectItemCategory; + + if (category == MapEntityCategory.Equipment) + { + SelectItemCategory(categoryButton, category); + } + x += buttonWidth; + } + + SelectTab(Tab.Map); + + UpdateLocationTab(campaign.Map.CurrentLocation); + + campaign.Map.OnLocationSelected += SelectLocation; + campaign.Map.OnLocationChanged += (location) => UpdateLocationTab(location); + campaign.CargoManager.OnItemsChanged += RefreshItemTab; + } + + private void UpdateLocationTab(Location location) + { + if (characterPreviewFrame != null) + { + characterPreviewFrame.Parent.RemoveChild(characterPreviewFrame); + characterPreviewFrame = null; + } + + if (location.HireManager == null) + { + hireList.ClearChildren(); + hireList.Enabled = false; + + new GUITextBlock(new Rectangle(0, 0, 0, 0), TextManager.Get("HireUnavailable"), Color.Transparent, Color.LightGray, Alignment.Center, Alignment.Center, "", hireList); + return; + } + + hireList.Enabled = true; + hireList.ClearChildren(); + + foreach (CharacterInfo c in location.HireManager.availableCharacters) + { + var frame = c.CreateCharacterFrame(hireList, c.Name + " (" + c.Job.Name + ")", c); + + new GUITextBlock( + new Rectangle(0, 0, 0, 25), + c.Salary.ToString(), + null, null, + Alignment.TopRight, "", frame); + } + + RefreshItemTab(); + } + + public void Update(float deltaTime) + { + mapZoom += PlayerInput.ScrollWheelSpeed / 1000.0f; + mapZoom = MathHelper.Clamp(mapZoom, 1.0f, 4.0f); + + if (GameMain.GameSession?.Map != null) + { + GameMain.GameSession.Map.Update(deltaTime, new Rectangle( + tabs[(int)selectedTab].Rect.X + 20, + tabs[(int)selectedTab].Rect.Y + 20, + tabs[(int)selectedTab].Rect.Width - 310, + tabs[(int)selectedTab].Rect.Height - 40), mapZoom); + } + } + + public void Draw(SpriteBatch spriteBatch) + { + if (selectedTab == Tab.Map && GameMain.GameSession?.Map != null) + { + GameMain.GameSession.Map.Draw(spriteBatch, new Rectangle( + tabs[(int)selectedTab].Rect.X + 20, + tabs[(int)selectedTab].Rect.Y + 20, + tabs[(int)selectedTab].Rect.Width - 310, + tabs[(int)selectedTab].Rect.Height - 40), mapZoom); + } + } + + public void UpdateCharacterLists() + { + characterList.ClearChildren(); + foreach (CharacterInfo c in GameMain.GameSession.CrewManager.GetCharacterInfos()) + { + c.CreateCharacterFrame(characterList, c.Name + " (" + c.Job.Name + ") ", c); + } + } + + public void SelectLocation(Location location, LocationConnection connection) + { + GUIComponent locationPanel = tabs[(int)Tab.Map].GetChild("selectedlocation"); + + if (locationPanel != null) tabs[(int)Tab.Map].RemoveChild(locationPanel); + + locationPanel = new GUIFrame(new Rectangle(0, 0, 250, 190), Color.Transparent, Alignment.TopRight, null, tabs[(int)Tab.Map]); + locationPanel.UserData = "selectedlocation"; + + if (location == null) return; + + var titleText = new GUITextBlock(new Rectangle(0, 0, 250, 0), location.Name, "", Alignment.TopLeft, Alignment.TopCenter, locationPanel, true, GUI.LargeFont); + + if (GameMain.GameSession.Map.SelectedConnection != null && GameMain.GameSession.Map.SelectedConnection.Mission != null) + { + var mission = GameMain.GameSession.Map.SelectedConnection.Mission; + + new GUITextBlock(new Rectangle(0, titleText.Rect.Height + 20, 0, 20), TextManager.Get("Mission") + ": " + mission.Name, "", locationPanel); + new GUITextBlock(new Rectangle(0, titleText.Rect.Height + 40, 0, 20), TextManager.Get("Reward") + ": " + mission.Reward + " " + TextManager.Get("Credits"), "", locationPanel); + new GUITextBlock(new Rectangle(0, titleText.Rect.Height + 70, 0, 0), mission.Description, "", Alignment.TopLeft, Alignment.TopLeft, locationPanel, true, GUI.SmallFont); + } + + if (startButton != null) startButton.Enabled = true; + + selectedLevel = connection.Level; + + OnLocationSelected?.Invoke(location, connection); + } + + private void CreateItemFrame(PurchasedItem pi, GUIListBox listBox, int width) + { + GUIFrame frame = new GUIFrame(new Rectangle(0, 0, 0, 50), "ListBoxElement", listBox); + frame.UserData = pi; + frame.Padding = new Vector4(5.0f, 5.0f, 5.0f, 5.0f); + + frame.ToolTip = pi.itemPrefab.Description; + + ScalableFont font = listBox.Rect.Width < 280 ? GUI.SmallFont : GUI.Font; + + GUITextBlock textBlock = new GUITextBlock( + new Rectangle(50, 0, 0, 25), + pi.itemPrefab.Name, + null, null, + Alignment.Left, Alignment.CenterX | Alignment.Left, + "", frame); + textBlock.Font = font; + textBlock.Padding = new Vector4(5.0f, 0.0f, 5.0f, 0.0f); + textBlock.ToolTip = pi.itemPrefab.Description; + + if (pi.itemPrefab.sprite != null) + { + GUIImage img = new GUIImage(new Rectangle(0, 0, 40, 40), pi.itemPrefab.sprite, Alignment.CenterLeft, frame); + img.Color = pi.itemPrefab.SpriteColor; + img.Scale = Math.Min(Math.Min(40.0f / img.SourceRect.Width, 40.0f / img.SourceRect.Height), 1.0f); + } + + textBlock = new GUITextBlock( + new Rectangle(width - 160, 0, 80, 25), + pi.itemPrefab.Price.ToString(), + null, null, Alignment.TopLeft, + Alignment.TopLeft, "", frame); + textBlock.Font = font; + textBlock.ToolTip = pi.itemPrefab.Description; + + //If its the store menu, quantity will always be 0 + if (pi.quantity > 0) + { + var amountInput = new GUINumberInput(new Rectangle(width - 80, 0, 50, 40), "", GUINumberInput.NumberType.Int, frame); + amountInput.MinValueInt = 0; + amountInput.MaxValueInt = 1000; + amountInput.UserData = pi; + amountInput.IntValue = pi.quantity; + amountInput.OnValueChanged += (numberInput) => + { + PurchasedItem purchasedItem = numberInput.UserData as PurchasedItem; + + //Attempting to buy + if (numberInput.IntValue > purchasedItem.quantity) + { + int quantity = numberInput.IntValue - purchasedItem.quantity; + //Cap the numberbox based on the amount we can afford. + quantity = campaign.Money <= 0 ? + 0 : Math.Min((int)(Campaign.Money / (float)purchasedItem.itemPrefab.Price), quantity); + for (int i = 0; i < quantity; i++) + { + BuyItem(numberInput, purchasedItem); + } + numberInput.IntValue = purchasedItem.quantity; + } + //Attempting to sell + else + { + int quantity = purchasedItem.quantity - numberInput.IntValue; + for (int i = 0; i < quantity; i++) + { + SellItem(numberInput, purchasedItem); + } + } + }; + } + } + + private bool BuyItem(GUIComponent component, object obj) + { + PurchasedItem pi = obj as PurchasedItem; + if (pi == null || pi.itemPrefab == null) return false; + + if (GameMain.Client != null && !GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign)) + { + return false; + } + + if (pi.itemPrefab.Price > campaign.Money) return false; + + campaign.CargoManager.PurchaseItem(pi.itemPrefab, 1); + GameMain.Client?.SendCampaignState(); + + return false; + } + + private bool SellItem(GUIComponent component, object obj) + { + PurchasedItem pi = obj as PurchasedItem; + if (pi == null || pi.itemPrefab == null) return false; + + if (GameMain.Client != null && !GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign)) + { + return false; + } + + campaign.CargoManager.SellItem(pi.itemPrefab,1); + GameMain.Client?.SendCampaignState(); + + return false; + } + + private void RefreshItemTab() + { + selectedItemList.ClearChildren(); + foreach (PurchasedItem pi in campaign.CargoManager.PurchasedItems) + { + CreateItemFrame(pi, selectedItemList, selectedItemList.Rect.Width); + } + selectedItemList.children.Sort((x, y) => (x.UserData as PurchasedItem).itemPrefab.Name.CompareTo((y.UserData as PurchasedItem).itemPrefab.Name)); + selectedItemList.children.Sort((x, y) => (x.UserData as PurchasedItem).itemPrefab.Category.CompareTo((y.UserData as PurchasedItem).itemPrefab.Category)); + selectedItemList.UpdateScrollBarSize(); + } + + public void SelectTab(Tab tab) + { + selectedTab = tab; + for (int i = 0; i< tabs.Length; i++) + { + tabs[i].Visible = (int)selectedTab == i; + } + } + + private bool SelectItemCategory(GUIButton button, object selection) + { + if (!(selection is MapEntityCategory)) return false; + + storeItemList.ClearChildren(); + + MapEntityCategory category = (MapEntityCategory)selection; + var items = MapEntityPrefab.List.FindAll(ep => ep.Price > 0.0f && ep.Category.HasFlag(category) && ep is ItemPrefab); + + int width = storeItemList.Rect.Width; + + foreach (ItemPrefab ep in items) + { + CreateItemFrame(new PurchasedItem((ItemPrefab)ep,0), storeItemList, width); + } + + storeItemList.children.Sort((x, y) => (x.UserData as PurchasedItem).itemPrefab.Name.CompareTo((y.UserData as PurchasedItem).itemPrefab.Name)); + + foreach (GUIComponent child in button.Parent.children) + { + var otherButton = child as GUIButton; + if (child.UserData is MapEntityCategory && otherButton != button) + { + otherButton.Selected = false; + } + } + + button.Selected = true; + return true; + } + + public string GetMoney() + { + return TextManager.Get("Credits") + ": " + ((GameMain.GameSession == null) ? "0" : string.Format(CultureInfo.InvariantCulture, "{0:N0}", campaign.Money)); + } + + private bool SelectCharacter(GUIComponent component, object selection) + { + GUIComponent prevInfoFrame = null; + foreach (GUIComponent child in tabs[(int)selectedTab].children) + { + if (!(child.UserData is CharacterInfo)) continue; + + prevInfoFrame = child; + } + + if (prevInfoFrame != null) tabs[(int)selectedTab].RemoveChild(prevInfoFrame); + + CharacterInfo characterInfo = selection as CharacterInfo; + if (characterInfo == null) return false; + + characterList.Deselect(); + hireList.Deselect(); + + if (Character.Controlled != null && characterInfo == Character.Controlled.Info) return false; + + if (characterPreviewFrame == null || characterPreviewFrame.UserData != characterInfo) + { + int width = Math.Min(300, tabs[(int)Tab.Crew].Rect.Width - hireList.Rect.Width - characterList.Rect.Width - 50); + + characterPreviewFrame = new GUIFrame(new Rectangle(0, 60, width, 300), + new Color(0.0f, 0.0f, 0.0f, 0.8f), + Alignment.TopCenter, "", tabs[(int)selectedTab]); + characterPreviewFrame.Padding = new Vector4(20.0f, 20.0f, 20.0f, 20.0f); + characterPreviewFrame.UserData = characterInfo; + + characterInfo.CreateInfoFrame(characterPreviewFrame); + } + + if (component.Parent == hireList) + { + GUIButton hireButton = new GUIButton(new Rectangle(0, 0, 100, 20), TextManager.Get("HireButton"), Alignment.BottomCenter, "", characterPreviewFrame); + hireButton.Enabled = campaign.Money >= characterInfo.Salary; + hireButton.UserData = characterInfo; + hireButton.OnClicked = HireCharacter; + } + + return true; + } + + private bool HireCharacter(GUIButton button, object selection) + { + CharacterInfo characterInfo = selection as CharacterInfo; + if (characterInfo == null) return false; + + SinglePlayerCampaign spCampaign = campaign as SinglePlayerCampaign; + if (spCampaign == null) + { + DebugConsole.ThrowError("Characters can only be hired in the single player campaign.\n" + Environment.StackTrace); + return false; + } + + if (spCampaign.TryHireCharacter(GameMain.GameSession.Map.CurrentLocation.HireManager, characterInfo)) + { + UpdateLocationTab(GameMain.GameSession.Map.CurrentLocation); + SelectCharacter(null, null); + UpdateCharacterLists(); + } + + return false; + } + + + } +} diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs index 21aaadf33..200acdeb0 100644 --- a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs @@ -1,2409 +1,2409 @@ -using Barotrauma.Items.Components; -using Barotrauma.Networking; -using FarseerPhysics; -using Microsoft.Xna.Framework; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.IO; -using System.Linq; - -namespace Barotrauma -{ - struct ColoredText - { - public string Text; - public Color Color; - public bool IsCommand; - - public readonly string Time; - - public ColoredText(string text, Color color, bool isCommand) - { - this.Text = text; - this.Color = color; - this.IsCommand = isCommand; - - Time = DateTime.Now.ToString(); - } - } - - static partial class DebugConsole - { - public class Command - { - public readonly string[] names; - public readonly string help; - - private Action onExecute; - - /// - /// 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 Func GetValidArgs; - - 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, Func getValidArgs = null) - { - names = name.Split('|'); - this.help = help; - - this.onExecute = onExecute; - this.onClientExecute = onClientExecute; - this.onClientRequestExecute = onClientRequestExecute; - - this.GetValidArgs = getValidArgs; - } - - - /// - /// 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, Func getValidArgs = null) - { - names = name.Split('|'); - this.help = help; - - this.onExecute = onExecute; - this.onClientExecute = onExecute; - - this.GetValidArgs = getValidArgs; - } - - public void Execute(string[] args) - { - if (onExecute == null) return; - onExecute(args); - } - - public void ClientExecute(string[] args) - { - onClientExecute(args); - } - - public void ServerExecuteOnClientRequest(Client client, Vector2 cursorWorldPos, string[] args) - { - if (onClientRequestExecute == null) - { - if (onExecute == null) return; - onExecute(args); - } - else - { - onClientRequestExecute(client, cursorWorldPos, args); - } - } - } - - const int MaxMessages = 200; - - public static List Messages = new List(); - - public delegate void QuestionCallback(string answer); - private static QuestionCallback activeQuestionCallback; - - private static List commands = new List(); - public static List Commands - { - get { return commands; } - } - - private static string currentAutoCompletedCommand; - private static int currentAutoCompletedIndex; - - //used for keeping track of the message entered when pressing up/down - static int selectedIndex; - - private static List unsavedMessages = new List(); - private static int messagesPerFile = 800; - public const string SavePath = "ConsoleLogs"; - - static DebugConsole() - { - commands.Add(new Command("help", "", (string[] args) => - { - if (args.Length == 0) - { - foreach (Command c in commands) - { - if (string.IsNullOrEmpty(c.help)) continue; - NewMessage(c.help, Color.Cyan); - } - } - else - { - var matchingCommand = commands.Find(c => c.names.Any(name => name == args[0])); - if (matchingCommand == null) - { - NewMessage("Command " + args[0] + " not found.", Color.Red); - } - else - { - NewMessage(matchingCommand.help, Color.Cyan); - } - } - })); - - commands.Add(new Command("clientlist", "clientlist: List all the clients connected to the server.", (string[] args) => - { - if (GameMain.Server == null) return; - NewMessage("***************", Color.Cyan); - foreach (Client c in GameMain.Server.ConnectedClients) - { - NewMessage("- " + c.ID.ToString() + ": " + c.Name + (c.Character != null ? " playing " + c.Character.LogName : "") + ", " + c.Connection.RemoteEndPoint.Address.ToString(), Color.Cyan); - } - NewMessage("***************", Color.Cyan); - }, null, - (Client client, Vector2 cursorWorldPos, string[] args) => - { - GameMain.Server.SendConsoleMessage("***************", client); - foreach (Client c in GameMain.Server.ConnectedClients) - { - GameMain.Server.SendConsoleMessage("- " + c.ID.ToString() + ": " + c.Name + ", " + c.Connection.RemoteEndPoint.Address.ToString(), client); - } - GameMain.Server.SendConsoleMessage("***************", client); - })); - - commands.Add(new Command("traitorlist", "traitorlist: List all the traitors and their targets.", (string[] args) => - { - if (GameMain.Server == null) return; - TraitorManager traitorManager = GameMain.Server.TraitorManager; - if (traitorManager == null) return; - foreach (Traitor t in traitorManager.TraitorList) - { - NewMessage("- Traitor " + t.Character.Name + "'s target is " + t.TargetCharacter.Name + ".", Color.Cyan); - } - NewMessage("The code words are: " + traitorManager.codeWords + ", response: " + traitorManager.codeResponse + ".", Color.Cyan); - }, - null, - (Client client, Vector2 cursorPos, string[] args) => - { - TraitorManager traitorManager = GameMain.Server.TraitorManager; - if (traitorManager == null) return; - foreach (Traitor t in traitorManager.TraitorList) - { - GameMain.Server.SendConsoleMessage("- Traitor " + t.Character.Name + "'s target is " + t.TargetCharacter.Name + ".", client); - } - GameMain.Server.SendConsoleMessage("The code words are: " + traitorManager.codeWords + ", response: " + traitorManager.codeResponse + ".", client); - })); - - commands.Add(new Command("itemlist", "itemlist: List all the item prefabs available for spawning.", (string[] args) => - { - NewMessage("***************", Color.Cyan); - foreach (MapEntityPrefab ep in MapEntityPrefab.List) - { - var itemPrefab = ep as ItemPrefab; - if (itemPrefab == null || itemPrefab.Name == null) continue; - NewMessage("- " + itemPrefab.Name, Color.Cyan); - } - NewMessage("***************", Color.Cyan); - })); - - commands.Add(new Command("setpassword|setserverpassword", "setpassword [password]: Changes the password of the server that's being hosted.", (string[] args) => - { - if (GameMain.Server == null || args.Length == 0) return; - GameMain.Server.SetPassword(args[0]); - })); - - commands.Add(new Command("createfilelist", "", (string[] args) => - { - UpdaterUtil.SaveFileList("filelist.xml"); - })); - - commands.Add(new Command("spawn|spawncharacter", "spawn [creaturename] [near/inside/outside/cursor]: Spawn a creature at a random spawnpoint (use the second parameter to only select spawnpoints near/inside/outside the submarine).", (string[] args) => - { - string errorMsg; - SpawnCharacter(args, GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), out errorMsg); - if (!string.IsNullOrWhiteSpace(errorMsg)) - { - ThrowError(errorMsg); - } - }, - null, - (Client client, Vector2 cursorPos, string[] args) => - { - string errorMsg; - SpawnCharacter(args, cursorPos, out errorMsg); - if (!string.IsNullOrWhiteSpace(errorMsg)) - { - ThrowError(errorMsg); - } - }, - () => - { - List characterFiles = GameMain.Config.SelectedContentPackage.GetFilesOfType(ContentType.Character); - for (int i = 0; i < characterFiles.Count; i++) - { - characterFiles[i] = Path.GetFileNameWithoutExtension(characterFiles[i]).ToLowerInvariant(); - } - - return new string[][] - { - characterFiles.ToArray(), - new string[] { "near", "inside", "outside", "cursor" } - }; - })); - - commands.Add(new Command("spawnitem", "spawnitem [itemname] [cursor/inventory/cargo/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) => - { - SpawnItem(args, GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), Character.Controlled, out string errorMsg); - if (!string.IsNullOrWhiteSpace(errorMsg)) - { - ThrowError(errorMsg); - } - }, - null, - (Client client, Vector2 cursorWorldPos, string[] args) => - { - SpawnItem(args, cursorWorldPos, client.Character, out string errorMsg); - if (!string.IsNullOrWhiteSpace(errorMsg)) - { - GameMain.Server.SendConsoleMessage(errorMsg, client); - } - }, - () => - { - List itemNames = new List(); - foreach (MapEntityPrefab prefab in MapEntityPrefab.List) - { - if (prefab is ItemPrefab itemPrefab) itemNames.Add(itemPrefab.Name); - } - - return new string[][] - { - itemNames.ToArray(), - new string[] { "cursor", "inventory" } - }; - })); - - - commands.Add(new Command("disablecrewai", "disablecrewai: Disable the AI of the NPCs in the crew.", (string[] args) => - { - 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.SendConsoleMessage("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.SendConsoleMessage("Crew AI enabled", client); - })); - - commands.Add(new Command("autorestart", "autorestart [true/false]: Enable or disable round auto-restart.", (string[] args) => - { - if (GameMain.Server == null) return; - bool enabled = GameMain.Server.AutoRestart; - if (args.Length > 0) - { - bool.TryParse(args[0], out enabled); - } - else - { - enabled = !enabled; - } - if (enabled != GameMain.Server.AutoRestart) - { - if (GameMain.Server.AutoRestartInterval <= 0) GameMain.Server.AutoRestartInterval = 10; - GameMain.Server.AutoRestartTimer = GameMain.Server.AutoRestartInterval; - GameMain.Server.AutoRestart = enabled; -#if CLIENT - GameMain.NetLobbyScreen.SetAutoRestart(enabled, GameMain.Server.AutoRestartTimer); -#endif - 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) => - { - if (GameMain.Server == null) return; - if (args.Length > 0) - { - int parsedInt = 0; - if (int.TryParse(args[0], out parsedInt)) - { - if (parsedInt >= 0) - { - GameMain.Server.AutoRestart = true; - GameMain.Server.AutoRestartInterval = parsedInt; - if (GameMain.Server.AutoRestartTimer >= GameMain.Server.AutoRestartInterval) GameMain.Server.AutoRestartTimer = GameMain.Server.AutoRestartInterval; - NewMessage("Autorestart interval set to " + GameMain.Server.AutoRestartInterval + " seconds.", Color.White); - } - else - { - GameMain.Server.AutoRestart = false; - NewMessage("Autorestart disabled.", Color.White); - } -#if CLIENT - GameMain.NetLobbyScreen.SetAutoRestart(GameMain.Server.AutoRestart, GameMain.Server.AutoRestartTimer); -#endif - GameMain.NetLobbyScreen.LastUpdateID++; - } - } - }, null, null)); - - commands.Add(new Command("autorestarttimer", "autorestarttimer [seconds]: Set the current autorestart countdown to the specified value.", (string[] args) => - { - if (GameMain.Server == null) return; - if (args.Length > 0) - { - int parsedInt = 0; - if (int.TryParse(args[0], out parsedInt)) - { - if (parsedInt >= 0) - { - GameMain.Server.AutoRestart = true; - GameMain.Server.AutoRestartTimer = parsedInt; - if (GameMain.Server.AutoRestartInterval <= GameMain.Server.AutoRestartTimer) GameMain.Server.AutoRestartInterval = GameMain.Server.AutoRestartTimer; - GameMain.NetLobbyScreen.LastUpdateID++; - NewMessage("Autorestart timer set to " + GameMain.Server.AutoRestartTimer + " seconds.", Color.White); - } - else - { - GameMain.Server.AutoRestart = false; - NewMessage("Autorestart disabled.", Color.White); - } -#if CLIENT - GameMain.NetLobbyScreen.SetAutoRestart(GameMain.Server.AutoRestart, GameMain.Server.AutoRestartTimer); -#endif - 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) => - { - if (GameMain.Server == null) return; - if (args.Length < 1) - { - NewMessage("giveperm [id]: Grants administrative permissions to the player with the specified client ID.", Color.Cyan); - return; - } - - int id; - int.TryParse(args[0], out id); - var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); - if (client == null) - { - ThrowError("Client id \"" + id + "\" not found."); - return; - } - - NewMessage("Valid permissions are:",Color.White); - NewMessage(" - all",Color.White); - foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) - { - NewMessage(" - " + permission.ToString(),Color.White); - } - ShowQuestionPrompt("Permission to grant to \"" + client.Name + "\"?", (perm) => - { - 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)) - { - 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; - } - - NewMessage("Valid permissions are:", Color.White); - NewMessage(" - all", Color.White); - foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) - { - NewMessage(" - " + permission.ToString(), Color.White); - } - 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.SendConsoleMessage("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.SendConsoleMessage(perm + " is not a valid permission!", senderClient); - return; - } - } - client.GivePermission(permission); - GameMain.Server.UpdateClientPermissions(client); - GameMain.Server.SendConsoleMessage("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) => - { - if (GameMain.Server == null) return; - if (args.Length < 1) - { - NewMessage("revokeperm [id]: Revokes administrative permissions to the player with the specified client ID.", Color.Cyan); - return; - } - - int id; - int.TryParse(args[0], out id); - var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); - if (client == null) - { - ThrowError("Client id \"" + id + "\" not found."); - return; - } - - NewMessage("Valid permissions are:", Color.White); - NewMessage(" - all", Color.White); - foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) - { - NewMessage(" - " + permission.ToString(), Color.White); - } - ShowQuestionPrompt("Permission to revoke from \"" + client.Name + "\"?", (perm) => - { - 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)) - { - 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; - } - - NewMessage("Valid permissions are:", Color.White); - NewMessage(" - all", Color.White); - foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) - { - NewMessage(" - " + permission.ToString(), Color.White); - } - - 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.SendConsoleMessage("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.SendConsoleMessage(perm + " is not a valid permission!", senderClient); - return; - } - } - client.RemovePermission(permission); - GameMain.Server.UpdateClientPermissions(client); - GameMain.Server.SendConsoleMessage("Revoked " + perm + " permissions from " + client.Name + ".", senderClient); - NewMessage(senderClient.Name + " revoked " + perm + " permissions from " + client.Name + ".", Color.White); - })); - - - commands.Add(new Command("giverank", "giverank [id]: Assigns a specific rank (= a set of administrative permissions) to the player with the specified client ID.", (string[] args) => - { - if (GameMain.Server == null) return; - if (args.Length < 1) - { - NewMessage("giverank [id]: Assigns a specific rank(= a set of administrative permissions) to the player with the specified client ID.", Color.Cyan); - return; - } - - int id; - int.TryParse(args[0], out id); - var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); - if (client == null) - { - ThrowError("Client id \"" + id + "\" not found."); - return; - } - - NewMessage("Valid ranks are:", Color.White); - foreach (PermissionPreset permissionPreset in PermissionPreset.List) - { - NewMessage(" - " + permissionPreset.Name, Color.White); - } - - ShowQuestionPrompt("Rank to grant to \"" + client.Name + "\"?", (rank) => - { - PermissionPreset preset = PermissionPreset.List.Find(p => p.Name.ToLowerInvariant() == rank.ToLowerInvariant()); - if (preset == null) - { - ThrowError("Rank \"" + rank + "\" not found."); - return; - } - - client.SetPermissions(preset.Permissions, preset.PermittedCommands); - GameMain.Server.UpdateClientPermissions(client); - NewMessage("Assigned the rank \"" + preset.Name + "\" 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; - } - - NewMessage("Valid ranks are:", Color.White); - foreach (PermissionPreset permissionPreset in PermissionPreset.List) - { - NewMessage(" - " + permissionPreset.Name, Color.White); - } - ShowQuestionPrompt("Rank to grant to client #" + id + "?", (rank) => - { - GameMain.Client.SendConsoleCommand("giverank " + id + " " + rank); - }); -#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.SendConsoleMessage("Client id \"" + id + "\" not found.", senderClient); - return; - } - - string rank = string.Join("", args.Skip(1)); - PermissionPreset preset = PermissionPreset.List.Find(p => p.Name.ToLowerInvariant() == rank.ToLowerInvariant()); - if (preset == null) - { - GameMain.Server.SendConsoleMessage("Rank \"" + rank + "\" not found.", senderClient); - return; - } - - client.SetPermissions(preset.Permissions, preset.PermittedCommands); - GameMain.Server.UpdateClientPermissions(client); - GameMain.Server.SendConsoleMessage("Assigned the rank \"" + preset.Name + "\" to " + client.Name + ".", senderClient); - NewMessage(senderClient.Name + " granted the rank \"" + preset.Name + "\" to " + client.Name + ".", Color.White); - })); - - commands.Add(new Command("givecommandperm", "givecommandperm [id]: Gives the player with the specified client ID the permission to use the specified console commands.", (string[] args) => - { - if (GameMain.Server == null) return; - if (args.Length < 1) - { - NewMessage("givecommandperm [id]: Gives the player with the specified client ID the permission to use the specified console commands.", Color.Cyan); - return; - } - - int id; - int.TryParse(args[0], out id); - var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); - if (client == null) - { - ThrowError("Client id \"" + id + "\" not found."); - return; - } - - ShowQuestionPrompt("Console command permissions to grant to \"" + client.Name + "\"? You may enter multiple commands separated with a space.", (commandsStr) => - { - string[] splitCommands = commandsStr.Split(' '); - List grantedCommands = new List(); - for (int i = 0; i < splitCommands.Length; i++) - { - splitCommands[i] = splitCommands[i].Trim().ToLowerInvariant(); - Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i])); - if (matchingCommand == null) - { - ThrowError("Could not find the command \"" + splitCommands[i] + "\"!"); - } - else - { - grantedCommands.Add(matchingCommand); - } - } - - client.GivePermission(ClientPermissions.ConsoleCommands); - client.SetPermissions(client.Permissions, client.PermittedConsoleCommands.Union(grantedCommands).Distinct().ToList()); - GameMain.Server.UpdateClientPermissions(client); - NewMessage("Gave the client \"" + client.Name + "\" the permission to use console commands " + string.Join(", ", grantedCommands.Select(c => c.names[0])) + ".", 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("Console command permissions to grant to client #" + id + "? You may enter multiple commands separated with a space.", (commandNames) => - { - GameMain.Client.SendConsoleCommand("givecommandperm " + id + " " + commandNames); - }); -#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.SendConsoleMessage("Client id \"" + id + "\" not found.", senderClient); - return; - } - - string[] splitCommands = args.Skip(1).ToArray(); - List grantedCommands = new List(); - for (int i = 0; i < splitCommands.Length; i++) - { - splitCommands[i] = splitCommands[i].Trim().ToLowerInvariant(); - Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i])); - if (matchingCommand == null) - { - GameMain.Server.SendConsoleMessage("Could not find the command \"" + splitCommands[i] + "\"!", senderClient); - } - else - { - grantedCommands.Add(matchingCommand); - } - } - - client.GivePermission(ClientPermissions.ConsoleCommands); - client.SetPermissions(client.Permissions, client.PermittedConsoleCommands.Union(grantedCommands).Distinct().ToList()); - GameMain.Server.UpdateClientPermissions(client); - GameMain.Server.SendConsoleMessage("Gave the client \"" + client.Name + "\" the permission to use the console commands " + string.Join(", ", grantedCommands.Select(c => c.names[0])) + ".", senderClient); - NewMessage("Gave the client \"" + client.Name + "\" the permission to use the console commands " + string.Join(", ", grantedCommands.Select(c => c.names[0])) + ".", Color.White); - })); - - - commands.Add(new Command("revokecommandperm", "revokecommandperm [id]: Revokes permission to use the specified console commands from the player with the specified client ID.", (string[] args) => - { - if (GameMain.Server == null) return; - if (args.Length < 1) - { - NewMessage("revokecommandperm [id]: Revokes permission to use the specified console commands from the player with the specified client ID.", Color.Cyan); - return; - } - - int id; - int.TryParse(args[0], out id); - var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); - if (client == null) - { - ThrowError("Client id \"" + id + "\" not found."); - return; - } - - ShowQuestionPrompt("Console command permissions to revoke from \"" + client.Name + "\"? You may enter multiple commands separated with a space.", (commandsStr) => - { - string[] splitCommands = commandsStr.Split(' '); - List revokedCommands = new List(); - for (int i = 0; i < splitCommands.Length; i++) - { - splitCommands[i] = splitCommands[i].Trim().ToLowerInvariant(); - Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i])); - if (matchingCommand == null) - { - ThrowError("Could not find the command \"" + splitCommands[i] + "\"!"); - } - else - { - revokedCommands.Add(matchingCommand); - } - } - - client.SetPermissions(client.Permissions, client.PermittedConsoleCommands.Except(revokedCommands).ToList()); - GameMain.Server.UpdateClientPermissions(client); - NewMessage("Revoked \"" + client.Name + "\"'s permission to use the console commands " + string.Join(", ", revokedCommands.Select(c => c.names[0])) + ".", 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("Console command permissions to grant to client #" + id + "? You may enter multiple commands separated with a space.", (commandNames) => - { - GameMain.Client.SendConsoleCommand("givecommandperm " + id + " " + commandNames); - }); -#endif - }, - (Client senderClient, Vector2 cursorWorldPos, string[] args) => - { - if (args.Length < 2) return; - - int.TryParse(args[0], out int id); - var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); - if (client == null) - { - GameMain.Server.SendConsoleMessage("Client id \"" + id + "\" not found.", senderClient); - return; - } - - string[] splitCommands = args.Skip(1).ToArray(); - List revokedCommands = new List(); - for (int i = 0; i < splitCommands.Length; i++) - { - splitCommands[i] = splitCommands[i].Trim().ToLowerInvariant(); - Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i])); - if (matchingCommand == null) - { - GameMain.Server.SendConsoleMessage("Could not find the command \"" + splitCommands[i] + "\"!", senderClient); - } - else - { - revokedCommands.Add(matchingCommand); - } - } - - client.GivePermission(ClientPermissions.ConsoleCommands); - client.SetPermissions(client.Permissions, client.PermittedConsoleCommands.Except(revokedCommands).ToList()); - GameMain.Server.UpdateClientPermissions(client); - GameMain.Server.SendConsoleMessage("Revoked \"" + client.Name + "\"'s permission to use the console commands " + string.Join(", ", revokedCommands.Select(c => c.names[0])) + ".", senderClient); - NewMessage(senderClient.Name + " revoked \"" + client.Name + "\"'s permission to use the console commands " + string.Join(", ", revokedCommands.Select(c => c.names[0])) + ".", Color.White); - })); - - - commands.Add(new Command("showperm", "showperm [id]: Shows the current administrative permissions of the client with the specified client ID.", (string[] args) => - { - if (GameMain.Server == null) return; - if (args.Length < 1) - { - NewMessage("showperm [id]: Shows the current administrative permissions of the client with the specified client ID.", Color.Cyan); - return; - } - - int.TryParse(args[0], out int id); - var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); - if (client == null) - { - ThrowError("Client id \"" + id + "\" not found."); - return; - } - - if (client.Permissions == ClientPermissions.None) - { - NewMessage(client.Name + " has no special permissions.", Color.White); - return; - } - - NewMessage(client.Name + " has the following permissions:", Color.White); - foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) - { - if (permission == ClientPermissions.None || !client.HasPermission(permission)) continue; - System.Reflection.FieldInfo fi = typeof(ClientPermissions).GetField(permission.ToString()); - DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); - NewMessage(" - " + attributes[0].Description, Color.White); - } - if (client.HasPermission(ClientPermissions.ConsoleCommands)) - { - if (client.PermittedConsoleCommands.Count == 0) - { - NewMessage("No permitted console commands:", Color.White); - } - else - { - NewMessage("Permitted console commands:", Color.White); - foreach (Command permittedCommand in client.PermittedConsoleCommands) - { - NewMessage(" - " + permittedCommand.names[0], 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; - } - - GameMain.Client.SendConsoleCommand("showperm " + id); -#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.SendConsoleMessage("Client id \"" + id + "\" not found.", senderClient); - return; - } - - if (client.Permissions == ClientPermissions.None) - { - GameMain.Server.SendConsoleMessage(client.Name + " has no special permissions.", senderClient); - return; - } - - GameMain.Server.SendConsoleMessage(client.Name + " has the following permissions:", senderClient); - foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) - { - if (permission == ClientPermissions.None || !client.HasPermission(permission)) continue; - System.Reflection.FieldInfo fi = typeof(ClientPermissions).GetField(permission.ToString()); - DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); - GameMain.Server.SendConsoleMessage(" - " + attributes[0].Description, senderClient); - } - if (client.HasPermission(ClientPermissions.ConsoleCommands)) - { - if (client.PermittedConsoleCommands.Count == 0) - { - GameMain.Server.SendConsoleMessage("No permitted console commands:", senderClient); - } - else - { - GameMain.Server.SendConsoleMessage("Permitted console commands:", senderClient); - foreach (Command permittedCommand in client.PermittedConsoleCommands) - { - GameMain.Server.SendConsoleMessage(" - " + permittedCommand.names[0], senderClient); - } - } - } - })); - - commands.Add(new Command("togglekarma", "togglekarma: Toggles the karma system.", (string[] args) => - { - throw new NotImplementedException(); - 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; - - string playerName = string.Join(" ", args); - - ShowQuestionPrompt("Reason for kicking \"" + playerName + "\"?", (reason) => - { - GameMain.NetworkMember.KickPlayer(playerName, reason); - }); - }, - () => - { - if (GameMain.NetworkMember == null) return null; - - return new string[][] - { - GameMain.NetworkMember.ConnectedClients.Select(c => c.Name).ToArray() - }; - })); - - commands.Add(new Command("kickid", "kickid [id]: Kick the player with the specified client ID out of the server.", (string[] args) => - { - if (GameMain.NetworkMember == null || args.Length == 0) return; - - 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 kicking \"" + client.Name + "\"?", (reason) => - { - GameMain.NetworkMember.KickPlayer(client.Name, reason); - }); - })); - - commands.Add(new Command("ban", "ban [name]: Kick and ban the player from the server.", (string[] args) => - { - if (GameMain.NetworkMember == null || args.Length == 0) return; - - string clientName = string.Join(" ", args); - ShowQuestionPrompt("Reason for banning \"" + clientName + "\"?", (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)) - { - TimeSpan parsedBanDuration; - if (!TryParseTimeSpan(duration, out 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.NetworkMember.BanPlayer(clientName, reason, false, banDuration); - }); - }); - }, - () => - { - if (GameMain.NetworkMember == null) return null; - - return new string[][] - { - GameMain.NetworkMember.ConnectedClients.Select(c => c.Name).ToArray() - }; - })); - - commands.Add(new Command("banid", "banid [id]: Kick and ban the player with the specified client ID from the server.", (string[] args) => - { - if (GameMain.NetworkMember == null || args.Length == 0) return; - - 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) => - { - 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.NetworkMember.BanPlayer(client.Name, reason, false, banDuration); - }); - }); - })); - - - commands.Add(new Command("banip", "banip [ip]: Ban the IP address from the server.", (string[] args) => - { - if (GameMain.Server == 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; - } - - var clients = GameMain.Server.ConnectedClients.FindAll(c => c.Connection.RemoteEndPoint.Address.ToString() == args[0]); - if (clients.Count == 0) - { - GameMain.Server.BanList.BanPlayer("Unnamed", args[0], reason, banDuration); - } - else - { - foreach (Client cl in clients) - { - GameMain.Server.BanClient(cl, reason, false, banDuration); - } - } - }); - }); - }, - (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 = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args, false); - if (tpCharacter == null) return; - - var cam = GameMain.GameScreen.Cam; - tpCharacter.AnimController.CurrentHull = null; - 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 = (args.Length == 0) ? client.Character : 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); - }, - () => - { - return new string[][] - { - Character.CharacterList.Select(c => c.Name).Distinct().ToArray() - }; - })); - - commands.Add(new Command("godmode", "godmode: Toggle submarine godmode. Makes the main submarine invulnerable to damage.", (string[] args) => - { - if (Submarine.MainSub == null) return; - - 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.SendConsoleMessage(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", "locky: Lock vertical movement of the main submarine.", (string[] args) => - { - Submarine.LockY = !Submarine.LockY; - }, null, null)); - - commands.Add(new Command("dumpids", "", (string[] args) => - { - try - { - int count = args.Length == 0 ? 10 : int.Parse(args[0]); - Entity.DumpIds(count); - } - catch (Exception e) - { - ThrowError("Failed to dump ids", e); - } - })); - - 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 = (args.Length == 0) ? Character.Controlled : 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 = (args.Length == 0) ? client.Character : 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); - } - }, - () => - { - return new string[][] - { - Character.CharacterList.Select(c => c.Name).Distinct().ToArray() - }; - })); - - 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 = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args); - if (revivedCharacter == null) return; - - revivedCharacter.Revive(); - 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; - } - } - }, - null, - (Client client, Vector2 cursorWorldPos, string[] args) => - { - Character revivedCharacter = (args.Length == 0) ? client.Character : FindMatchingCharacter(args); - if (revivedCharacter == null) return; - - revivedCharacter.Revive(); - 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; - } - } - }, - () => - { - return new string[][] - { - Character.CharacterList.Select(c => c.Name).Distinct().ToArray() - }; - })); - - 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("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 = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args); - if (ragdolledCharacter != null) - { - ragdolledCharacter.IsForceRagdolled = !ragdolledCharacter.IsForceRagdolled; - } - }, - null, - (Client client, Vector2 cursorWorldPos, string[] args) => - { - Character ragdolledCharacter = (args.Length == 0) ? client.Character : FindMatchingCharacter(args); - if (ragdolledCharacter != null) - { - ragdolledCharacter.IsForceRagdolled = !ragdolledCharacter.IsForceRagdolled; - } - }, - () => - { - return new string[][] - { - Character.CharacterList.Select(c => c.Name).Distinct().ToArray() - }; - })); - - commands.Add(new Command("freecamera|freecam", "freecam: Detach the camera from the controlled character.", (string[] args) => - { - Character.Controlled = null; - GameMain.GameScreen.Cam.TargetPos = Vector2.Zero; - })); - - commands.Add(new Command("water|editwater", "water/editwater: Toggle water editing. Allows adding water into rooms by holding the left mouse button and removing it by holding the right mouse button.", (string[] args) => - { - if (GameMain.Client == null) - { - Hull.EditWater = !Hull.EditWater; - NewMessage(Hull.EditWater ? "Water editing on" : "Water editing off", Color.White); - } - })); - - commands.Add(new Command("fire|editfire", "fire/editfire: Allows putting up fires by left clicking.", (string[] args) => - { - if (GameMain.Client == null) - { - Hull.EditFire = !Hull.EditFire; - NewMessage(Hull.EditFire ? "Fire spawning on" : "Fire spawning off", Color.White); - } - })); - - commands.Add(new Command("explosion", "explosion [range] [force] [damage] [structuredamage] [emp strength]: Creates an explosion at the position of the cursor.", (string[] args) => - { - Vector2 explosionPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition); - float range = 500, force = 10, damage = 50, structureDamage = 10, empStrength = 0.0f; - 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); - if (args.Length > 4) float.TryParse(args[4], out empStrength); - new Explosion(range, force, damage, structureDamage, empStrength).Explode(explosionPos); - }, - null, - (Client client, Vector2 cursorWorldPos, string[] args) => - { - Vector2 explosionPos = cursorWorldPos; - float range = 500, force = 10, damage = 50, structureDamage = 10, empStrength = 0.0f; ; - 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); - if (args.Length > 4) float.TryParse(args[4], out empStrength); - new Explosion(range, force, damage, structureDamage, empStrength).Explode(explosionPos); - })); - - commands.Add(new Command("fixitems", "fixitems: Repairs all items and restores them to full condition.", (string[] args) => - { - foreach (Item it in Item.ItemList) - { - it.Condition = it.Prefab.Health; - } - }, null, null)); - - commands.Add(new Command("fixhulls|fixwalls", "fixwalls/fixhulls: Fixes all walls.", (string[] args) => - { - foreach (Structure w in Structure.WallList) - { - for (int i = 0; i < w.SectionCount; i++) - { - 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) => - { - Item reactorItem = Item.ItemList.Find(i => i.GetComponent() != null); - if (reactorItem == null) return; - - float power = 5000.0f; - if (args.Length > 0) float.TryParse(args[0], out power); - - var reactor = reactorItem.GetComponent(); - reactor.ShutDownTemp = power == 0 ? 0 : 7000.0f; - reactor.AutoTemp = true; - reactor.Temperature = power; - - if (GameMain.Server != null) - { - reactorItem.CreateServerEvent(reactor); - } - }, null, null)); - - commands.Add(new Command("oxygen|air", "oxygen/air: Replenishes the oxygen levels in every room to 100%.", (string[] args) => - { - foreach (Hull hull in Hull.hullList) - { - hull.OxygenPercentage = 100.0f; - } - }, null, null)); - - commands.Add(new Command("kill", "kill [character]: Immediately kills the specified character.", (string[] args) => - { - 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); - }, - () => - { - return new string[][] - { - Character.CharacterList.Select(c => c.Name).Distinct().ToArray() - }; - })); - - commands.Add(new Command("killmonsters", "killmonsters: Immediately kills all AI-controlled enemies in the level.", (string[] args) => - { - foreach (Character c in Character.CharacterList) - { - if (!(c.AIController is EnemyAIController)) continue; - c.AddDamage(CauseOfDeath.Damage, c.MaxHealth * 2, null); - } - }, null, null)); - - commands.Add(new Command("netstats", "netstats: Toggles the visibility of the network statistics UI.", (string[] args) => - { - if (GameMain.Server == null) return; - GameMain.Server.ShowNetStats = !GameMain.Server.ShowNetStats; - })); - - commands.Add(new Command("setclientcharacter", "setclientcharacter [client name] ; [character name]: Gives the client control of the specified character.", (string[] args) => - { - 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]\""); - 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) - { - 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.SendConsoleMessage("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.SendConsoleMessage("Client \"" + clientName + "\" not found.", senderClient); - } - - var character = FindMatchingCharacter(argsRight, false); - GameMain.Server.SetClientCharacter(client, character); - }, - () => - { - if (GameMain.NetworkMember == null) return null; - - return new string[][] - { - GameMain.NetworkMember.ConnectedClients.Select(c => c.Name).ToArray(), - Character.CharacterList.Select(c => c.Name).Distinct().ToArray() - }; - })); - - commands.Add(new Command("campaigninfo|campaignstatus", "campaigninfo: Display information about the state of the currently active campaign.", (string[] args) => - { - var campaign = GameMain.GameSession?.GameMode as CampaignMode; - if (campaign == null) - { - ThrowError("No campaign active!"); - return; - } - - campaign.LogState(); - })); - - commands.Add(new Command("campaigndestination|setcampaigndestination", "campaigndestination [index]: Set the location to head towards in the currently active campaign.", (string[] args) => - { - 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; - } - Location location = campaign.Map.CurrentLocation.Connections[destinationIndex].OtherLocation(campaign.Map.CurrentLocation); - campaign.Map.SelectLocation(location); - NewMessage(location.Name+" selected.", Color.White); - }); - } - 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; - } - Location location = campaign.Map.CurrentLocation.Connections[destinationIndex].OtherLocation(campaign.Map.CurrentLocation); - 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.SendConsoleMessage("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.SendConsoleMessage("Index out of bounds!", senderClient); - return; - } - Location location = campaign.Map.CurrentLocation.Connections[destinationIndex].OtherLocation(campaign.Map.CurrentLocation); - campaign.Map.SelectLocation(location); - GameMain.Server.SendConsoleMessage(location.Name + " selected.", senderClient); - })); - -#if DEBUG - commands.Add(new Command("spamevents", "A debug command that immediately creates entity events for all items, characters and structures.", (string[] args) => - { - foreach (Item item in Item.ItemList) - { - for (int i = 0; i < item.components.Count; i++) - { - if (item.components[i] is IServerSerializable) - { - GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ComponentState, i }); - } - var itemContainer = item.GetComponent(); - if (itemContainer != null) - { - GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.InventoryState, 0 }); - } - - GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.Status }); - - item.NeedsPositionUpdate = true; - } - } - - foreach (Character c in Character.CharacterList) - { - GameMain.Server.CreateEntityEvent(c, new object[] { NetEntityEvent.Type.Status }); - } - - foreach (Structure wall in Structure.WallList) - { - GameMain.Server.CreateEntityEvent(wall); - } - }, null, null)); - - commands.Add(new Command("flipx", "flipx: mirror the main submarine horizontally", (string[] args) => - { - Submarine.MainSub?.FlipX(); - })); -#endif - InitProjectSpecific(); - - commands.Sort((c1, c2) => c1.names[0].CompareTo(c2.names[0])); - } - - private static string[] SplitCommand(string command) - { - command = command.Trim(); - - List commands = new List(); - int escape = 0; - bool inQuotes = false; - string piece = ""; - - for (int i = 0; i < command.Length; i++) - { - if (command[i] == '\\') - { - if (escape == 0) escape = 2; - else piece += '\\'; - } - else if (command[i] == '"') - { - if (escape == 0) inQuotes = !inQuotes; - else piece += '"'; - } - else if (command[i] == ' ' && !inQuotes) - { - if (!string.IsNullOrWhiteSpace(piece)) commands.Add(piece); - piece = ""; - } - else if (escape == 0) piece += command[i]; - - if (escape > 0) escape--; - } - - if (!string.IsNullOrWhiteSpace(piece)) commands.Add(piece); //add final piece - - return commands.ToArray(); - } - - public static string AutoComplete(string command) - { - string[] splitCommand = SplitCommand(command); - string[] args = splitCommand.Skip(1).ToArray(); - - //if an argument is given or the last character is a space, attempt to autocomplete the argument - if (args.Length > 0 || (command.Length > 0 && command.Last() == ' ')) - { - Command matchingCommand = commands.Find(c => c.names.Contains(splitCommand[0])); - if (matchingCommand == null || matchingCommand.GetValidArgs == null) return command; - - int autoCompletedArgIndex = args.Length > 0 && command.Last() != ' ' ? args.Length - 1 : args.Length; - - //get all valid arguments for the given command - string[][] allArgs = matchingCommand.GetValidArgs(); - if (allArgs == null || allArgs.GetLength(0) < autoCompletedArgIndex + 1) return command; - - if (string.IsNullOrEmpty(currentAutoCompletedCommand)) - { - currentAutoCompletedCommand = autoCompletedArgIndex > args.Length - 1 ? " " : args.Last(); - } - - //find all valid autocompletions for the given argument - string[] validArgs = allArgs[autoCompletedArgIndex].Where(arg => - currentAutoCompletedCommand.Trim().Length <= arg.Length && - arg.Substring(0, currentAutoCompletedCommand.Trim().Length).ToLower() == currentAutoCompletedCommand.Trim().ToLower()).ToArray(); - - if (validArgs.Length == 0) return command; - - currentAutoCompletedIndex = currentAutoCompletedIndex % validArgs.Length; - string autoCompletedArg = validArgs[currentAutoCompletedIndex++]; - - //add quotation marks to args that contain spaces - if (autoCompletedArg.Contains(' ')) autoCompletedArg = '"' + autoCompletedArg + '"'; - for (int i = 0; i < splitCommand.Length; i++) - { - if (splitCommand[i].Contains(' ')) splitCommand[i] = '"' + splitCommand[i] + '"'; - } - - return string.Join(" ", autoCompletedArgIndex >= args.Length ? splitCommand : splitCommand.Take(splitCommand.Length - 1)) + " " + autoCompletedArg; - } - else - { - if (string.IsNullOrWhiteSpace(currentAutoCompletedCommand)) - { - currentAutoCompletedCommand = command; - } - - List matchingCommands = new List(); - foreach (Command c in commands) - { - foreach (string name in c.names) - { - if (currentAutoCompletedCommand.Length > name.Length) continue; - if (currentAutoCompletedCommand == name.Substring(0, currentAutoCompletedCommand.Length)) - { - matchingCommands.Add(name); - } - } - } - - if (matchingCommands.Count == 0) return command; - - currentAutoCompletedIndex = currentAutoCompletedIndex % matchingCommands.Count; - return matchingCommands[currentAutoCompletedIndex++]; - } - } - - private static string AutoCompleteStr(string str, IEnumerable validStrings) - { - if (string.IsNullOrEmpty(str)) return str; - foreach (string validStr in validStrings) - { - if (validStr.Length > str.Length && validStr.Substring(0, str.Length) == str) return validStr; - } - return str; - } - - public static void ResetAutoComplete() - { - currentAutoCompletedCommand = ""; - currentAutoCompletedIndex = 0; - } - - public static string SelectMessage(int direction) - { - if (Messages.Count == 0) return ""; - - direction = MathHelper.Clamp(direction, -1, 1); - - int i = 0; - do - { - selectedIndex += direction; - if (selectedIndex < 0) selectedIndex = Messages.Count - 1; - selectedIndex = selectedIndex % Messages.Count; - if (++i >= Messages.Count) break; - } while (!Messages[selectedIndex].IsCommand); - - return Messages[selectedIndex].Text; - } - - public static void ExecuteCommand(string command) - { - if (activeQuestionCallback != null) - { -#if CLIENT - activeQuestionText = null; -#endif - NewMessage(command, Color.White, true); - //reset the variable before invoking the delegate because the method may need to activate another question - var temp = activeQuestionCallback; - activeQuestionCallback = null; - temp(command); - return; - } - - if (string.IsNullOrWhiteSpace(command) || command == "\\" || command == "\n") return; - - string[] splitCommand = SplitCommand(command); - if (splitCommand.Length == 0) - { - ThrowError("Failed to execute command \"" + command + "\"!"); - GameAnalyticsManager.AddErrorEventOnce( - "DebugConsole.ExecuteCommand:LengthZero", - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, - "Failed to execute command \"" + command + "\"!"); - return; - } - - if (!splitCommand[0].ToLowerInvariant().Equals("admin")) - { - NewMessage(command, Color.White, true); - } - -#if CLIENT - if (GameMain.Client != null) - { - if (GameMain.Client.HasConsoleCommandPermission(splitCommand[0].ToLowerInvariant())) - { - 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 !DEBUG - if (!IsCommandPermitted(splitCommand[0].ToLowerInvariant(), GameMain.Client)) - { - ThrowError("You're not permitted to use the command \"" + splitCommand[0].ToLowerInvariant() + "\"!"); - return; - } -#endif - } -#endif - - bool commandFound = false; - foreach (Command c in commands) - { - if (c.names.Contains(splitCommand[0].ToLowerInvariant())) - { - c.Execute(splitCommand.Skip(1).ToArray()); - commandFound = true; - break; - } - } - - if (!commandFound) - { - 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.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; - } - - string[] splitCommand = SplitCommand(command); - Command matchingCommand = commands.Find(c => c.names.Contains(splitCommand[0].ToLowerInvariant())); - if (matchingCommand != null && !client.PermittedConsoleCommands.Contains(matchingCommand)) - { - 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.SendConsoleMessage("Command \"" + splitCommand[0] + "\" not found.", client); - return; - } - - if (!MathUtils.IsValid(cursorWorldPos)) - { - GameMain.Server.SendConsoleMessage("Could not execute command \"" + command + "\" - invalid cursor position.", client); - NewMessage(client.Name + " attempted to execute the console command \"" + command + "\" with invalid cursor position.", Color.White); - 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); - } - } - - - private static Character FindMatchingCharacter(string[] args, bool ignoreRemotePlayers = false) - { - if (args.Length == 0) return null; - - int characterIndex; - string characterName; - if (int.TryParse(args.Last(), out characterIndex) && args.Length > 1) - { - characterName = string.Join(" ", args.Take(args.Length - 1)).ToLowerInvariant(); - } - else - { - characterName = string.Join(" ", args).ToLowerInvariant(); - characterIndex = -1; - } - - var matchingCharacters = Character.CharacterList.FindAll(c => (!ignoreRemotePlayers || !c.IsRemotePlayer) && c.Name.ToLowerInvariant() == characterName); - - if (!matchingCharacters.Any()) - { - NewMessage("Character \""+ characterName + "\" not found", Color.Red); - return null; - } - - if (characterIndex == -1) - { - if (matchingCharacters.Count > 1) - { - NewMessage( - "Found multiple matching characters. " + - "Use \"[charactername] [0-" + (matchingCharacters.Count - 1) + "]\" to choose a specific character.", - Color.LightGray); - } - return matchingCharacters[0]; - } - else if (characterIndex < 0 || characterIndex >= matchingCharacters.Count) - { - ThrowError("Character index out of range. Select an index between 0 and " + (matchingCharacters.Count - 1)); - } - else - { - return matchingCharacters[characterIndex]; - } - - 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, Character controlledCharacter, out string errorMsg) - { - errorMsg = ""; - if (args.Length < 1) return; - - Vector2? spawnPos = null; - Inventory spawnInventory = null; - - int extraParams = 0; - switch (args.Last().ToLowerInvariant()) - { - case "cursor": - extraParams = 1; - spawnPos = cursorPos; - break; - case "inventory": - extraParams = 1; - spawnInventory = controlledCharacter == null ? null : controlledCharacter.Inventory; - break; - case "cargo": - var wp = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub); - spawnPos = wp == null ? Vector2.Zero : wp.WorldPosition; - break; - //Dont do a thing, random is basically Human points anyways - its in the help description. - case "random": - extraParams = 1; - return; - default: - extraParams = 0; - break; - } - - string itemName = string.Join(" ", args.Take(args.Length - extraParams)).ToLowerInvariant(); - - ItemPrefab itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab; - if (itemPrefab == null && extraParams == 0) - { - if (GameMain.Server != null) - { - var client = GameMain.Server.ConnectedClients.Find(c => c.Name.ToLower() == args.Last().ToLower()); - if (client != null) - { - extraParams += 1; - itemName = string.Join(" ", args.Take(args.Length - extraParams)).ToLowerInvariant(); - if (client.Character != null && client.Character.Name == args.Last().ToLower()) spawnInventory = client.Character.Inventory; - itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab; - } - } - } - //Check again if the item can be found again after having checked for a character - if (itemPrefab == null) - { - errorMsg = "Item \"" + itemName + "\" not found!"; - return; - } - - if ((spawnPos == null || spawnPos == Vector2.Zero) && 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, bool isCommand = false) - { - if (string.IsNullOrEmpty((msg))) return; - -#if SERVER - var newMsg = new ColoredText(msg, color, isCommand); - Messages.Add(newMsg); - - //TODO: REMOVE - Console.ForegroundColor = XnaToConsoleColor.Convert(color); - Console.WriteLine(msg); - Console.ForegroundColor = ConsoleColor.White; - - if (GameSettings.SaveDebugConsoleLogs) - { - unsavedMessages.Add(newMsg); - if (unsavedMessages.Count >= messagesPerFile) - { - SaveLogs(); - unsavedMessages.Clear(); - } - } - - if (Messages.Count > MaxMessages) - { - Messages.RemoveRange(0, Messages.Count - MaxMessages); - } -#elif CLIENT - lock (queuedMessages) - { - queuedMessages.Enqueue(new ColoredText(msg, color, isCommand)); - } -#endif - } - - public static void ShowQuestionPrompt(string question, QuestionCallback onAnswered) - { - -#if CLIENT - 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) - { - timeSpan = new TimeSpan(); - if (string.IsNullOrWhiteSpace(s)) return false; - - string currNum = ""; - foreach (char c in s) - { - if (char.IsDigit(c)) - { - currNum += c; - } - else if (char.IsWhiteSpace(c)) - { - continue; - } - else - { - int parsedNum = 0; - if (!int.TryParse(currNum, out parsedNum)) - { - return false; - } - - switch (c) - { - case 'd': - timeSpan += new TimeSpan(parsedNum, 0, 0, 0, 0); - break; - case 'h': - timeSpan += new TimeSpan(0, parsedNum, 0, 0, 0); - break; - case 'm': - timeSpan += new TimeSpan(0, 0, parsedNum, 0, 0); - break; - case 's': - timeSpan += new TimeSpan(0, 0, 0, parsedNum, 0); - break; - default: - return false; - } - - currNum = ""; - } - } - - 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) - { - if (GameSettings.VerboseLogging) NewMessage(message, Color.Gray); - } - - public static void ThrowError(string error, Exception e = null) - { - if (e != null) - { - error += " {" + e.Message + "}\n" + e.StackTrace; - } - System.Diagnostics.Debug.WriteLine(error); - NewMessage(error, Color.Red); -#if CLIENT - isOpen = true; -#endif - } - - - public static void SaveLogs() - { - if (unsavedMessages.Count == 0) return; - if (!Directory.Exists(SavePath)) - { - try - { - Directory.CreateDirectory(SavePath); - } - catch (Exception e) - { - ThrowError("Failed to create a folder for debug console logs", e); - return; - } - } - - string fileName = "DebugConsoleLog_" + DateTime.Now.ToShortDateString() + "_" + DateTime.Now.ToShortTimeString() + ".txt"; - var invalidChars = Path.GetInvalidFileNameChars(); - foreach (char invalidChar in invalidChars) - { - fileName = fileName.Replace(invalidChar.ToString(), ""); - } - - string filePath = Path.Combine(SavePath, fileName); - if (File.Exists(filePath)) - { - int fileNum = 2; - while (File.Exists(filePath + " (" + fileNum + ")")) - { - fileNum++; - } - filePath = filePath + " (" + fileNum + ")"; - } - - try - { - File.WriteAllLines(filePath, unsavedMessages.Select(l => "[" + l.Time + "] " + l.Text)); - } - catch (Exception e) - { - unsavedMessages.Clear(); - ThrowError("Saving debug console log to " + filePath + " failed", e); - } - } - } -} +using Barotrauma.Items.Components; +using Barotrauma.Networking; +using FarseerPhysics; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; + +namespace Barotrauma +{ + struct ColoredText + { + public string Text; + public Color Color; + public bool IsCommand; + + public readonly string Time; + + public ColoredText(string text, Color color, bool isCommand) + { + this.Text = text; + this.Color = color; + this.IsCommand = isCommand; + + Time = DateTime.Now.ToString(); + } + } + + static partial class DebugConsole + { + public class Command + { + public readonly string[] names; + public readonly string help; + + private Action onExecute; + + /// + /// 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 Func GetValidArgs; + + 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, Func getValidArgs = null) + { + names = name.Split('|'); + this.help = help; + + this.onExecute = onExecute; + this.onClientExecute = onClientExecute; + this.onClientRequestExecute = onClientRequestExecute; + + this.GetValidArgs = getValidArgs; + } + + + /// + /// 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, Func getValidArgs = null) + { + names = name.Split('|'); + this.help = help; + + this.onExecute = onExecute; + this.onClientExecute = onExecute; + + this.GetValidArgs = getValidArgs; + } + + public void Execute(string[] args) + { + if (onExecute == null) return; + onExecute(args); + } + + public void ClientExecute(string[] args) + { + onClientExecute(args); + } + + public void ServerExecuteOnClientRequest(Client client, Vector2 cursorWorldPos, string[] args) + { + if (onClientRequestExecute == null) + { + if (onExecute == null) return; + onExecute(args); + } + else + { + onClientRequestExecute(client, cursorWorldPos, args); + } + } + } + + const int MaxMessages = 200; + + public static List Messages = new List(); + + public delegate void QuestionCallback(string answer); + private static QuestionCallback activeQuestionCallback; + + private static List commands = new List(); + public static List Commands + { + get { return commands; } + } + + private static string currentAutoCompletedCommand; + private static int currentAutoCompletedIndex; + + //used for keeping track of the message entered when pressing up/down + static int selectedIndex; + + private static List unsavedMessages = new List(); + private static int messagesPerFile = 800; + public const string SavePath = "ConsoleLogs"; + + static DebugConsole() + { + commands.Add(new Command("help", "", (string[] args) => + { + if (args.Length == 0) + { + foreach (Command c in commands) + { + if (string.IsNullOrEmpty(c.help)) continue; + NewMessage(c.help, Color.Cyan); + } + } + else + { + var matchingCommand = commands.Find(c => c.names.Any(name => name == args[0])); + if (matchingCommand == null) + { + NewMessage("Command " + args[0] + " not found.", Color.Red); + } + else + { + NewMessage(matchingCommand.help, Color.Cyan); + } + } + })); + + commands.Add(new Command("clientlist", "clientlist: List all the clients connected to the server.", (string[] args) => + { + if (GameMain.Server == null) return; + NewMessage("***************", Color.Cyan); + foreach (Client c in GameMain.Server.ConnectedClients) + { + NewMessage("- " + c.ID.ToString() + ": " + c.Name + (c.Character != null ? " playing " + c.Character.LogName : "") + ", " + c.Connection.RemoteEndPoint.Address.ToString(), Color.Cyan); + } + NewMessage("***************", Color.Cyan); + }, null, + (Client client, Vector2 cursorWorldPos, string[] args) => + { + GameMain.Server.SendConsoleMessage("***************", client); + foreach (Client c in GameMain.Server.ConnectedClients) + { + GameMain.Server.SendConsoleMessage("- " + c.ID.ToString() + ": " + c.Name + ", " + c.Connection.RemoteEndPoint.Address.ToString(), client); + } + GameMain.Server.SendConsoleMessage("***************", client); + })); + + commands.Add(new Command("traitorlist", "traitorlist: List all the traitors and their targets.", (string[] args) => + { + if (GameMain.Server == null) return; + TraitorManager traitorManager = GameMain.Server.TraitorManager; + if (traitorManager == null) return; + foreach (Traitor t in traitorManager.TraitorList) + { + NewMessage("- Traitor " + t.Character.Name + "'s target is " + t.TargetCharacter.Name + ".", Color.Cyan); + } + NewMessage("The code words are: " + traitorManager.codeWords + ", response: " + traitorManager.codeResponse + ".", Color.Cyan); + }, + null, + (Client client, Vector2 cursorPos, string[] args) => + { + TraitorManager traitorManager = GameMain.Server.TraitorManager; + if (traitorManager == null) return; + foreach (Traitor t in traitorManager.TraitorList) + { + GameMain.Server.SendConsoleMessage("- Traitor " + t.Character.Name + "'s target is " + t.TargetCharacter.Name + ".", client); + } + GameMain.Server.SendConsoleMessage("The code words are: " + traitorManager.codeWords + ", response: " + traitorManager.codeResponse + ".", client); + })); + + commands.Add(new Command("itemlist", "itemlist: List all the item prefabs available for spawning.", (string[] args) => + { + NewMessage("***************", Color.Cyan); + foreach (MapEntityPrefab ep in MapEntityPrefab.List) + { + var itemPrefab = ep as ItemPrefab; + if (itemPrefab == null || itemPrefab.Name == null) continue; + NewMessage("- " + itemPrefab.Name, Color.Cyan); + } + NewMessage("***************", Color.Cyan); + })); + + commands.Add(new Command("setpassword|setserverpassword", "setpassword [password]: Changes the password of the server that's being hosted.", (string[] args) => + { + if (GameMain.Server == null || args.Length == 0) return; + GameMain.Server.SetPassword(args[0]); + })); + + commands.Add(new Command("createfilelist", "", (string[] args) => + { + UpdaterUtil.SaveFileList("filelist.xml"); + })); + + commands.Add(new Command("spawn|spawncharacter", "spawn [creaturename] [near/inside/outside/cursor]: Spawn a creature at a random spawnpoint (use the second parameter to only select spawnpoints near/inside/outside the submarine).", (string[] args) => + { + string errorMsg; + SpawnCharacter(args, GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), out errorMsg); + if (!string.IsNullOrWhiteSpace(errorMsg)) + { + ThrowError(errorMsg); + } + }, + null, + (Client client, Vector2 cursorPos, string[] args) => + { + string errorMsg; + SpawnCharacter(args, cursorPos, out errorMsg); + if (!string.IsNullOrWhiteSpace(errorMsg)) + { + ThrowError(errorMsg); + } + }, + () => + { + List characterFiles = GameMain.Config.SelectedContentPackage.GetFilesOfType(ContentType.Character); + for (int i = 0; i < characterFiles.Count; i++) + { + characterFiles[i] = Path.GetFileNameWithoutExtension(characterFiles[i]).ToLowerInvariant(); + } + + return new string[][] + { + characterFiles.ToArray(), + new string[] { "near", "inside", "outside", "cursor" } + }; + })); + + commands.Add(new Command("spawnitem", "spawnitem [itemname] [cursor/inventory/cargo/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) => + { + SpawnItem(args, GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), Character.Controlled, out string errorMsg); + if (!string.IsNullOrWhiteSpace(errorMsg)) + { + ThrowError(errorMsg); + } + }, + null, + (Client client, Vector2 cursorWorldPos, string[] args) => + { + SpawnItem(args, cursorWorldPos, client.Character, out string errorMsg); + if (!string.IsNullOrWhiteSpace(errorMsg)) + { + GameMain.Server.SendConsoleMessage(errorMsg, client); + } + }, + () => + { + List itemNames = new List(); + foreach (MapEntityPrefab prefab in MapEntityPrefab.List) + { + if (prefab is ItemPrefab itemPrefab) itemNames.Add(itemPrefab.Name); + } + + return new string[][] + { + itemNames.ToArray(), + new string[] { "cursor", "inventory" } + }; + })); + + + commands.Add(new Command("disablecrewai", "disablecrewai: Disable the AI of the NPCs in the crew.", (string[] args) => + { + 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.SendConsoleMessage("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.SendConsoleMessage("Crew AI enabled", client); + })); + + commands.Add(new Command("autorestart", "autorestart [true/false]: Enable or disable round auto-restart.", (string[] args) => + { + if (GameMain.Server == null) return; + bool enabled = GameMain.Server.AutoRestart; + if (args.Length > 0) + { + bool.TryParse(args[0], out enabled); + } + else + { + enabled = !enabled; + } + if (enabled != GameMain.Server.AutoRestart) + { + if (GameMain.Server.AutoRestartInterval <= 0) GameMain.Server.AutoRestartInterval = 10; + GameMain.Server.AutoRestartTimer = GameMain.Server.AutoRestartInterval; + GameMain.Server.AutoRestart = enabled; +#if CLIENT + GameMain.NetLobbyScreen.SetAutoRestart(enabled, GameMain.Server.AutoRestartTimer); +#endif + 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) => + { + if (GameMain.Server == null) return; + if (args.Length > 0) + { + int parsedInt = 0; + if (int.TryParse(args[0], out parsedInt)) + { + if (parsedInt >= 0) + { + GameMain.Server.AutoRestart = true; + GameMain.Server.AutoRestartInterval = parsedInt; + if (GameMain.Server.AutoRestartTimer >= GameMain.Server.AutoRestartInterval) GameMain.Server.AutoRestartTimer = GameMain.Server.AutoRestartInterval; + NewMessage("Autorestart interval set to " + GameMain.Server.AutoRestartInterval + " seconds.", Color.White); + } + else + { + GameMain.Server.AutoRestart = false; + NewMessage("Autorestart disabled.", Color.White); + } +#if CLIENT + GameMain.NetLobbyScreen.SetAutoRestart(GameMain.Server.AutoRestart, GameMain.Server.AutoRestartTimer); +#endif + GameMain.NetLobbyScreen.LastUpdateID++; + } + } + }, null, null)); + + commands.Add(new Command("autorestarttimer", "autorestarttimer [seconds]: Set the current autorestart countdown to the specified value.", (string[] args) => + { + if (GameMain.Server == null) return; + if (args.Length > 0) + { + int parsedInt = 0; + if (int.TryParse(args[0], out parsedInt)) + { + if (parsedInt >= 0) + { + GameMain.Server.AutoRestart = true; + GameMain.Server.AutoRestartTimer = parsedInt; + if (GameMain.Server.AutoRestartInterval <= GameMain.Server.AutoRestartTimer) GameMain.Server.AutoRestartInterval = GameMain.Server.AutoRestartTimer; + GameMain.NetLobbyScreen.LastUpdateID++; + NewMessage("Autorestart timer set to " + GameMain.Server.AutoRestartTimer + " seconds.", Color.White); + } + else + { + GameMain.Server.AutoRestart = false; + NewMessage("Autorestart disabled.", Color.White); + } +#if CLIENT + GameMain.NetLobbyScreen.SetAutoRestart(GameMain.Server.AutoRestart, GameMain.Server.AutoRestartTimer); +#endif + 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) => + { + if (GameMain.Server == null) return; + if (args.Length < 1) + { + NewMessage("giveperm [id]: Grants administrative permissions to the player with the specified client ID.", Color.Cyan); + return; + } + + int id; + int.TryParse(args[0], out id); + var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); + if (client == null) + { + ThrowError("Client id \"" + id + "\" not found."); + return; + } + + NewMessage("Valid permissions are:",Color.White); + NewMessage(" - all",Color.White); + foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) + { + NewMessage(" - " + permission.ToString(),Color.White); + } + ShowQuestionPrompt("Permission to grant to \"" + client.Name + "\"?", (perm) => + { + 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)) + { + 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; + } + + NewMessage("Valid permissions are:", Color.White); + NewMessage(" - all", Color.White); + foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) + { + NewMessage(" - " + permission.ToString(), Color.White); + } + 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.SendConsoleMessage("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.SendConsoleMessage(perm + " is not a valid permission!", senderClient); + return; + } + } + client.GivePermission(permission); + GameMain.Server.UpdateClientPermissions(client); + GameMain.Server.SendConsoleMessage("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) => + { + if (GameMain.Server == null) return; + if (args.Length < 1) + { + NewMessage("revokeperm [id]: Revokes administrative permissions to the player with the specified client ID.", Color.Cyan); + return; + } + + int id; + int.TryParse(args[0], out id); + var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); + if (client == null) + { + ThrowError("Client id \"" + id + "\" not found."); + return; + } + + NewMessage("Valid permissions are:", Color.White); + NewMessage(" - all", Color.White); + foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) + { + NewMessage(" - " + permission.ToString(), Color.White); + } + ShowQuestionPrompt("Permission to revoke from \"" + client.Name + "\"?", (perm) => + { + 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)) + { + 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; + } + + NewMessage("Valid permissions are:", Color.White); + NewMessage(" - all", Color.White); + foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) + { + NewMessage(" - " + permission.ToString(), Color.White); + } + + 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.SendConsoleMessage("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.SendConsoleMessage(perm + " is not a valid permission!", senderClient); + return; + } + } + client.RemovePermission(permission); + GameMain.Server.UpdateClientPermissions(client); + GameMain.Server.SendConsoleMessage("Revoked " + perm + " permissions from " + client.Name + ".", senderClient); + NewMessage(senderClient.Name + " revoked " + perm + " permissions from " + client.Name + ".", Color.White); + })); + + + commands.Add(new Command("giverank", "giverank [id]: Assigns a specific rank (= a set of administrative permissions) to the player with the specified client ID.", (string[] args) => + { + if (GameMain.Server == null) return; + if (args.Length < 1) + { + NewMessage("giverank [id]: Assigns a specific rank(= a set of administrative permissions) to the player with the specified client ID.", Color.Cyan); + return; + } + + int id; + int.TryParse(args[0], out id); + var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); + if (client == null) + { + ThrowError("Client id \"" + id + "\" not found."); + return; + } + + NewMessage("Valid ranks are:", Color.White); + foreach (PermissionPreset permissionPreset in PermissionPreset.List) + { + NewMessage(" - " + permissionPreset.Name, Color.White); + } + + ShowQuestionPrompt("Rank to grant to \"" + client.Name + "\"?", (rank) => + { + PermissionPreset preset = PermissionPreset.List.Find(p => p.Name.ToLowerInvariant() == rank.ToLowerInvariant()); + if (preset == null) + { + ThrowError("Rank \"" + rank + "\" not found."); + return; + } + + client.SetPermissions(preset.Permissions, preset.PermittedCommands); + GameMain.Server.UpdateClientPermissions(client); + NewMessage("Assigned the rank \"" + preset.Name + "\" 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; + } + + NewMessage("Valid ranks are:", Color.White); + foreach (PermissionPreset permissionPreset in PermissionPreset.List) + { + NewMessage(" - " + permissionPreset.Name, Color.White); + } + ShowQuestionPrompt("Rank to grant to client #" + id + "?", (rank) => + { + GameMain.Client.SendConsoleCommand("giverank " + id + " " + rank); + }); +#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.SendConsoleMessage("Client id \"" + id + "\" not found.", senderClient); + return; + } + + string rank = string.Join("", args.Skip(1)); + PermissionPreset preset = PermissionPreset.List.Find(p => p.Name.ToLowerInvariant() == rank.ToLowerInvariant()); + if (preset == null) + { + GameMain.Server.SendConsoleMessage("Rank \"" + rank + "\" not found.", senderClient); + return; + } + + client.SetPermissions(preset.Permissions, preset.PermittedCommands); + GameMain.Server.UpdateClientPermissions(client); + GameMain.Server.SendConsoleMessage("Assigned the rank \"" + preset.Name + "\" to " + client.Name + ".", senderClient); + NewMessage(senderClient.Name + " granted the rank \"" + preset.Name + "\" to " + client.Name + ".", Color.White); + })); + + commands.Add(new Command("givecommandperm", "givecommandperm [id]: Gives the player with the specified client ID the permission to use the specified console commands.", (string[] args) => + { + if (GameMain.Server == null) return; + if (args.Length < 1) + { + NewMessage("givecommandperm [id]: Gives the player with the specified client ID the permission to use the specified console commands.", Color.Cyan); + return; + } + + int id; + int.TryParse(args[0], out id); + var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); + if (client == null) + { + ThrowError("Client id \"" + id + "\" not found."); + return; + } + + ShowQuestionPrompt("Console command permissions to grant to \"" + client.Name + "\"? You may enter multiple commands separated with a space.", (commandsStr) => + { + string[] splitCommands = commandsStr.Split(' '); + List grantedCommands = new List(); + for (int i = 0; i < splitCommands.Length; i++) + { + splitCommands[i] = splitCommands[i].Trim().ToLowerInvariant(); + Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i])); + if (matchingCommand == null) + { + ThrowError("Could not find the command \"" + splitCommands[i] + "\"!"); + } + else + { + grantedCommands.Add(matchingCommand); + } + } + + client.GivePermission(ClientPermissions.ConsoleCommands); + client.SetPermissions(client.Permissions, client.PermittedConsoleCommands.Union(grantedCommands).Distinct().ToList()); + GameMain.Server.UpdateClientPermissions(client); + NewMessage("Gave the client \"" + client.Name + "\" the permission to use console commands " + string.Join(", ", grantedCommands.Select(c => c.names[0])) + ".", 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("Console command permissions to grant to client #" + id + "? You may enter multiple commands separated with a space.", (commandNames) => + { + GameMain.Client.SendConsoleCommand("givecommandperm " + id + " " + commandNames); + }); +#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.SendConsoleMessage("Client id \"" + id + "\" not found.", senderClient); + return; + } + + string[] splitCommands = args.Skip(1).ToArray(); + List grantedCommands = new List(); + for (int i = 0; i < splitCommands.Length; i++) + { + splitCommands[i] = splitCommands[i].Trim().ToLowerInvariant(); + Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i])); + if (matchingCommand == null) + { + GameMain.Server.SendConsoleMessage("Could not find the command \"" + splitCommands[i] + "\"!", senderClient); + } + else + { + grantedCommands.Add(matchingCommand); + } + } + + client.GivePermission(ClientPermissions.ConsoleCommands); + client.SetPermissions(client.Permissions, client.PermittedConsoleCommands.Union(grantedCommands).Distinct().ToList()); + GameMain.Server.UpdateClientPermissions(client); + GameMain.Server.SendConsoleMessage("Gave the client \"" + client.Name + "\" the permission to use the console commands " + string.Join(", ", grantedCommands.Select(c => c.names[0])) + ".", senderClient); + NewMessage("Gave the client \"" + client.Name + "\" the permission to use the console commands " + string.Join(", ", grantedCommands.Select(c => c.names[0])) + ".", Color.White); + })); + + + commands.Add(new Command("revokecommandperm", "revokecommandperm [id]: Revokes permission to use the specified console commands from the player with the specified client ID.", (string[] args) => + { + if (GameMain.Server == null) return; + if (args.Length < 1) + { + NewMessage("revokecommandperm [id]: Revokes permission to use the specified console commands from the player with the specified client ID.", Color.Cyan); + return; + } + + int id; + int.TryParse(args[0], out id); + var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); + if (client == null) + { + ThrowError("Client id \"" + id + "\" not found."); + return; + } + + ShowQuestionPrompt("Console command permissions to revoke from \"" + client.Name + "\"? You may enter multiple commands separated with a space.", (commandsStr) => + { + string[] splitCommands = commandsStr.Split(' '); + List revokedCommands = new List(); + for (int i = 0; i < splitCommands.Length; i++) + { + splitCommands[i] = splitCommands[i].Trim().ToLowerInvariant(); + Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i])); + if (matchingCommand == null) + { + ThrowError("Could not find the command \"" + splitCommands[i] + "\"!"); + } + else + { + revokedCommands.Add(matchingCommand); + } + } + + client.SetPermissions(client.Permissions, client.PermittedConsoleCommands.Except(revokedCommands).ToList()); + GameMain.Server.UpdateClientPermissions(client); + NewMessage("Revoked \"" + client.Name + "\"'s permission to use the console commands " + string.Join(", ", revokedCommands.Select(c => c.names[0])) + ".", 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("Console command permissions to grant to client #" + id + "? You may enter multiple commands separated with a space.", (commandNames) => + { + GameMain.Client.SendConsoleCommand("givecommandperm " + id + " " + commandNames); + }); +#endif + }, + (Client senderClient, Vector2 cursorWorldPos, string[] args) => + { + if (args.Length < 2) return; + + int.TryParse(args[0], out int id); + var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); + if (client == null) + { + GameMain.Server.SendConsoleMessage("Client id \"" + id + "\" not found.", senderClient); + return; + } + + string[] splitCommands = args.Skip(1).ToArray(); + List revokedCommands = new List(); + for (int i = 0; i < splitCommands.Length; i++) + { + splitCommands[i] = splitCommands[i].Trim().ToLowerInvariant(); + Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i])); + if (matchingCommand == null) + { + GameMain.Server.SendConsoleMessage("Could not find the command \"" + splitCommands[i] + "\"!", senderClient); + } + else + { + revokedCommands.Add(matchingCommand); + } + } + + client.GivePermission(ClientPermissions.ConsoleCommands); + client.SetPermissions(client.Permissions, client.PermittedConsoleCommands.Except(revokedCommands).ToList()); + GameMain.Server.UpdateClientPermissions(client); + GameMain.Server.SendConsoleMessage("Revoked \"" + client.Name + "\"'s permission to use the console commands " + string.Join(", ", revokedCommands.Select(c => c.names[0])) + ".", senderClient); + NewMessage(senderClient.Name + " revoked \"" + client.Name + "\"'s permission to use the console commands " + string.Join(", ", revokedCommands.Select(c => c.names[0])) + ".", Color.White); + })); + + + commands.Add(new Command("showperm", "showperm [id]: Shows the current administrative permissions of the client with the specified client ID.", (string[] args) => + { + if (GameMain.Server == null) return; + if (args.Length < 1) + { + NewMessage("showperm [id]: Shows the current administrative permissions of the client with the specified client ID.", Color.Cyan); + return; + } + + int.TryParse(args[0], out int id); + var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); + if (client == null) + { + ThrowError("Client id \"" + id + "\" not found."); + return; + } + + if (client.Permissions == ClientPermissions.None) + { + NewMessage(client.Name + " has no special permissions.", Color.White); + return; + } + + NewMessage(client.Name + " has the following permissions:", Color.White); + foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) + { + if (permission == ClientPermissions.None || !client.HasPermission(permission)) continue; + System.Reflection.FieldInfo fi = typeof(ClientPermissions).GetField(permission.ToString()); + DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); + NewMessage(" - " + attributes[0].Description, Color.White); + } + if (client.HasPermission(ClientPermissions.ConsoleCommands)) + { + if (client.PermittedConsoleCommands.Count == 0) + { + NewMessage("No permitted console commands:", Color.White); + } + else + { + NewMessage("Permitted console commands:", Color.White); + foreach (Command permittedCommand in client.PermittedConsoleCommands) + { + NewMessage(" - " + permittedCommand.names[0], 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; + } + + GameMain.Client.SendConsoleCommand("showperm " + id); +#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.SendConsoleMessage("Client id \"" + id + "\" not found.", senderClient); + return; + } + + if (client.Permissions == ClientPermissions.None) + { + GameMain.Server.SendConsoleMessage(client.Name + " has no special permissions.", senderClient); + return; + } + + GameMain.Server.SendConsoleMessage(client.Name + " has the following permissions:", senderClient); + foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) + { + if (permission == ClientPermissions.None || !client.HasPermission(permission)) continue; + System.Reflection.FieldInfo fi = typeof(ClientPermissions).GetField(permission.ToString()); + DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); + GameMain.Server.SendConsoleMessage(" - " + attributes[0].Description, senderClient); + } + if (client.HasPermission(ClientPermissions.ConsoleCommands)) + { + if (client.PermittedConsoleCommands.Count == 0) + { + GameMain.Server.SendConsoleMessage("No permitted console commands:", senderClient); + } + else + { + GameMain.Server.SendConsoleMessage("Permitted console commands:", senderClient); + foreach (Command permittedCommand in client.PermittedConsoleCommands) + { + GameMain.Server.SendConsoleMessage(" - " + permittedCommand.names[0], senderClient); + } + } + } + })); + + commands.Add(new Command("togglekarma", "togglekarma: Toggles the karma system.", (string[] args) => + { + throw new NotImplementedException(); + 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; + + string playerName = string.Join(" ", args); + + ShowQuestionPrompt("Reason for kicking \"" + playerName + "\"?", (reason) => + { + GameMain.NetworkMember.KickPlayer(playerName, reason); + }); + }, + () => + { + if (GameMain.NetworkMember == null) return null; + + return new string[][] + { + GameMain.NetworkMember.ConnectedClients.Select(c => c.Name).ToArray() + }; + })); + + commands.Add(new Command("kickid", "kickid [id]: Kick the player with the specified client ID out of the server.", (string[] args) => + { + if (GameMain.NetworkMember == null || args.Length == 0) return; + + 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 kicking \"" + client.Name + "\"?", (reason) => + { + GameMain.NetworkMember.KickPlayer(client.Name, reason); + }); + })); + + commands.Add(new Command("ban", "ban [name]: Kick and ban the player from the server.", (string[] args) => + { + if (GameMain.NetworkMember == null || args.Length == 0) return; + + string clientName = string.Join(" ", args); + ShowQuestionPrompt("Reason for banning \"" + clientName + "\"?", (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)) + { + TimeSpan parsedBanDuration; + if (!TryParseTimeSpan(duration, out 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.NetworkMember.BanPlayer(clientName, reason, false, banDuration); + }); + }); + }, + () => + { + if (GameMain.NetworkMember == null) return null; + + return new string[][] + { + GameMain.NetworkMember.ConnectedClients.Select(c => c.Name).ToArray() + }; + })); + + commands.Add(new Command("banid", "banid [id]: Kick and ban the player with the specified client ID from the server.", (string[] args) => + { + if (GameMain.NetworkMember == null || args.Length == 0) return; + + 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) => + { + 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.NetworkMember.BanPlayer(client.Name, reason, false, banDuration); + }); + }); + })); + + + commands.Add(new Command("banip", "banip [ip]: Ban the IP address from the server.", (string[] args) => + { + if (GameMain.Server == 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; + } + + var clients = GameMain.Server.ConnectedClients.FindAll(c => c.Connection.RemoteEndPoint.Address.ToString() == args[0]); + if (clients.Count == 0) + { + GameMain.Server.BanList.BanPlayer("Unnamed", args[0], reason, banDuration); + } + else + { + foreach (Client cl in clients) + { + GameMain.Server.BanClient(cl, reason, false, banDuration); + } + } + }); + }); + }, + (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 = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args, false); + if (tpCharacter == null) return; + + var cam = GameMain.GameScreen.Cam; + tpCharacter.AnimController.CurrentHull = null; + 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 = (args.Length == 0) ? client.Character : 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); + }, + () => + { + return new string[][] + { + Character.CharacterList.Select(c => c.Name).Distinct().ToArray() + }; + })); + + commands.Add(new Command("godmode", "godmode: Toggle submarine godmode. Makes the main submarine invulnerable to damage.", (string[] args) => + { + if (Submarine.MainSub == null) return; + + 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.SendConsoleMessage(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", "locky: Lock vertical movement of the main submarine.", (string[] args) => + { + Submarine.LockY = !Submarine.LockY; + }, null, null)); + + commands.Add(new Command("dumpids", "", (string[] args) => + { + try + { + int count = args.Length == 0 ? 10 : int.Parse(args[0]); + Entity.DumpIds(count); + } + catch (Exception e) + { + ThrowError("Failed to dump ids", e); + } + })); + + 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 = (args.Length == 0) ? Character.Controlled : 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 = (args.Length == 0) ? client.Character : 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); + } + }, + () => + { + return new string[][] + { + Character.CharacterList.Select(c => c.Name).Distinct().ToArray() + }; + })); + + 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 = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args); + if (revivedCharacter == null) return; + + revivedCharacter.Revive(); + 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; + } + } + }, + null, + (Client client, Vector2 cursorWorldPos, string[] args) => + { + Character revivedCharacter = (args.Length == 0) ? client.Character : FindMatchingCharacter(args); + if (revivedCharacter == null) return; + + revivedCharacter.Revive(); + 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; + } + } + }, + () => + { + return new string[][] + { + Character.CharacterList.Select(c => c.Name).Distinct().ToArray() + }; + })); + + 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("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 = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args); + if (ragdolledCharacter != null) + { + ragdolledCharacter.IsForceRagdolled = !ragdolledCharacter.IsForceRagdolled; + } + }, + null, + (Client client, Vector2 cursorWorldPos, string[] args) => + { + Character ragdolledCharacter = (args.Length == 0) ? client.Character : FindMatchingCharacter(args); + if (ragdolledCharacter != null) + { + ragdolledCharacter.IsForceRagdolled = !ragdolledCharacter.IsForceRagdolled; + } + }, + () => + { + return new string[][] + { + Character.CharacterList.Select(c => c.Name).Distinct().ToArray() + }; + })); + + commands.Add(new Command("freecamera|freecam", "freecam: Detach the camera from the controlled character.", (string[] args) => + { + Character.Controlled = null; + GameMain.GameScreen.Cam.TargetPos = Vector2.Zero; + })); + + commands.Add(new Command("water|editwater", "water/editwater: Toggle water editing. Allows adding water into rooms by holding the left mouse button and removing it by holding the right mouse button.", (string[] args) => + { + if (GameMain.Client == null) + { + Hull.EditWater = !Hull.EditWater; + NewMessage(Hull.EditWater ? "Water editing on" : "Water editing off", Color.White); + } + })); + + commands.Add(new Command("fire|editfire", "fire/editfire: Allows putting up fires by left clicking.", (string[] args) => + { + if (GameMain.Client == null) + { + Hull.EditFire = !Hull.EditFire; + NewMessage(Hull.EditFire ? "Fire spawning on" : "Fire spawning off", Color.White); + } + })); + + commands.Add(new Command("explosion", "explosion [range] [force] [damage] [structuredamage] [emp strength]: Creates an explosion at the position of the cursor.", (string[] args) => + { + Vector2 explosionPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition); + float range = 500, force = 10, damage = 50, structureDamage = 10, empStrength = 0.0f; + 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); + if (args.Length > 4) float.TryParse(args[4], out empStrength); + new Explosion(range, force, damage, structureDamage, empStrength).Explode(explosionPos); + }, + null, + (Client client, Vector2 cursorWorldPos, string[] args) => + { + Vector2 explosionPos = cursorWorldPos; + float range = 500, force = 10, damage = 50, structureDamage = 10, empStrength = 0.0f; ; + 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); + if (args.Length > 4) float.TryParse(args[4], out empStrength); + new Explosion(range, force, damage, structureDamage, empStrength).Explode(explosionPos); + })); + + commands.Add(new Command("fixitems", "fixitems: Repairs all items and restores them to full condition.", (string[] args) => + { + foreach (Item it in Item.ItemList) + { + it.Condition = it.Prefab.Health; + } + }, null, null)); + + commands.Add(new Command("fixhulls|fixwalls", "fixwalls/fixhulls: Fixes all walls.", (string[] args) => + { + foreach (Structure w in Structure.WallList) + { + for (int i = 0; i < w.SectionCount; i++) + { + 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) => + { + Item reactorItem = Item.ItemList.Find(i => i.GetComponent() != null); + if (reactorItem == null) return; + + float power = 5000.0f; + if (args.Length > 0) float.TryParse(args[0], out power); + + var reactor = reactorItem.GetComponent(); + reactor.ShutDownTemp = power == 0 ? 0 : 7000.0f; + reactor.AutoTemp = true; + reactor.Temperature = power; + + if (GameMain.Server != null) + { + reactorItem.CreateServerEvent(reactor); + } + }, null, null)); + + commands.Add(new Command("oxygen|air", "oxygen/air: Replenishes the oxygen levels in every room to 100%.", (string[] args) => + { + foreach (Hull hull in Hull.hullList) + { + hull.OxygenPercentage = 100.0f; + } + }, null, null)); + + commands.Add(new Command("kill", "kill [character]: Immediately kills the specified character.", (string[] args) => + { + 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); + }, + () => + { + return new string[][] + { + Character.CharacterList.Select(c => c.Name).Distinct().ToArray() + }; + })); + + commands.Add(new Command("killmonsters", "killmonsters: Immediately kills all AI-controlled enemies in the level.", (string[] args) => + { + foreach (Character c in Character.CharacterList) + { + if (!(c.AIController is EnemyAIController)) continue; + c.AddDamage(CauseOfDeath.Damage, c.MaxHealth * 2, null); + } + }, null, null)); + + commands.Add(new Command("netstats", "netstats: Toggles the visibility of the network statistics UI.", (string[] args) => + { + if (GameMain.Server == null) return; + GameMain.Server.ShowNetStats = !GameMain.Server.ShowNetStats; + })); + + commands.Add(new Command("setclientcharacter", "setclientcharacter [client name] ; [character name]: Gives the client control of the specified character.", (string[] args) => + { + 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]\""); + 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) + { + 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.SendConsoleMessage("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.SendConsoleMessage("Client \"" + clientName + "\" not found.", senderClient); + } + + var character = FindMatchingCharacter(argsRight, false); + GameMain.Server.SetClientCharacter(client, character); + }, + () => + { + if (GameMain.NetworkMember == null) return null; + + return new string[][] + { + GameMain.NetworkMember.ConnectedClients.Select(c => c.Name).ToArray(), + Character.CharacterList.Select(c => c.Name).Distinct().ToArray() + }; + })); + + commands.Add(new Command("campaigninfo|campaignstatus", "campaigninfo: Display information about the state of the currently active campaign.", (string[] args) => + { + var campaign = GameMain.GameSession?.GameMode as CampaignMode; + if (campaign == null) + { + ThrowError("No campaign active!"); + return; + } + + campaign.LogState(); + })); + + commands.Add(new Command("campaigndestination|setcampaigndestination", "campaigndestination [index]: Set the location to head towards in the currently active campaign.", (string[] args) => + { + 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; + } + Location location = campaign.Map.CurrentLocation.Connections[destinationIndex].OtherLocation(campaign.Map.CurrentLocation); + campaign.Map.SelectLocation(location); + NewMessage(location.Name+" selected.", Color.White); + }); + } + 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; + } + Location location = campaign.Map.CurrentLocation.Connections[destinationIndex].OtherLocation(campaign.Map.CurrentLocation); + 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.SendConsoleMessage("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.SendConsoleMessage("Index out of bounds!", senderClient); + return; + } + Location location = campaign.Map.CurrentLocation.Connections[destinationIndex].OtherLocation(campaign.Map.CurrentLocation); + campaign.Map.SelectLocation(location); + GameMain.Server.SendConsoleMessage(location.Name + " selected.", senderClient); + })); + +#if DEBUG + commands.Add(new Command("spamevents", "A debug command that immediately creates entity events for all items, characters and structures.", (string[] args) => + { + foreach (Item item in Item.ItemList) + { + for (int i = 0; i < item.components.Count; i++) + { + if (item.components[i] is IServerSerializable) + { + GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ComponentState, i }); + } + var itemContainer = item.GetComponent(); + if (itemContainer != null) + { + GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.InventoryState, 0 }); + } + + GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.Status }); + + item.NeedsPositionUpdate = true; + } + } + + foreach (Character c in Character.CharacterList) + { + GameMain.Server.CreateEntityEvent(c, new object[] { NetEntityEvent.Type.Status }); + } + + foreach (Structure wall in Structure.WallList) + { + GameMain.Server.CreateEntityEvent(wall); + } + }, null, null)); + + commands.Add(new Command("flipx", "flipx: mirror the main submarine horizontally", (string[] args) => + { + Submarine.MainSub?.FlipX(); + })); +#endif + InitProjectSpecific(); + + commands.Sort((c1, c2) => c1.names[0].CompareTo(c2.names[0])); + } + + private static string[] SplitCommand(string command) + { + command = command.Trim(); + + List commands = new List(); + int escape = 0; + bool inQuotes = false; + string piece = ""; + + for (int i = 0; i < command.Length; i++) + { + if (command[i] == '\\') + { + if (escape == 0) escape = 2; + else piece += '\\'; + } + else if (command[i] == '"') + { + if (escape == 0) inQuotes = !inQuotes; + else piece += '"'; + } + else if (command[i] == ' ' && !inQuotes) + { + if (!string.IsNullOrWhiteSpace(piece)) commands.Add(piece); + piece = ""; + } + else if (escape == 0) piece += command[i]; + + if (escape > 0) escape--; + } + + if (!string.IsNullOrWhiteSpace(piece)) commands.Add(piece); //add final piece + + return commands.ToArray(); + } + + public static string AutoComplete(string command) + { + string[] splitCommand = SplitCommand(command); + string[] args = splitCommand.Skip(1).ToArray(); + + //if an argument is given or the last character is a space, attempt to autocomplete the argument + if (args.Length > 0 || (command.Length > 0 && command.Last() == ' ')) + { + Command matchingCommand = commands.Find(c => c.names.Contains(splitCommand[0])); + if (matchingCommand == null || matchingCommand.GetValidArgs == null) return command; + + int autoCompletedArgIndex = args.Length > 0 && command.Last() != ' ' ? args.Length - 1 : args.Length; + + //get all valid arguments for the given command + string[][] allArgs = matchingCommand.GetValidArgs(); + if (allArgs == null || allArgs.GetLength(0) < autoCompletedArgIndex + 1) return command; + + if (string.IsNullOrEmpty(currentAutoCompletedCommand)) + { + currentAutoCompletedCommand = autoCompletedArgIndex > args.Length - 1 ? " " : args.Last(); + } + + //find all valid autocompletions for the given argument + string[] validArgs = allArgs[autoCompletedArgIndex].Where(arg => + currentAutoCompletedCommand.Trim().Length <= arg.Length && + arg.Substring(0, currentAutoCompletedCommand.Trim().Length).ToLower() == currentAutoCompletedCommand.Trim().ToLower()).ToArray(); + + if (validArgs.Length == 0) return command; + + currentAutoCompletedIndex = currentAutoCompletedIndex % validArgs.Length; + string autoCompletedArg = validArgs[currentAutoCompletedIndex++]; + + //add quotation marks to args that contain spaces + if (autoCompletedArg.Contains(' ')) autoCompletedArg = '"' + autoCompletedArg + '"'; + for (int i = 0; i < splitCommand.Length; i++) + { + if (splitCommand[i].Contains(' ')) splitCommand[i] = '"' + splitCommand[i] + '"'; + } + + return string.Join(" ", autoCompletedArgIndex >= args.Length ? splitCommand : splitCommand.Take(splitCommand.Length - 1)) + " " + autoCompletedArg; + } + else + { + if (string.IsNullOrWhiteSpace(currentAutoCompletedCommand)) + { + currentAutoCompletedCommand = command; + } + + List matchingCommands = new List(); + foreach (Command c in commands) + { + foreach (string name in c.names) + { + if (currentAutoCompletedCommand.Length > name.Length) continue; + if (currentAutoCompletedCommand == name.Substring(0, currentAutoCompletedCommand.Length)) + { + matchingCommands.Add(name); + } + } + } + + if (matchingCommands.Count == 0) return command; + + currentAutoCompletedIndex = currentAutoCompletedIndex % matchingCommands.Count; + return matchingCommands[currentAutoCompletedIndex++]; + } + } + + private static string AutoCompleteStr(string str, IEnumerable validStrings) + { + if (string.IsNullOrEmpty(str)) return str; + foreach (string validStr in validStrings) + { + if (validStr.Length > str.Length && validStr.Substring(0, str.Length) == str) return validStr; + } + return str; + } + + public static void ResetAutoComplete() + { + currentAutoCompletedCommand = ""; + currentAutoCompletedIndex = 0; + } + + public static string SelectMessage(int direction) + { + if (Messages.Count == 0) return ""; + + direction = MathHelper.Clamp(direction, -1, 1); + + int i = 0; + do + { + selectedIndex += direction; + if (selectedIndex < 0) selectedIndex = Messages.Count - 1; + selectedIndex = selectedIndex % Messages.Count; + if (++i >= Messages.Count) break; + } while (!Messages[selectedIndex].IsCommand); + + return Messages[selectedIndex].Text; + } + + public static void ExecuteCommand(string command) + { + if (activeQuestionCallback != null) + { +#if CLIENT + activeQuestionText = null; +#endif + NewMessage(command, Color.White, true); + //reset the variable before invoking the delegate because the method may need to activate another question + var temp = activeQuestionCallback; + activeQuestionCallback = null; + temp(command); + return; + } + + if (string.IsNullOrWhiteSpace(command) || command == "\\" || command == "\n") return; + + string[] splitCommand = SplitCommand(command); + if (splitCommand.Length == 0) + { + ThrowError("Failed to execute command \"" + command + "\"!"); + GameAnalyticsManager.AddErrorEventOnce( + "DebugConsole.ExecuteCommand:LengthZero", + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Failed to execute command \"" + command + "\"!"); + return; + } + + if (!splitCommand[0].ToLowerInvariant().Equals("admin")) + { + NewMessage(command, Color.White, true); + } + +#if CLIENT + if (GameMain.Client != null) + { + if (GameMain.Client.HasConsoleCommandPermission(splitCommand[0].ToLowerInvariant())) + { + 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 !DEBUG + if (!IsCommandPermitted(splitCommand[0].ToLowerInvariant(), GameMain.Client)) + { + ThrowError("You're not permitted to use the command \"" + splitCommand[0].ToLowerInvariant() + "\"!"); + return; + } +#endif + } +#endif + + bool commandFound = false; + foreach (Command c in commands) + { + if (c.names.Contains(splitCommand[0].ToLowerInvariant())) + { + c.Execute(splitCommand.Skip(1).ToArray()); + commandFound = true; + break; + } + } + + if (!commandFound) + { + 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.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; + } + + string[] splitCommand = SplitCommand(command); + Command matchingCommand = commands.Find(c => c.names.Contains(splitCommand[0].ToLowerInvariant())); + if (matchingCommand != null && !client.PermittedConsoleCommands.Contains(matchingCommand)) + { + 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.SendConsoleMessage("Command \"" + splitCommand[0] + "\" not found.", client); + return; + } + + if (!MathUtils.IsValid(cursorWorldPos)) + { + GameMain.Server.SendConsoleMessage("Could not execute command \"" + command + "\" - invalid cursor position.", client); + NewMessage(client.Name + " attempted to execute the console command \"" + command + "\" with invalid cursor position.", Color.White); + 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); + } + } + + + private static Character FindMatchingCharacter(string[] args, bool ignoreRemotePlayers = false) + { + if (args.Length == 0) return null; + + int characterIndex; + string characterName; + if (int.TryParse(args.Last(), out characterIndex) && args.Length > 1) + { + characterName = string.Join(" ", args.Take(args.Length - 1)).ToLowerInvariant(); + } + else + { + characterName = string.Join(" ", args).ToLowerInvariant(); + characterIndex = -1; + } + + var matchingCharacters = Character.CharacterList.FindAll(c => (!ignoreRemotePlayers || !c.IsRemotePlayer) && c.Name.ToLowerInvariant() == characterName); + + if (!matchingCharacters.Any()) + { + NewMessage("Character \""+ characterName + "\" not found", Color.Red); + return null; + } + + if (characterIndex == -1) + { + if (matchingCharacters.Count > 1) + { + NewMessage( + "Found multiple matching characters. " + + "Use \"[charactername] [0-" + (matchingCharacters.Count - 1) + "]\" to choose a specific character.", + Color.LightGray); + } + return matchingCharacters[0]; + } + else if (characterIndex < 0 || characterIndex >= matchingCharacters.Count) + { + ThrowError("Character index out of range. Select an index between 0 and " + (matchingCharacters.Count - 1)); + } + else + { + return matchingCharacters[characterIndex]; + } + + 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, Character controlledCharacter, out string errorMsg) + { + errorMsg = ""; + if (args.Length < 1) return; + + Vector2? spawnPos = null; + Inventory spawnInventory = null; + + int extraParams = 0; + switch (args.Last().ToLowerInvariant()) + { + case "cursor": + extraParams = 1; + spawnPos = cursorPos; + break; + case "inventory": + extraParams = 1; + spawnInventory = controlledCharacter == null ? null : controlledCharacter.Inventory; + break; + case "cargo": + var wp = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub); + spawnPos = wp == null ? Vector2.Zero : wp.WorldPosition; + break; + //Dont do a thing, random is basically Human points anyways - its in the help description. + case "random": + extraParams = 1; + return; + default: + extraParams = 0; + break; + } + + string itemName = string.Join(" ", args.Take(args.Length - extraParams)).ToLowerInvariant(); + + ItemPrefab itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab; + if (itemPrefab == null && extraParams == 0) + { + if (GameMain.Server != null) + { + var client = GameMain.Server.ConnectedClients.Find(c => c.Name.ToLower() == args.Last().ToLower()); + if (client != null) + { + extraParams += 1; + itemName = string.Join(" ", args.Take(args.Length - extraParams)).ToLowerInvariant(); + if (client.Character != null && client.Character.Name == args.Last().ToLower()) spawnInventory = client.Character.Inventory; + itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab; + } + } + } + //Check again if the item can be found again after having checked for a character + if (itemPrefab == null) + { + errorMsg = "Item \"" + itemName + "\" not found!"; + return; + } + + if ((spawnPos == null || spawnPos == Vector2.Zero) && 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, bool isCommand = false) + { + if (string.IsNullOrEmpty((msg))) return; + +#if SERVER + var newMsg = new ColoredText(msg, color, isCommand); + Messages.Add(newMsg); + + //TODO: REMOVE + Console.ForegroundColor = XnaToConsoleColor.Convert(color); + Console.WriteLine(msg); + Console.ForegroundColor = ConsoleColor.White; + + if (GameSettings.SaveDebugConsoleLogs) + { + unsavedMessages.Add(newMsg); + if (unsavedMessages.Count >= messagesPerFile) + { + SaveLogs(); + unsavedMessages.Clear(); + } + } + + if (Messages.Count > MaxMessages) + { + Messages.RemoveRange(0, Messages.Count - MaxMessages); + } +#elif CLIENT + lock (queuedMessages) + { + queuedMessages.Enqueue(new ColoredText(msg, color, isCommand)); + } +#endif + } + + public static void ShowQuestionPrompt(string question, QuestionCallback onAnswered) + { + +#if CLIENT + 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) + { + timeSpan = new TimeSpan(); + if (string.IsNullOrWhiteSpace(s)) return false; + + string currNum = ""; + foreach (char c in s) + { + if (char.IsDigit(c)) + { + currNum += c; + } + else if (char.IsWhiteSpace(c)) + { + continue; + } + else + { + int parsedNum = 0; + if (!int.TryParse(currNum, out parsedNum)) + { + return false; + } + + switch (c) + { + case 'd': + timeSpan += new TimeSpan(parsedNum, 0, 0, 0, 0); + break; + case 'h': + timeSpan += new TimeSpan(0, parsedNum, 0, 0, 0); + break; + case 'm': + timeSpan += new TimeSpan(0, 0, parsedNum, 0, 0); + break; + case 's': + timeSpan += new TimeSpan(0, 0, 0, parsedNum, 0); + break; + default: + return false; + } + + currNum = ""; + } + } + + 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) + { + if (GameSettings.VerboseLogging) NewMessage(message, Color.Gray); + } + + public static void ThrowError(string error, Exception e = null) + { + if (e != null) + { + error += " {" + e.Message + "}\n" + e.StackTrace; + } + System.Diagnostics.Debug.WriteLine(error); + NewMessage(error, Color.Red); +#if CLIENT + isOpen = true; +#endif + } + + + public static void SaveLogs() + { + if (unsavedMessages.Count == 0) return; + if (!Directory.Exists(SavePath)) + { + try + { + Directory.CreateDirectory(SavePath); + } + catch (Exception e) + { + ThrowError("Failed to create a folder for debug console logs", e); + return; + } + } + + string fileName = "DebugConsoleLog_" + DateTime.Now.ToShortDateString() + "_" + DateTime.Now.ToShortTimeString() + ".txt"; + var invalidChars = Path.GetInvalidFileNameChars(); + foreach (char invalidChar in invalidChars) + { + fileName = fileName.Replace(invalidChar.ToString(), ""); + } + + string filePath = Path.Combine(SavePath, fileName); + if (File.Exists(filePath)) + { + int fileNum = 2; + while (File.Exists(filePath + " (" + fileNum + ")")) + { + fileNum++; + } + filePath = filePath + " (" + fileNum + ")"; + } + + try + { + File.WriteAllLines(filePath, unsavedMessages.Select(l => "[" + l.Time + "] " + l.Text)); + } + catch (Exception e) + { + unsavedMessages.Clear(); + ThrowError("Saving debug console log to " + filePath + " failed", e); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs b/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs index 85fccbbde..51c9d475e 100644 --- a/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs +++ b/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs @@ -1,207 +1,207 @@ -using Barotrauma.Items.Components; -using Microsoft.Xna.Framework; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Barotrauma -{ - class PurchasedItem - { - public ItemPrefab itemPrefab; - public int quantity; - - public PurchasedItem(ItemPrefab itemPrefab, int quantity) - { - this.itemPrefab = itemPrefab; - this.quantity = quantity; - } - } - - class CargoManager - { - private readonly List purchasedItems; - - private readonly CampaignMode campaign; - - public Action OnItemsChanged; - - public List PurchasedItems - { - get { return purchasedItems; } - } - - public CargoManager(CampaignMode campaign) - { - purchasedItems = new List(); - this.campaign = campaign; - } - - public void SetPurchasedItems(List items) - { - purchasedItems.Clear(); - purchasedItems.AddRange(items); - - OnItemsChanged?.Invoke(); - } - - public void PurchaseItem(ItemPrefab item, int Quantity = 1) - { - PurchasedItem purchasedItem = PurchasedItems.Find(pi => pi.itemPrefab == item); - - if(purchasedItem != null && Quantity == 1) - { - campaign.Money -= item.Price; - purchasedItem.quantity += 1; - } - else - { - campaign.Money -= (item.Price * Quantity); - purchasedItem = new PurchasedItem(item, Quantity); - purchasedItems.Add(purchasedItem); - } - - OnItemsChanged?.Invoke(); - } - - public void SellItem(ItemPrefab item, int quantity = 1) - { - campaign.Money += (item.Price * quantity); - PurchasedItem purchasedItem = PurchasedItems.Find(pi => pi.itemPrefab == item); - if (purchasedItem != null && purchasedItem.quantity - quantity > 0) - { - purchasedItem.quantity -= quantity; - } - else - { - PurchasedItems.Remove(purchasedItem); - } - - OnItemsChanged?.Invoke(); - } - - public int GetTotalItemCost() - { - return purchasedItems.Sum(i => (i.itemPrefab.Price * i.quantity)); - } - - public void CreateItems() - { - CreateItems(purchasedItems); - OnItemsChanged?.Invoke(); - } - - public static void CreateItems(List itemsToSpawn) - { - WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub); - - if (wp == null) - { - DebugConsole.ThrowError("The submarine must have a waypoint marked as Cargo for bought items to be placed correctly!"); - return; - } - - Hull cargoRoom = Hull.FindHull(wp.WorldPosition); - - if (cargoRoom == null) - { - DebugConsole.ThrowError("A waypoint marked as Cargo must be placed inside a room!"); - return; - } - - Dictionary availableContainers = new Dictionary(); - ItemPrefab containerPrefab = null; - foreach (PurchasedItem pi in itemsToSpawn) - { - Vector2 position = new Vector2( - Rand.Range(cargoRoom.Rect.X + 20, cargoRoom.Rect.Right - 20), - cargoRoom.Rect.Y - cargoRoom.Rect.Height + pi.itemPrefab.Size.Y / 2); - - ItemContainer itemContainer = null; - if (!string.IsNullOrEmpty(pi.itemPrefab.CargoContainerName)) - { - itemContainer = availableContainers.Keys.ToList().Find(ac => - ac.Item.Prefab.NameMatches(pi.itemPrefab.CargoContainerName) || - ac.Item.Prefab.Tags.Contains(pi.itemPrefab.CargoContainerName.ToLowerInvariant())); - - if (itemContainer == null) - { - containerPrefab = MapEntityPrefab.List.Find(ep => - ep.NameMatches(pi.itemPrefab.CargoContainerName) || - (ep.Tags != null && ep.Tags.Contains(pi.itemPrefab.CargoContainerName.ToLowerInvariant()))) as ItemPrefab; - - if (containerPrefab == null) - { - DebugConsole.ThrowError("Cargo spawning failed - could not find the item prefab for container \"" + containerPrefab.Name + "\"!"); - continue; - } - - Item containerItem = new Item(containerPrefab, position, wp.Submarine); - itemContainer = containerItem.GetComponent(); - if (itemContainer == null) - { - DebugConsole.ThrowError("Cargo spawning failed - container \"" + containerItem.Name + "\" does not have an ItemContainer component!"); - continue; - } - availableContainers.Add(itemContainer, itemContainer.Capacity); - if (GameMain.Server != null) - { - Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false); - } - } - } - for (int i = 0; i < pi.quantity; i++) - { - if (itemContainer == null) - { - //no container, place at the waypoint - if (GameMain.Server != null) - { - Entity.Spawner.AddToSpawnQueue(pi.itemPrefab, position, wp.Submarine); - } - else - { - new Item(pi.itemPrefab, position, wp.Submarine); - } - continue; - } - //if the intial container has been removed due to it running out of space, add a new container - //of the same type and begin filling it - if (!availableContainers.ContainsKey(itemContainer)) - { - Item containerItemOverFlow = new Item(containerPrefab, position, wp.Submarine); - itemContainer = containerItemOverFlow.GetComponent(); - availableContainers.Add(itemContainer, itemContainer.Capacity); - if (GameMain.Server != null) - { - Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false); - } - } - - //place in the container - if (GameMain.Server != null) - { - Entity.Spawner.AddToSpawnQueue(pi.itemPrefab, itemContainer.Inventory); - } - else - { - var item = new Item(pi.itemPrefab, position, wp.Submarine); - itemContainer.Inventory.TryPutItem(item, null); - } - - //reduce the number of available slots in the container - //if there is a container - if (availableContainers.ContainsKey(itemContainer)) - { - availableContainers[itemContainer]--; - } - if (availableContainers.ContainsKey(itemContainer) && availableContainers[itemContainer] <= 0) - { - availableContainers.Remove(itemContainer); - } - } - } - itemsToSpawn.Clear(); - } - } -} +using Barotrauma.Items.Components; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Barotrauma +{ + class PurchasedItem + { + public ItemPrefab itemPrefab; + public int quantity; + + public PurchasedItem(ItemPrefab itemPrefab, int quantity) + { + this.itemPrefab = itemPrefab; + this.quantity = quantity; + } + } + + class CargoManager + { + private readonly List purchasedItems; + + private readonly CampaignMode campaign; + + public Action OnItemsChanged; + + public List PurchasedItems + { + get { return purchasedItems; } + } + + public CargoManager(CampaignMode campaign) + { + purchasedItems = new List(); + this.campaign = campaign; + } + + public void SetPurchasedItems(List items) + { + purchasedItems.Clear(); + purchasedItems.AddRange(items); + + OnItemsChanged?.Invoke(); + } + + public void PurchaseItem(ItemPrefab item, int Quantity = 1) + { + PurchasedItem purchasedItem = PurchasedItems.Find(pi => pi.itemPrefab == item); + + if(purchasedItem != null && Quantity == 1) + { + campaign.Money -= item.Price; + purchasedItem.quantity += 1; + } + else + { + campaign.Money -= (item.Price * Quantity); + purchasedItem = new PurchasedItem(item, Quantity); + purchasedItems.Add(purchasedItem); + } + + OnItemsChanged?.Invoke(); + } + + public void SellItem(ItemPrefab item, int quantity = 1) + { + campaign.Money += (item.Price * quantity); + PurchasedItem purchasedItem = PurchasedItems.Find(pi => pi.itemPrefab == item); + if (purchasedItem != null && purchasedItem.quantity - quantity > 0) + { + purchasedItem.quantity -= quantity; + } + else + { + PurchasedItems.Remove(purchasedItem); + } + + OnItemsChanged?.Invoke(); + } + + public int GetTotalItemCost() + { + return purchasedItems.Sum(i => (i.itemPrefab.Price * i.quantity)); + } + + public void CreateItems() + { + CreateItems(purchasedItems); + OnItemsChanged?.Invoke(); + } + + public static void CreateItems(List itemsToSpawn) + { + WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub); + + if (wp == null) + { + DebugConsole.ThrowError("The submarine must have a waypoint marked as Cargo for bought items to be placed correctly!"); + return; + } + + Hull cargoRoom = Hull.FindHull(wp.WorldPosition); + + if (cargoRoom == null) + { + DebugConsole.ThrowError("A waypoint marked as Cargo must be placed inside a room!"); + return; + } + + Dictionary availableContainers = new Dictionary(); + ItemPrefab containerPrefab = null; + foreach (PurchasedItem pi in itemsToSpawn) + { + Vector2 position = new Vector2( + Rand.Range(cargoRoom.Rect.X + 20, cargoRoom.Rect.Right - 20), + cargoRoom.Rect.Y - cargoRoom.Rect.Height + pi.itemPrefab.Size.Y / 2); + + ItemContainer itemContainer = null; + if (!string.IsNullOrEmpty(pi.itemPrefab.CargoContainerName)) + { + itemContainer = availableContainers.Keys.ToList().Find(ac => + ac.Item.Prefab.NameMatches(pi.itemPrefab.CargoContainerName) || + ac.Item.Prefab.Tags.Contains(pi.itemPrefab.CargoContainerName.ToLowerInvariant())); + + if (itemContainer == null) + { + containerPrefab = MapEntityPrefab.List.Find(ep => + ep.NameMatches(pi.itemPrefab.CargoContainerName) || + (ep.Tags != null && ep.Tags.Contains(pi.itemPrefab.CargoContainerName.ToLowerInvariant()))) as ItemPrefab; + + if (containerPrefab == null) + { + DebugConsole.ThrowError("Cargo spawning failed - could not find the item prefab for container \"" + containerPrefab.Name + "\"!"); + continue; + } + + Item containerItem = new Item(containerPrefab, position, wp.Submarine); + itemContainer = containerItem.GetComponent(); + if (itemContainer == null) + { + DebugConsole.ThrowError("Cargo spawning failed - container \"" + containerItem.Name + "\" does not have an ItemContainer component!"); + continue; + } + availableContainers.Add(itemContainer, itemContainer.Capacity); + if (GameMain.Server != null) + { + Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false); + } + } + } + for (int i = 0; i < pi.quantity; i++) + { + if (itemContainer == null) + { + //no container, place at the waypoint + if (GameMain.Server != null) + { + Entity.Spawner.AddToSpawnQueue(pi.itemPrefab, position, wp.Submarine); + } + else + { + new Item(pi.itemPrefab, position, wp.Submarine); + } + continue; + } + //if the intial container has been removed due to it running out of space, add a new container + //of the same type and begin filling it + if (!availableContainers.ContainsKey(itemContainer)) + { + Item containerItemOverFlow = new Item(containerPrefab, position, wp.Submarine); + itemContainer = containerItemOverFlow.GetComponent(); + availableContainers.Add(itemContainer, itemContainer.Capacity); + if (GameMain.Server != null) + { + Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false); + } + } + + //place in the container + if (GameMain.Server != null) + { + Entity.Spawner.AddToSpawnQueue(pi.itemPrefab, itemContainer.Inventory); + } + else + { + var item = new Item(pi.itemPrefab, position, wp.Submarine); + itemContainer.Inventory.TryPutItem(item, null); + } + + //reduce the number of available slots in the container + //if there is a container + if (availableContainers.ContainsKey(itemContainer)) + { + availableContainers[itemContainer]--; + } + if (availableContainers.ContainsKey(itemContainer) && availableContainers[itemContainer] <= 0) + { + availableContainers.Remove(itemContainer); + } + } + } + itemsToSpawn.Clear(); + } + } +} From 6e606fdc519910594898ae364937037632936952 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 3 Sep 2018 15:15:27 +0300 Subject: [PATCH 189/198] Fixed incorrect rotation of welding tools and other 2-handed items that are held in one hand when not aiming. The items were rotated according to the left hand, but positioned on the right hand. --- .../Source/Characters/Animation/HumanoidAnimController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs index 04875d727..d503d7df7 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs @@ -1226,7 +1226,7 @@ namespace Barotrauma transformedHoldPos = rightHand.pullJoint.WorldAnchorA - transformedHandlePos[0]; itemAngle = (rightHand.Rotation + (holdAngle - MathHelper.PiOver2) * Dir); } - if (character.SelectedItems[1] == item) + else if (character.SelectedItems[1] == item) { if (leftHand.IsSevered) return; transformedHoldPos = leftHand.pullJoint.WorldAnchorA - transformedHandlePos[1]; From 8e7cd8e5f333c488c30b01ebbb5bfb86a906f18a Mon Sep 17 00:00:00 2001 From: Juan Pablo Arce Date: Sun, 9 Sep 2018 01:02:54 -0300 Subject: [PATCH 190/198] Added space and exclamation point to default AllowedClientNameChars --- .../BarotraumaShared/Source/Networking/GameServerSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs index f670bdb78..4a18ed99c 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/GameServerSettings.cs @@ -380,7 +380,7 @@ namespace Barotrauma.Networking GameMain.NetLobbyScreen.SetTraitorsEnabled(traitorsEnabled); //"65-90", "97-122", "48-59" = upper and lower case english alphabet and numbers - string[] allowedClientNameCharsStr = doc.Root.GetAttributeStringArray("AllowedClientNameChars", new string[] { "65-90", "97-122", "48-59" }); + string[] allowedClientNameCharsStr = doc.Root.GetAttributeStringArray("AllowedClientNameChars", new string[] { "32-33", "65-90", "97-122", "48-59" }); foreach (string allowedClientNameCharRange in allowedClientNameCharsStr) { string[] splitRange = allowedClientNameCharRange.Split('-'); From 2b3c0d103be6482c4ff757b3f8e6f16327502da3 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 10 Sep 2018 11:40:06 +0300 Subject: [PATCH 191/198] Fixed NotImplementedException when attempting to clone linked submarines --- Barotrauma/BarotraumaShared/Source/Map/LinkedSubmarine.cs | 7 ++++++- Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs | 5 +---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Map/LinkedSubmarine.cs b/Barotrauma/BarotraumaShared/Source/Map/LinkedSubmarine.cs index 79f4c679d..f0680617d 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/LinkedSubmarine.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/LinkedSubmarine.cs @@ -99,7 +99,12 @@ namespace Barotrauma { return Vector2.Distance(position, WorldPosition) < 50.0f; } - + + public override MapEntity Clone() + { + return CreateDummy(Submarine, filePath, Position); + } + private void GenerateWallVertices(XElement rootElement) { List points = new List(); diff --git a/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs index 200454f55..7ca8efdc1 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs @@ -163,10 +163,7 @@ namespace Barotrauma return (Submarine.RectContains(WorldRect, position)); } - public virtual MapEntity Clone() - { - throw new NotImplementedException(); - } + public abstract MapEntity Clone(); public static List Clone(List entitiesToClone) { From 57c9e5a731b5c1919cccb307430a508e8f9a4eb4 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 10 Sep 2018 12:06:39 +0300 Subject: [PATCH 192/198] Fixed IndexOutOfRange exceptions when cloning items with requiredItems that aren't present in the xml, + some more exception handling & error logging --- Barotrauma/BarotraumaShared/Source/Items/Item.cs | 14 ++++++++++++-- .../BarotraumaShared/Source/Map/MapEntity.cs | 14 +++++++++++++- .../BarotraumaShared/Source/Map/Submarine.cs | 2 +- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index 17dd9133d..54ef20b8d 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -495,14 +495,24 @@ namespace Barotrauma if (!property.Value.Attributes.OfType().Any()) continue; clone.properties[property.Key].TrySetValue(property.Value.GetValue()); } - for (int i = 0; i < components.Count; i++) + + if (components.Count != clone.components.Count) + { + string errorMsg = "Error while cloning item \"" + Name + "\" - clone does not have the same number of components. "; + errorMsg += "Original components: " + string.Join(", ", components.Select(c => c.GetType().ToString())); + errorMsg += ", cloned components: " + string.Join(", ", clone.components.Select(c => c.GetType().ToString())); + DebugConsole.ThrowError(errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Item.Clone:" + Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + } + + for (int i = 0; i < components.Count && i < clone.components.Count; i++) { foreach (KeyValuePair property in components[i].properties) { if (!property.Value.Attributes.OfType().Any()) continue; clone.components[i].properties[property.Key].TrySetValue(property.Value.GetValue()); } - for (int j = 0; j < components[i].requiredItems.Count; j++) + for (int j = 0; j < components[i].requiredItems.Count && i < clone.components[i].requiredItems.Count; j++) { clone.components[i].requiredItems[j].JoinedNames = components[i].requiredItems[j].JoinedNames; } diff --git a/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs index 7ca8efdc1..a3dc6f4af 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs @@ -171,7 +171,19 @@ namespace Barotrauma foreach (MapEntity e in entitiesToClone) { Debug.Assert(e != null); - clones.Add(e.Clone()); + try + { + clones.Add(e.Clone()); + } + catch (Exception ex) + { + DebugConsole.ThrowError("Cloning entity \"" + e.Name + "\" failed.", ex); + GameAnalyticsManager.AddErrorEventOnce( + "MapEntity.Clone:" + e.Name, + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Cloning entity \"" + e.Name + "\" failed (" + ex.Message + ").\n" + ex.StackTrace); + return clones; + } Debug.Assert(clones.Last() != null); } diff --git a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs index 9aa51ee67..3b20ba24f 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs @@ -170,7 +170,7 @@ namespace Barotrauma { get { - return subBody.Borders; + return subBody == null ? Rectangle.Empty : subBody.Borders; } } From 34c721bf89582e1c9e44c0d4093a01cceed104b5 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 10 Sep 2018 13:56:53 +0300 Subject: [PATCH 193/198] Hitscan weapons can hit targets outside the sub when firing from the inside and vice versa. Closes #639 --- .../Source/Items/Components/Projectile.cs | 97 +++++++++++++------ 1 file changed, 68 insertions(+), 29 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs index 859969cb4..18ed90922 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs @@ -12,6 +12,22 @@ namespace Barotrauma.Items.Components { class Projectile : ItemComponent { + struct HitscanResult + { + public Fixture Fixture; + public Vector2 Point; + public Vector2 Normal; + public float Fraction; + + public HitscanResult(Fixture fixture, Vector2 point, Vector2 normal, float fraction) + { + Fixture = fixture; + Point = point; + Normal = normal; + Fraction = fraction; + } + } + //continuous collision detection is used while the projectile is moving faster than this const float ContinuousCollisionThreshold = 5.0f; @@ -168,7 +184,7 @@ namespace Barotrauma.Items.Components GameMain.World.RemoveJoint(stickJoint); stickJoint = null; } - + private void DoHitscan(Vector2 dir) { float rotation = item.body.Rotation; @@ -183,39 +199,41 @@ namespace Barotrauma.Items.Components Vector2 rayStart = item.SimPosition; Vector2 rayEnd = item.SimPosition + dir * 1000.0f; - List> hits = new List>(); - GameMain.World.RayCast((fixture, point, normal, fraction) => + List hits = new List(); + + hits.AddRange(DoRayCast(rayStart, rayEnd)); + + if (item.Submarine != null) { - if (fixture == null || fixture.IsSensor) return -1; - - if (fixture.UserData is Item) return -1; - - if (!fixture.CollisionCategories.HasFlag(Physics.CollisionCharacter) && - !fixture.CollisionCategories.HasFlag(Physics.CollisionWall) && - !fixture.CollisionCategories.HasFlag(Physics.CollisionLevel)) return -1; - - /*item.body.SetTransform(point, rotation); - if (OnProjectileCollision(fixture, normal)) + //shooting indoors, do a hitscan outside as well + hits.AddRange(DoRayCast(rayStart + item.Submarine.SimPosition, rayEnd + item.Submarine.SimPosition)); + } + else + { + //shooting outdoors, see if we can hit anything inside a sub + foreach (Submarine submarine in Submarine.Loaded) { - Character.Controlled.AnimController.Teleport(point - Character.Controlled.SimPosition, Vector2.Zero); - hitSomething = true; - return 0; - }*/ + var inSubHits = DoRayCast(rayStart - submarine.SimPosition, rayEnd - submarine.SimPosition); + //transform back to world coordinates + for (int i = 0; i < inSubHits.Count; i++) + { + inSubHits[i] = new HitscanResult( + inSubHits[i].Fixture, + inSubHits[i].Point + submarine.SimPosition, + inSubHits[i].Normal, + inSubHits[i].Fraction); + } - hits.Add(new Tuple(fixture,point,normal)); - - return hits.Count<25 ? 1 : 0; - }, rayStart, rayEnd); + hits.AddRange(inSubHits); + } + } bool hitSomething = false; - hits = hits.OrderBy(t => Vector2.DistanceSquared(rayStart, t.Item2)).ToList(); - foreach (Tuple t in hits) + hits = hits.OrderBy(h => h.Fraction).ToList(); + foreach (HitscanResult h in hits) { - Fixture fixture = t.Item1; - Vector2 point = t.Item2; - Vector2 normal = t.Item3; - item.body.SetTransform(point, rotation); - if (OnProjectileCollision(fixture, normal)) + item.body.SetTransform(h.Point, rotation); + if (OnProjectileCollision(h.Fixture, h.Normal)) { hitSomething = true; break; @@ -229,6 +247,27 @@ namespace Barotrauma.Items.Components } } + private List DoRayCast(Vector2 rayStart, Vector2 rayEnd) + { + List hits = new List(); + GameMain.World.RayCast((fixture, point, normal, fraction) => + { + if (fixture == null || fixture.IsSensor) return -1; + + if (fixture.UserData is Item) return -1; + + if (!fixture.CollisionCategories.HasFlag(Physics.CollisionCharacter) && + !fixture.CollisionCategories.HasFlag(Physics.CollisionWall) && + !fixture.CollisionCategories.HasFlag(Physics.CollisionLevel)) return -1; + + hits.Add(new HitscanResult(fixture, point, normal, fraction)); + + return hits.Count < 25 ? 1 : 0; + }, rayStart, rayEnd); + + return hits; + } + public override void Update(float deltaTime, Camera cam) { ApplyStatusEffects(ActionType.OnActive, deltaTime, null); @@ -309,7 +348,7 @@ namespace Barotrauma.Items.Components item.Move(-submarine.Position); item.Submarine = submarine; item.body.Submarine = submarine; - return true; + return !Hitscan; } Structure structure; From 571ed150fef01bcda7d1ad56863fd9e719f758d5 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 10 Sep 2018 16:14:44 +0300 Subject: [PATCH 194/198] Ragdolls can't be dragged through walls or other colliders. Closes #772 --- .../Animation/HumanoidAnimController.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs index d503d7df7..965ddd280 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs @@ -1083,8 +1083,31 @@ namespace Barotrauma } } } + Limb pullLimb = i == 0 ? leftHand : rightHand; + if (GameMain.Client == null) + { + //stop dragging if there's something between the pull limb and the target limb + Vector2 sourceSimPos = pullLimb.SimPosition; + Vector2 targetSimPos = targetLimb.SimPosition; + if (character.Submarine != null && character.SelectedCharacter.Submarine == null) + { + targetSimPos -= character.Submarine.SimPosition; + } + else if (character.Submarine == null && character.SelectedCharacter.Submarine != null) + { + sourceSimPos -= character.SelectedCharacter.Submarine.SimPosition; + } + + var body = Submarine.CheckVisibility(sourceSimPos, targetSimPos, ignoreSubs: true); + if (body != null) + { + character.DeselectCharacter(); + return; + } + } + if (i == 1 && inWater) { targetLimb.pullJoint.Enabled = false; From 89c8de8b27e415cbdeecc8b598040968ddc7a833 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 10 Sep 2018 18:33:33 +0300 Subject: [PATCH 195/198] Dropped flashlights don't consume battery (and turn off automatically). Closes #775 --- Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml b/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml index 28f6bcfc1..ec89b9926 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Tools/tools.xml @@ -286,11 +286,11 @@ + + + + - - - - From ae70b973ddbc4a3c8c5a5f604476d2eeed373e44 Mon Sep 17 00:00:00 2001 From: juanjp600 Date: Mon, 10 Sep 2018 18:09:26 -0300 Subject: [PATCH 196/198] Changed TextureLoader.PreMultiplyAlpha return type to void --- .../BarotraumaClient/Source/Utils/TextureLoader.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Utils/TextureLoader.cs b/Barotrauma/BarotraumaClient/Source/Utils/TextureLoader.cs index e77eceafe..888519079 100644 --- a/Barotrauma/BarotraumaClient/Source/Utils/TextureLoader.cs +++ b/Barotrauma/BarotraumaClient/Source/Utils/TextureLoader.cs @@ -46,7 +46,7 @@ namespace Barotrauma using (Stream fileStream = File.OpenRead(path)) { var texture = Texture2D.FromStream(_graphicsDevice, fileStream); - texture = PreMultiplyAlpha(texture); + PreMultiplyAlpha(texture); return texture; } @@ -63,7 +63,7 @@ namespace Barotrauma try { var texture = Texture2D.FromStream(_graphicsDevice, fileStream); - texture = PreMultiplyAlpha(texture); + PreMultiplyAlpha(texture); return texture; } catch (Exception e) @@ -73,7 +73,7 @@ namespace Barotrauma } } - private static Texture2D PreMultiplyAlpha(Texture2D texture) + private static void PreMultiplyAlpha(Texture2D texture) { // Setup a render target to hold our final texture which will have premulitplied alpha values using (RenderTarget2D renderTarget = new RenderTarget2D(_graphicsDevice, texture.Width, texture.Height)) @@ -103,9 +103,7 @@ namespace Barotrauma // Unset texture from graphic device and set modified data back to it _graphicsDevice.Textures[0] = null; texture.SetData(data); - } - - return texture; + } } From a403ca44c5e21d820b2dbe3263a30531ec9d0eeb Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 12 Sep 2018 11:43:57 +0300 Subject: [PATCH 197/198] Fixed inventory sync delay being decremented in two places, causing the delay to be 0.5 seconds instead of the intended 1 s. Closes #788 --- Barotrauma/BarotraumaClient/Source/Items/Inventory.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs b/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs index c4a963467..135076d9e 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs @@ -500,7 +500,6 @@ namespace Barotrauma { while (syncItemsDelay > 0.0f || (GameMain.Client != null && GameMain.Client.MidRoundSyncing)) { - syncItemsDelay -= CoroutineManager.DeltaTime; yield return CoroutineStatus.Running; } From bf87006bc369f11678932c616d1a4e39f75a185f Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 12 Sep 2018 15:22:52 +0300 Subject: [PATCH 198/198] Attempt to fix items dropping client-side when moving them from inventory another (#558). The clients delay applying the received remote state of an inventory they just put an item inside, because otherwise the inventory may revert to an old state if items are moved in rapid succession. I think the problem was that the delay was not applied to the inventory the item is _taken from_, so it was possible that the clients applied an old delayed state even though an item had just been moved out of the inventory. Another problem was that the server didn't create an event for the inventory the item was removed from, so if there were any wrong items in a client-side inventory, they would just be dropped instead of being moved back to the correct inventory. --- .../Source/Items/Inventory.cs | 43 ++++++++++++++++--- .../Source/Items/ItemInventory.cs | 1 + 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs b/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs index 9ffc74290..037a80876 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs @@ -20,7 +20,7 @@ namespace Barotrauma public bool Locked; private ushort[] receivedItemIDs; - private float syncItemsDelay; + protected float syncItemsDelay; private CoroutineHandle syncItemsCoroutine; public Inventory(Entity owner, int capacity, Vector2? centerPos = null, int slotsPerRow=5) @@ -116,6 +116,8 @@ namespace Barotrauma { if (Owner == null) return; + Inventory prevInventory = item.ParentInventory; + if (removeItem) { item.Drop(user); @@ -137,6 +139,8 @@ namespace Barotrauma if (createNetworkEvent) { CreateNetworkEvent(); + //also delay syncing the inventory the item was inside + if (prevInventory != null && prevInventory != this) prevInventory.syncItemsDelay = 1.0f; } } @@ -148,7 +152,8 @@ namespace Barotrauma } #if CLIENT else if (GameMain.Client != null) - { + { + syncItemsDelay = 1.0f; GameMain.Client.CreateEntityEvent(Owner as IClientSerializable, new object[] { NetEntityEvent.Type.InventoryState }); } #endif @@ -189,8 +194,6 @@ namespace Barotrauma public void ClientWrite(NetBuffer msg, object[] extraData = null) { ServerWrite(msg, null); - - syncItemsDelay = 1.0f; } public void ServerRead(ClientNetObject type, NetBuffer msg, Barotrauma.Networking.Client c) @@ -203,18 +206,40 @@ namespace Barotrauma newItemIDs[i] = msg.ReadUInt16(); } - if (c == null || c.Character == null || !c.Character.CanAccessInventory(this)) return; + if (c == null || c.Character == null) return; + + if (!c.Character.CanAccessInventory(this)) + { + //create a network event to correct the client's inventory state + //otherwise they may have an item in their inventory they shouldn't have been able to pick up, + //and receiving an event for that inventory later will cause the item to be dropped + CreateNetworkEvent(); + for (int i = 0; i < capacity; i++) + { + var item = Entity.FindEntityByID(newItemIDs[i]) as Item; + if (item == null) continue; + if (item.ParentInventory != null && item.ParentInventory != this) + { + item.ParentInventory.CreateNetworkEvent(); + } + } + return; + } + + List prevItemInventories = new List(Items.Select(i => i?.ParentInventory)); for (int i = 0; i < capacity; i++) { - if (newItemIDs[i] == 0 || (Entity.FindEntityByID(newItemIDs[i]) as Item != Items[i])) + Item newItem = newItemIDs[i] == 0 ? null : Entity.FindEntityByID(newItemIDs[i]) as Item; + prevItemInventories.Add(newItem?.ParentInventory); + + if (newItemIDs[i] == 0 || (newItem != Items[i])) { if (Items[i] != null) Items[i].Drop(); System.Diagnostics.Debug.Assert(Items[i] == null); } } - for (int i = 0; i < capacity; i++) { if (newItemIDs[i] > 0) @@ -234,6 +259,10 @@ namespace Barotrauma } CreateNetworkEvent(); + foreach (Inventory prevInventory in prevItemInventories.Distinct()) + { + if (prevInventory != this) prevInventory?.CreateNetworkEvent(); + } foreach (Item item in Items.Distinct()) { diff --git a/Barotrauma/BarotraumaShared/Source/Items/ItemInventory.cs b/Barotrauma/BarotraumaShared/Source/Items/ItemInventory.cs index eb6039bc6..71e12115b 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/ItemInventory.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/ItemInventory.cs @@ -101,6 +101,7 @@ namespace Barotrauma #if CLIENT else if (GameMain.Client != null) { + syncItemsDelay = 1.0f; GameMain.Client.CreateEntityEvent(Owner as IClientSerializable, new object[] { NetEntityEvent.Type.InventoryState, componentIndex }); } #endif