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 diff --git a/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs index 40fceb2b3..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.5")] -[assembly: AssemblyFileVersion("0.8.1.5")] +[assembly: AssemblyVersion("0.8.2.0")] +[assembly: AssemblyFileVersion("0.8.2.0")] 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 diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs index 309920928..f5d90be8b 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; } @@ -97,7 +100,6 @@ namespace Barotrauma foreach (Limb limb in Limbs) { - if (limb.pullJoint != null) { Vector2 pos = ConvertUnits.ToDisplayUnits(limb.pullJoint.WorldAnchorA); @@ -134,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/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; } 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/BarotraumaClient/Source/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs index 7fe1028ac..2dc31ab59 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)) @@ -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; @@ -342,7 +344,7 @@ namespace Barotrauma } else { - this.isDead = false; + if (this.isDead) Revive(); health = msg.ReadRangedSingle(minHealth, maxHealth, 8); diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs b/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs index 6bf04257c..395c60f0c 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 * 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); } diff --git a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs index b095508b2..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) @@ -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/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/GameMain.cs b/Barotrauma/BarotraumaClient/Source/GameMain.cs index 73363e61b..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; @@ -134,7 +136,7 @@ namespace Barotrauma Config.WasGameUpdated = false; Config.Save("config.xml"); } - + ApplyGraphicsSettings(); Content.RootDirectory = "Content"; @@ -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) @@ -244,12 +275,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/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/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) 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/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/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; 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; 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/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/Signal/Wire.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Wire.cs index 297a045d0..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,7 +83,7 @@ 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) 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/DockingPort.cs b/Barotrauma/BarotraumaClient/Source/Items/DockingPort.cs index f7de0dab8..0800ae328 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,43 @@ namespace Barotrauma.Items.Components rect.Width, (int)(rect.Height / 2 * dockingState)), Color.White); } } + + if (!GameMain.DebugDraw) return; + + 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); + } } } diff --git a/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs b/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs index 7b1028a69..135076d9e 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,75 @@ 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 + { + if (syncItemsCoroutine != null) + { + CoroutineManager.StopCoroutines(syncItemsCoroutine); + syncItemsCoroutine = null; + } + ApplyReceivedState(); + } + } + + private IEnumerable SyncItemsAfterDelay() + { + while (syncItemsDelay > 0.0f || (GameMain.Client != null && GameMain.Client.MidRoundSyncing)) + { + yield return CoroutineStatus.Running; + } + + if (Owner.Removed || GameMain.Client == null) + { + yield return CoroutineStatus.Success; + } + + ApplyReceivedState(); + + yield return CoroutineStatus.Success; + } + + private void ApplyReceivedState() + { + for (int i = 0; i < capacity; i++) + { + 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); + } + } + + for (int i = 0; i < capacity; i++) + { + if (receivedItemIDs[i] > 0) + { + var item = Entity.FindEntityByID(receivedItemIDs[i]) as Item; + if (item == null || item == Items[i]) continue; + TryPutItem(item, i, true, true, null, false); + } + } + + receivedItemIDs = null; + } } } diff --git a/Barotrauma/BarotraumaClient/Source/Items/Item.cs b/Barotrauma/BarotraumaClient/Source/Items/Item.cs index 978540201..d77fe94e6 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Item.cs @@ -377,11 +377,11 @@ 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); - + condition = msg.ReadSingle(); if (FixRequirements.Count > 0) { if (Condition <= 0.0f) @@ -401,10 +401,13 @@ 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); + ReadPropertyChange(msg, false); + break; + case NetEntityEvent.Type.Invalid: break; } } @@ -423,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) @@ -441,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(); @@ -453,7 +457,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 +465,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 +487,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 +507,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/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/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/BarotraumaClient/Source/Map/Submarine.cs b/Barotrauma/BarotraumaClient/Source/Map/Submarine.cs index c6c4e786e..e275bceff 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; @@ -145,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), @@ -179,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")); } @@ -216,6 +217,33 @@ 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.MemPos.Insert(index, new PosInfo(newTargetPosition, 0.0f, sendingTime)); + } } } 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 47c0a4e7a..fe097d88c 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; } @@ -61,6 +64,17 @@ namespace Barotrauma.Networking { get { return fileReceiver; } } + + public bool MidRoundSyncing + { + get { return entityEventManager.MidRoundSyncing; } + } + + public bool AllowDisguises + { + get; + private set; + } public GameClient(string newName) { @@ -99,6 +113,8 @@ namespace Barotrauma.Networking otherClients = new List(); + ServerLog = new ServerLog(""); + ChatMessage.LastID = 0; GameMain.NetLobbyScreen = new NetLobbyScreen(); } @@ -456,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); @@ -480,7 +499,7 @@ namespace Barotrauma.Networking if (gameStarted && Screen.Selected == GameMain.GameScreen) { - endVoteTickBox.Visible = Voting.AllowEndVoting && myCharacter != null; + endVoteTickBox.Visible = Voting.AllowEndVoting && HasSpawned; if (respawnManager != null) { @@ -656,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); } @@ -666,6 +687,7 @@ namespace Barotrauma.Networking private IEnumerable StartGame(NetIncomingMessage inc) { if (Character != null) Character.Remove(); + HasSpawned = false; GameMain.LightManager.LightingEnabled = true; @@ -696,6 +718,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; @@ -734,6 +757,8 @@ namespace Barotrauma.Networking GameMain.NetLobbyScreen.UsingShuttle = usingShuttle; + AllowDisguises = disguisesAllowed; + if (campaign == null) { if (!GameMain.NetLobbyScreen.TrySelectSub(subName, subHash, GameMain.NetLobbyScreen.SubList)) @@ -757,7 +782,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); @@ -815,7 +843,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); @@ -908,6 +936,8 @@ namespace Barotrauma.Networking { GameMain.NetLobbyScreen.LastUpdateID = updateID; + ServerLog.ServerName = serverName; + GameMain.NetLobbyScreen.ServerName = serverName; GameMain.NetLobbyScreen.ServerMessage.Text = serverText; @@ -982,6 +1012,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) { @@ -1016,41 +1049,53 @@ 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 + "\"!)", + "Message length: " + inc.LengthBits + " (" + inc.LengthBytes + " bytes)", + prevObjHeader != null ? "Previous object type: " + prevObjHeader.ToString() : "Error occurred on the very first object!", + "Previous object was " + (prevBitLength) + " bits long (" + (prevByteLength) + " 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); + } + 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"); 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; @@ -1181,8 +1226,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++) { @@ -1277,6 +1326,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); @@ -1341,6 +1407,17 @@ namespace Barotrauma.Networking public override void Disconnect() { client.Shutdown(""); + + foreach (var fileTransfer in FileReceiver.ActiveTransfers) + { + fileTransfer.Dispose(); + } + + if (HasPermission(ClientPermissions.ServerLog)) + { + ServerLog?.Save(); + } + GameMain.NetworkMember = null; } @@ -1560,14 +1637,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..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,18 @@ 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) + { + 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; diff --git a/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs b/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs index 2ea912a2f..e25b575a4 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(); @@ -140,7 +145,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; } @@ -153,23 +164,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 @@ -188,10 +200,19 @@ namespace Barotrauma.Networking catch (Exception e) { + 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--) + { + 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, errorMsg); msg.Position = msgPosition + msgLength * 8; } } diff --git a/Barotrauma/BarotraumaClient/Source/Networking/NetworkMember.cs b/Barotrauma/BarotraumaClient/Source/Networking/NetworkMember.cs index cfc4b7f22..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,53 +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 (EndVoteCount > 0) + if (GUI.DisableHUD) return; + + if (gameStarted && Screen.Selected == GameMain.GameScreen) { - if (GameMain.NetworkMember.myCharacter == null) + GameMain.GameSession.CrewManager.Draw(spriteBatch); + + inGameHUD.Draw(spriteBatch); + + if (respawnManager != 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); + 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); + } } } - - if (respawnManager != null) - { - 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/Networking/Voting.cs b/Barotrauma/BarotraumaClient/Source/Networking/Voting.cs index b0a6294bb..b80f73379 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,12 @@ namespace Barotrauma { int votes = inc.ReadByte(); string subName = inc.ReadString(); - Submarine sub = Submarine.SavedSubmarines.Find(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); } } 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/CampaignSetupUI.cs b/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs index b477aee46..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)) @@ -144,7 +152,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/CampaignUI.cs b/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs index 4751c8ac2..a9e6f6cfc 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; @@ -149,6 +149,8 @@ namespace Barotrauma null, null, Alignment.TopRight, "", frame); } + + RefreshItemTab(); } public void Update(float deltaTime) @@ -216,55 +218,92 @@ 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 = 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) { - 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; @@ -272,15 +311,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; @@ -289,10 +328,13 @@ 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(); } public void SelectTab(Tab tab) @@ -311,16 +353,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/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); diff --git a/Barotrauma/BarotraumaClient/Source/Screens/LobbyScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/LobbyScreen.cs index 41ce03dfd..9a3ab04b0 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/LobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/LobbyScreen.cs @@ -82,13 +82,15 @@ 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(); - if (GameSettings.SendUserStatistics) GameAnalyticsSDK.Net.GameAnalytics.SetCustomDimension01("singleplayer"); + GameAnalyticsManager.SetCustomDimension01("singleplayer"); } public override void AddToGUIUpdateList() @@ -190,7 +192,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/BarotraumaClient/Source/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs index 6785b2c01..8804d17a6 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) @@ -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"), ""); diff --git a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs index 6f5edabac..f52552cf1 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; } } @@ -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) { @@ -501,7 +503,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); @@ -737,8 +739,21 @@ 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; + GUITextBlock submarineTextBlock = component.GetChild(); + if (submarineTextBlock != null) + { + submarineTextBlock.TextColor = Color.DarkRed * 0.8f; + submarineTextBlock.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; @@ -782,8 +797,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 +1482,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) @@ -1520,7 +1535,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; diff --git a/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs index 12659034f..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; @@ -349,7 +349,7 @@ namespace Barotrauma cam.UpdateTransform(); - if (GameSettings.SendUserStatistics) GameAnalyticsSDK.Net.GameAnalytics.SetCustomDimension01("editor"); + GameAnalyticsManager.SetCustomDimension01("editor"); } public override void Deselect() @@ -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); @@ -1082,6 +1086,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++) diff --git a/Barotrauma/BarotraumaClient/Source/Sounds/OggSound.cs b/Barotrauma/BarotraumaClient/Source/Sounds/OggSound.cs index 37aa0a313..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,11 +52,13 @@ 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); - ALHelper.Check(); + ALHelper.Check(oggFile); } //AL.Source(alSourceId, ALSourcei.Buffer, alBufferId); @@ -100,12 +102,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 8f88dfbb8..945cd0173 100644 --- a/Barotrauma/BarotraumaClient/Source/Sounds/OggStream.cs +++ b/Barotrauma/BarotraumaClient/Source/Sounds/OggStream.cs @@ -45,16 +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) + extraErrorMsg, + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + errorMsg); } } } @@ -83,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); @@ -94,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) @@ -163,7 +179,7 @@ namespace Barotrauma.Sounds AL.SourcePlay(alSourceId); this.Volume = volume; - ALHelper.Check(); + ALHelper.Check(FileName); Preparing = false; @@ -177,7 +193,7 @@ namespace Barotrauma.Sounds OggStreamer.Instance.RemoveStream(this); AL.SourcePause(alSourceId); - ALHelper.Check(); + ALHelper.Check(FileName); } public void Resume() @@ -187,7 +203,7 @@ namespace Barotrauma.Sounds OggStreamer.Instance.AddStream(this); AL.SourcePlay(alSourceId); - ALHelper.Check(); + ALHelper.Check(FileName); } public void Stop() @@ -226,7 +242,7 @@ namespace Barotrauma.Sounds set { AL.Source(alSourceId, ALSourcef.Gain, volume = value); - ALHelper.Check(); + ALHelper.Check(FileName); } } @@ -256,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) { @@ -292,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(); } @@ -314,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); @@ -429,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; } @@ -466,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; @@ -496,7 +512,7 @@ namespace Barotrauma.Sounds } AL.SourceQueueBuffers(stream.alSourceId, tempBuffers.Length, tempBuffers); - ALHelper.Check(); + ALHelper.Check(stream.FileName); if (finished && !stream.IsLooped) continue; @@ -514,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..e9bfdcfb8 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) { @@ -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; } 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) 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/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; + } } diff --git a/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs index 66089601e..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.5")] -[assembly: AssemblyFileVersion("0.8.1.5")] +[assembly: AssemblyVersion("0.8.2.0")] +[assembly: AssemblyFileVersion("0.8.2.0")] 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/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/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 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/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/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/Content/Items/Electricity/monitor.png b/Barotrauma/BarotraumaShared/Content/Items/Electricity/monitor.png deleted file mode 100644 index dab830060..000000000 Binary files a/Barotrauma/BarotraumaShared/Content/Items/Electricity/monitor.png and /dev/null differ diff --git a/Barotrauma/BarotraumaShared/Content/Items/Electricity/monitors.xml b/Barotrauma/BarotraumaShared/Content/Items/Electricity/monitors.xml deleted file mode 100644 index 780f08b61..000000000 --- a/Barotrauma/BarotraumaShared/Content/Items/Electricity/monitors.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/Content/Items/Electricity/signalitems.xml b/Barotrauma/BarotraumaShared/Content/Items/Electricity/signalitems.xml index 0c01875bd..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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + 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 @@ - + 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 @@ - + + + + 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/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/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 @@ + + + + - - - - 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 11f441901..dfc44a9be 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Weapons/railgun.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Weapons/railgun.xml @@ -2,13 +2,18 @@ - + + + holdpos="35,-10" aimpos="35,-10" handle1="-15,-6" handle2="26,7" holdangle="-40"/> @@ -119,15 +119,15 @@ + + - - - - + + - - + aimpos="90,10" handle1="-11,-7"/> + + 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 @@ - - - - - + + + + + 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 @@ - 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/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/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs index 495add3f1..ba7a0f353 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs @@ -274,7 +274,15 @@ namespace Barotrauma private void UpdateEscape(float deltaTime) { - SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(SimPosition - selectedAiTarget.SimPosition) * 5); + if (selectedAiTarget == null || selectedAiTarget.Entity == null || selectedAiTarget.Entity.Removed) + { + state = AIState.None; + return; + } + + 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); } @@ -484,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/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); } 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/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/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/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 ae83fb868..965ddd280 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 { @@ -92,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; } @@ -180,6 +190,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 @@ -1067,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; @@ -1204,25 +1243,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); } + else 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; } @@ -1235,6 +1278,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); @@ -1277,6 +1338,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/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs index c9a371e76..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; @@ -25,6 +26,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) + "\n" + Environment.StackTrace); + return new Limb[0]; } return limbs; @@ -42,6 +48,7 @@ namespace Barotrauma frozen = value; Collider.PhysEnabled = !frozen; + if (frozen && MainLimb != null) MainLimb.pullJoint.WorldAnchorB = MainLimb.SimPosition; } } @@ -87,7 +94,11 @@ namespace Barotrauma protected List collider; protected int colliderIndex = 0; - + + private Category prevCollisionCategory = Category.None; + + private Body outsideCollisionBlocker; + public PhysicsBody Collider { get @@ -269,11 +280,7 @@ namespace Barotrauma get { return ignorePlatforms; } set { - if (ignorePlatforms == value) return; ignorePlatforms = value; - - UpdateCollisionCategories(); - } } @@ -352,21 +359,29 @@ 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) { - 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); } @@ -391,7 +406,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) @@ -488,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) { @@ -739,7 +757,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); @@ -754,10 +780,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) @@ -774,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 @@ -799,10 +820,49 @@ namespace Barotrauma } CurrentHull = newHull; + character.Submarine = currentHull?.Submarine; + } - character.Submarine = currentHull == null ? null : currentHull.Submarine; + private void PreventOutsideCollision() + { + if (currentHull?.Submarine == null) + { + outsideCollisionBlocker.Enabled = false; + return; + } - UpdateCollisionCategories(); + 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) @@ -842,7 +902,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) @@ -868,10 +931,12 @@ namespace Barotrauma UpdateNetPlayerPosition(deltaTime); CheckDistFromCollider(); + UpdateCollisionCategories(); Vector2 flowForce = Vector2.Zero; FindHull(); + PreventOutsideCollision(); splashSoundTimer -= deltaTime; @@ -1089,22 +1154,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); + } + } } } } @@ -1165,6 +1227,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); @@ -1245,9 +1317,9 @@ 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; + //force collision categories to be updated + prevCollisionCategory = Category.None; } } @@ -1312,8 +1384,24 @@ namespace Barotrauma character.AnimController.Anim = AnimController.Animation.None; } - Collider.LinearVelocity = Vector2.Zero; - Collider.CorrectPosition(character.MemState, deltaTime, out 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.01f) + { + Collider.SetTransform(newPosition, Collider.Rotation); + } //unconscious/dead characters can't correct their position using AnimController movement // -> we need to correct it manually @@ -1431,10 +1519,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); + } + } } } @@ -1445,22 +1541,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.LengthSquared() < 0.01f) 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; } @@ -1526,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/Characters/Character.cs b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs index dbf3e7e0c..7eb02a887 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; } } @@ -1611,12 +1615,20 @@ 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); + + UpdateSightRange(); + if (aiTarget != null) aiTarget.SoundRange = 0.0f; - if (!IsDead) LockHands = false; + lowPassMultiplier = MathHelper.Lerp(lowPassMultiplier, 1.0f, 0.1f); + + if (health <= minHealth) Kill(CauseOfDeath.Bloodloss); //ragdoll button if (IsRagdolled) @@ -1643,21 +1655,7 @@ 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); @@ -1960,7 +1958,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); @@ -1997,7 +1995,7 @@ namespace Barotrauma } partial void KillProjSpecific(); - public void Revive(bool isNetworkMessage) + public void Revive() { if (Removed) { @@ -2024,6 +2022,7 @@ namespace Barotrauma foreach (Limb limb in AnimController.Limbs) { + limb.body.Enabled = true; limb.IsSevered = false; } @@ -2044,6 +2043,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 +2056,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; 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/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/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/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 } } diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs index ffb676918..200acdeb0 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) => @@ -249,7 +260,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); @@ -1323,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) @@ -1342,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) @@ -1502,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) => @@ -1712,7 +1730,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 }); @@ -1895,10 +1913,19 @@ namespace Barotrauma return; } - if (string.IsNullOrWhiteSpace(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); @@ -1976,6 +2003,13 @@ namespace Barotrauma 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()); @@ -2139,57 +2173,55 @@ namespace Barotrauma 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 = args[0]; + string itemName = string.Join(" ", args.Take(args.Length - extraParams)).ToLowerInvariant(); - var itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab; + 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 && 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 +2230,7 @@ namespace Barotrauma if (spawnPos != null) { Entity.Spawner.AddToSpawnQueue(itemPrefab, (Vector2)spawnPos); + } else if (spawnInventory != null) { 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/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/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/GameAnalyticsManager.cs b/Barotrauma/BarotraumaShared/Source/GameAnalyticsManager.cs index fa24b69e9..36cc8fbe1 100644 --- a/Barotrauma/BarotraumaShared/Source/GameAnalyticsManager.cs +++ b/Barotrauma/BarotraumaShared/Source/GameAnalyticsManager.cs @@ -1,19 +1,51 @@ using GameAnalyticsSDK.Net; using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Security.Cryptography; namespace Barotrauma { public static class GameAnalyticsManager { + private static HashSet sentEventIdentifiers = new HashSet(); + public static void Init() { -#if DEBUB +#if DEBUG 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.ConfigureBuild(GameMain.Version.ToString() + + (string.IsNullOrEmpty(exeName) ? "Unknown" : exeName) + ":" + + ((exeHash?.ShortHash == null) ? "Unknown" : exeHash.ShortHash)); + + GameAnalytics.AddDesignEvent("Executable:" + + (string.IsNullOrEmpty(exeName) ? "Unknown" : exeName) + ":" + + ((exeHash?.ShortHash == null) ? "Unknown" : exeHash.ShortHash)); + GameAnalytics.ConfigureAvailableCustomDimensions01("singleplayer", "multiplayer", "editor"); GameAnalytics.Initialize("a3a073c20982de7c15d21e840e149122", "9010ad9a671233b8d9610d76cec8c897d9ff3ba7"); - + string contentPackageName = GameMain.Config?.SelectedContentPackage?.Name; if (!string.IsNullOrEmpty(contentPackageName)) { @@ -21,5 +53,47 @@ 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); + } + + 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/CargoManager.cs b/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs index e07c7e124..51c9d475e 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,33 +45,53 @@ 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() { CreateItems(purchasedItems); + OnItemsChanged?.Invoke(); } - public static void CreateItems(List itemsToSpawn) + public static void CreateItems(List itemsToSpawn) { WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub); @@ -78,24 +110,25 @@ namespace Barotrauma } Dictionary availableContainers = new Dictionary(); - foreach (ItemPrefab prefab in itemsToSpawn) + 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 + 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; + 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) { @@ -117,41 +150,57 @@ namespace Barotrauma } } } + 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); + } + } - 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); + Entity.Spawner.AddToSpawnQueue(pi.itemPrefab, itemContainer.Inventory); } else { - var item = new Item(prefab, position, wp.Submarine); + 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) + //if there is a container + if (availableContainers.ContainsKey(itemContainer)) + { + availableContainers[itemContainer]--; + } + if (availableContainers.ContainsKey(itemContainer) && 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/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/Source/GameSession/GameSession.cs index ef9c4f3fb..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,18 +228,17 @@ 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(); } } - - if (GameSettings.SendUserStatistics) - { - GameAnalyticsSDK.Net.GameAnalytics.AddDesignEvent("Submarine:" + submarine.Name); - GameAnalyticsSDK.Net.GameAnalytics.AddProgressionEvent(GameAnalyticsSDK.Net.EGAProgressionStatus.Start, + + 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())); - } + #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/GameSettings.cs b/Barotrauma/BarotraumaShared/Source/GameSettings.cs index a476f143f..9acd0b434 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) { @@ -398,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() 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) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs index 236a2c85d..9b9b4c888 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs @@ -27,18 +27,14 @@ namespace Barotrauma.Items.Components private Joint joint; - private Hull[] hulls; - private ushort?[] hullIds; + private readonly Hull[] hulls = new Hull[2]; + 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); } @@ -129,7 +123,7 @@ namespace Barotrauma.Items.Components if (joint != null) { CreateJoint(joint is WeldJoint); - LinkHullsToGap(); + LinkHullsToGaps(); } else if (dockingTarget.joint != null) { @@ -138,7 +132,7 @@ namespace Barotrauma.Items.Components { dockingTarget.CreateJoint(dockingTarget.joint is WeldJoint); } - dockingTarget.LinkHullsToGap(); + dockingTarget.LinkHullsToGaps(); } } } @@ -207,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); } } @@ -238,35 +226,37 @@ 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; } - 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); + if (!item.linkedTo.Any(e => e is Hull) && !dockingTarget.item.linkedTo.Any(e => e is Hull)) { CreateHull(); @@ -345,17 +335,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() @@ -363,7 +367,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) @@ -392,6 +395,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]); + hulls[i].FreeID(); for (int j = 0; j < 2; j++) { @@ -402,9 +406,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; - - LinkHullsToGap(); } else { @@ -422,25 +423,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(); } + LinkHullsToGaps(); + + hulls[0].ShouldBeSaved = false; + hulls[1].ShouldBeSaved = false; item.linkedTo.Add(hulls[0]); item.linkedTo.Add(hulls[1]); - hullIds[0] = hulls[0].ID; - hullIds[1] = hulls[1].ID; - + gap.FreeID(); gap.DisableHullRechecks = true; - gapId = gap.ID; - + gap.ShouldBeSaved = false; item.linkedTo.Add(gap); foreach (Body body in bodies) @@ -453,7 +451,7 @@ namespace Barotrauma.Items.Components } } - private void LinkHullsToGap() + private void LinkHullsToGaps() { if (gap == null || hulls == null || hulls[0] == null || hulls[1] == null) { @@ -491,6 +489,37 @@ namespace Barotrauma.Items.Components gap.linkedTo.Add(hulls[0]); } } + + for (int i = 0; i < 2; i++) + { + 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 (!doorGap.linkedTo.Contains(hulls[0])) doorGap.linkedTo.Add(hulls[0]); + } + else + { + if (!doorGap.linkedTo.Contains(hulls[1])) doorGap.linkedTo.Add(hulls[1]); + } + } + else + { + if (item.WorldPosition.Y < dockingTarget.item.WorldPosition.Y) + { + if (!doorGap.linkedTo.Contains(hulls[0])) doorGap.linkedTo.Add(hulls[0]); + } + else + { + if (!doorGap.linkedTo.Contains(hulls[1])) doorGap.linkedTo.Add(hulls[1]); + } + } + } } public void Undock() @@ -503,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; @@ -546,26 +569,17 @@ 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) { gap.Remove(); gap = null; } - - hullIds[0] = null; - hullIds[1] = null; - - gapId = null; - if (bodies!=null) + if (bodies != null) { foreach (Body body in bodies) { @@ -627,6 +641,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() @@ -715,19 +732,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); } } @@ -735,19 +741,27 @@ 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(); 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)) { @@ -767,10 +781,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/Items/Components/Door.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs index 20e3900d5..80b768ae9 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(); @@ -349,6 +353,14 @@ 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 + ")"); + 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; + } + //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 +372,16 @@ 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 + ")"); + 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); List bodies = c.AnimController.Limbs.Select(l => l.body).ToList(); @@ -368,17 +390,24 @@ 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) { 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; } @@ -442,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 diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs index f37c5e456..bf954d466 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); @@ -160,7 +171,24 @@ namespace Barotrauma.Items.Components if (item.body != null) { item.body.ResetDynamics(); - item.SetTransform(picker.SimPosition, 0.0f); + 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; + //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); } picker.DeselectItem(item); @@ -214,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) @@ -221,6 +257,8 @@ namespace Barotrauma.Items.Components return base.Pick(picker); } + if (!CanBeDeattached()) return false; + if (Attached) { return base.Pick(picker); @@ -330,7 +368,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 { @@ -410,7 +448,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) @@ -419,7 +457,7 @@ namespace Barotrauma.Items.Components return; } - if (isAttached) + if (shouldBeAttached) { Drop(false, null); item.SetTransform(simPosition, 0.0f); @@ -427,16 +465,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(); + } } } } 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 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/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/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/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); } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Controller.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Controller.cs index 9c7dd768c..926742400 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; } @@ -116,7 +119,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; @@ -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,17 @@ 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].Condition <= 0.0f) continue; + 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/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); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs index 4c553b0ef..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; @@ -291,7 +292,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/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/Items/Components/Power/PowerContainer.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs index 475d322a9..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, false), 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) { 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); } 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/Items/Components/Projectile.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs index 5ee79b5d7..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,37 +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.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; @@ -227,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); @@ -291,6 +332,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; @@ -305,7 +348,7 @@ namespace Barotrauma.Items.Components item.Move(-submarine.Position); item.Submarine = submarine; item.body.Submarine = submarine; - return true; + return !Hitscan; } Structure structure; 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/ConnectionPanel.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs index f01912854..88a2471b8 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; @@ -13,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) @@ -149,11 +157,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); } } } @@ -166,9 +172,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(); @@ -182,6 +187,9 @@ namespace Barotrauma.Items.Components } } } + + //don't allow rewiring locked panels + if (Locked) return; item.CreateServerEvent(this); @@ -294,52 +302,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/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(); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/WifiComponent.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/WifiComponent.cs index e3ee5ff30..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; } @@ -95,6 +97,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/Components/Signal/Wire.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs index 2e4cac0fe..97907f398 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs @@ -30,19 +30,30 @@ 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; private Connection[] connections; + 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) @@ -158,19 +169,20 @@ 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) { 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; @@ -211,29 +223,85 @@ 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; + } + + if (item.CurrentHull == null) + { + newNodePos = item.WorldPosition - sub.Position - sub.HiddenSubPosition; + canPlaceNode = false; + } + else + { + 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 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 && GameMain.Client == null) + { + ClearConnections(); + CreateNetworkEvent(); + return; + } + } + } + } + else + { + newNodePos = RoundNode(item.Position, item.CurrentHull) - sub.HiddenSubPosition; + canPlaceNode = true; } - - newNodePos = RoundNode(item.Position, item.CurrentHull) - sub.HiddenSubPosition; } - + 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) + 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; @@ -376,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; @@ -527,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); @@ -540,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/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs index 7ad0320a4..cdea88b22 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs @@ -71,7 +71,7 @@ namespace Barotrauma.Items.Components rotation = (minRotation + maxRotation) / 2; } } - + public Turret(Item item, XElement element) : base(item, element) { @@ -155,6 +155,12 @@ namespace Barotrauma.Items.Components } public override bool Use(float deltaTime, Character character = null) + { + if (!characterUsable && character != null) return false; + return TryLaunch(character); + } + + private bool TryLaunch(Character character = null) { if (GameMain.Client != null) return false; @@ -164,7 +170,7 @@ namespace Barotrauma.Items.Components if (projectiles.Count == 0) return false; if (GetAvailablePower() < powerConsumption) return false; - + var batteries = item.GetConnectedComponents(); 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,14 +419,20 @@ 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; } } 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 eedb8504a..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,10 +139,12 @@ namespace Barotrauma if (createNetworkEvent) { CreateNetworkEvent(); + //also delay syncing the inventory the item was inside + if (prevInventory != null && prevInventory != this) prevInventory.syncItemsDelay = 1.0f; } } - private void CreateNetworkEvent() + protected virtual void CreateNetworkEvent() { if (GameMain.Server != null) { @@ -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) @@ -202,33 +205,64 @@ namespace Barotrauma { newItemIDs[i] = msg.ReadUInt16(); } - - if (c == null || c.Character == null || !c.Character.CanAccessInventory(this)) + + 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++) + { + 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) - { - if (Items[i] != null) Items[i].Drop(c.Character); - System.Diagnostics.Debug.Assert(Items[i]==null); - } - else + if (newItemIDs[i] > 0) { var item = Entity.FindEntityByID(newItemIDs[i]) as Item; if (item == null || item == Items[i]) continue; 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); } } - GameMain.Server.CreateEntityEvent(Owner as IServerSerializable, new object[] { NetEntityEvent.Type.InventoryState }); + CreateNetworkEvent(); + foreach (Inventory prevInventory in prevItemInventories.Distinct()) + { + if (prevInventory != this) prevInventory?.CreateNetworkEvent(); + } foreach (Item item in Items.Distinct()) { @@ -269,63 +303,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; - } } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index 32da8efeb..54ef20b8d 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -45,6 +45,8 @@ namespace Barotrauma public List drawableComponents; public PhysicsBody body; + + public readonly XElement StaticBodyConfig; private Vector2 lastSentPos; private bool prevBodyAwake; @@ -252,6 +254,11 @@ namespace Barotrauma get { return prefab.FireProof; } } + public bool WaterProof + { + get { return prefab.WaterProof; } + } + public bool CanUseOnSelf { get { return prefab.CanUseOnSelf; } @@ -268,7 +275,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; } @@ -335,18 +351,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; @@ -361,7 +377,7 @@ namespace Barotrauma rect = newRect; - condition = (float)(spawnCondition ?? prefab.Health); + condition = prefab.Health; lastSentCondition = condition; XElement element = prefab.ConfigElement; @@ -385,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); @@ -416,11 +435,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); @@ -479,14 +495,29 @@ 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 && i < clone.components[i].requiredItems.Count; j++) + { + clone.components[i].requiredItems[j].JoinedNames = components[i].requiredItems[j].JoinedNames; + } } + if (ContainedItems != null) { foreach (Item containedItem in ContainedItems) @@ -532,6 +563,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 @@ -637,7 +682,6 @@ namespace Barotrauma if (Container == null) return null; Item rootContainer = Container; - while (rootContainer.Container != null) { rootContainer = rootContainer.Container; @@ -783,12 +827,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,14 +889,29 @@ 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(); } 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; @@ -997,9 +1050,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++; @@ -1192,6 +1246,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; @@ -1268,29 +1323,75 @@ 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)) { + 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; } + int initialWritePos = msg.LengthBits; + 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: + if (extraData.Length < 2 || !(extraData[1] is int)) + { + errorMsg = "Failed to write a component state event for the item \"" + Name + "\" - component index not given."; + break; + } int componentIndex = (int)extraData[1]; - msg.WriteRangedInteger(0, components.Count-1, componentIndex); - + if (componentIndex < 0 || componentIndex >= components.Count) + { + 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)) + { + 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); + 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 - //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++) @@ -1305,9 +1406,30 @@ namespace Barotrauma msg.Write(targetID); break; case NetEntityEvent.Type.ChangeProperty: - WritePropertyChange(msg, extraData); + try + { + WritePropertyChange(msg, extraData, false); + } + 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.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); + } + } public void ServerRead(ClientNetObject type, NetBuffer msg, Client c) @@ -1322,7 +1444,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; @@ -1347,7 +1470,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)) { @@ -1364,14 +1487,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) { @@ -1430,16 +1553,24 @@ 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"); } } + else + { + throw new ArgumentException("Failed to write propery value - property \"" + (property == null ? "null" : property.Name) + "\" is not serializable."); + } } - 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; @@ -1487,6 +1618,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; @@ -1523,10 +1672,35 @@ 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; + 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) @@ -1554,10 +1728,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 @@ -1571,6 +1747,7 @@ namespace Barotrauma } } + byte teamID = msg.ReadByte(); bool tagsChanged = msg.ReadBoolean(); string tags = ""; if (tagsChanged) @@ -1596,23 +1773,29 @@ 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; } } } - 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) @@ -1766,16 +1949,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/Items/ItemInventory.cs b/Barotrauma/BarotraumaShared/Source/Items/ItemInventory.cs index 728eb9390..71e12115b 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,28 @@ 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) + { + syncItemsDelay = 1.0f; + GameMain.Client.CreateEntityEvent(Owner as IClientSerializable, new object[] { NetEntityEvent.Type.InventoryState, componentIndex }); + } +#endif + } + public override void RemoveItem(Item item) { base.RemoveItem(item); 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 { diff --git a/Barotrauma/BarotraumaShared/Source/Map/Entity.cs b/Barotrauma/BarotraumaShared/Source/Map/Entity.cs index ac0046d75..3297e0831 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 { @@ -19,12 +20,19 @@ namespace Barotrauma protected AITarget aiTarget; + private bool idFreed; + public virtual bool Removed { get; private set; } + public bool IdFreed + { + get { return idFreed; } + } + public ushort ID { get @@ -51,7 +59,8 @@ namespace Barotrauma DebugConsole.Log("The id of " + this + " is now " + value); } - id = value; + id = value; + idFreed = false; dictionary.Add(id, this); } } @@ -126,22 +135,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 + ")\n" + exception.StackTrace); } } + 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 +167,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 +189,45 @@ 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() + /// + /// Removes the entity from the entity dictionary and frees up the ID it was using. + /// + public void FreeID() { - 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.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); + DebugConsole.Log("Entity ID mismatch in entity dictionary. Entity " + existingEntity + " had the ID " + ID + " (expecting " + ToString() + ")"); + GameAnalyticsManager.AddErrorEventOnce("Entity.FreeID: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); @@ -202,6 +235,12 @@ namespace Barotrauma } dictionary.Remove(ID); + idFreed = true; + } + + public virtual void Remove() + { + if (!idFreed) FreeID(); Removed = true; } 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; diff --git a/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs b/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs index a11ce2b95..3280a3f51 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; } @@ -182,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/Gap.cs b/Barotrauma/BarotraumaShared/Source/Map/Gap.cs index cea75437a..140d57fae 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 { @@ -126,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(); } @@ -191,6 +208,8 @@ namespace Barotrauma { flowForce = Vector2.Zero; + outsideColliderRaycastTimer -= deltaTime; + if (open == 0.0f || linkedTo.Count == 0) { lerpedFlowForce = Vector2.Zero; @@ -214,7 +233,7 @@ namespace Barotrauma EmitParticles(deltaTime); - if (flowTargetHull != null && lerpedFlowForce != Vector2.Zero) + if (flowTargetHull != null && lerpedFlowForce.LengthSquared() > 0.0001f) { foreach (Character character in Character.CharacterList) { @@ -238,11 +257,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); } } @@ -505,7 +538,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 || linkedTo.Count == 0 || !(linkedTo[0] is Hull)) 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() @@ -586,7 +663,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/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/Map/Levels/BackgroundSpriteManager.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/BackgroundSpriteManager.cs index a5b52b7c8..8228bceff 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); @@ -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(); @@ -241,6 +243,9 @@ 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); }); if (cells.Any()) { @@ -305,10 +310,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) diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/CaveGenerator.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/CaveGenerator.cs index 790e08eff..3b2086d0c 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++) { @@ -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, Level level, 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,33 +411,35 @@ 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 //(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]); - FixtureFactory.AttachPolygon(bodyVertices, 5.0f, cellBody); + 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); + } } - 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/Level.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs index 1ab021186..35ac625d3 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) @@ -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; @@ -145,7 +146,11 @@ namespace Barotrauma private set; } - + public bool Mirrored + { + get; + private set; + } public LevelGenerationParams GenerationParams { @@ -170,7 +175,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) @@ -192,8 +199,10 @@ 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) { + Mirrored = mirror; + if (backgroundSpriteManager == null) { var files = GameMain.SelectedPackage.GetFilesOfType(ContentType.BackgroundSpritePrefabs); @@ -303,9 +312,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 +341,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 +355,10 @@ 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); } startPosition.X = pathCells[0].Center.X; @@ -377,20 +386,19 @@ 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 //---------------------------------------------------------------------------------- - + 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)))); - + foreach (VoronoiCell cell in cells) { if (cell.Center.Y < borders.Height / 2) continue; @@ -400,7 +408,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 +425,62 @@ namespace Barotrauma } } + //---------------------------------------------------------------------------------- + // mirror if needed + //---------------------------------------------------------------------------------- + + if (mirror) + { + HashSet mirroredEdges = new HashSet(); + HashSet mirroredSites = new HashSet(); + List allCells = new List(cells); + allCells.AddRange(pathCells); + foreach (VoronoiCell cell in allCells) + { + 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)) + { + //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); + } + 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,10 +499,9 @@ namespace Barotrauma ruins = new List(); for (int i = 0; i < generationParams.RuinCount; i++) { - GenerateRuin(mainPath); + GenerateRuin(mainPath, mirror); } - - + //---------------------------------------------------------------------------------- // generate the bodies and rendered triangles of the cells //---------------------------------------------------------------------------------- @@ -449,11 +512,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, @@ -466,7 +529,7 @@ namespace Barotrauma bodies.Add(TopBarrier); - GenerateSeaFloor(); + GenerateSeaFloor(mirror); backgroundSpriteManager.PlaceSprites(this, generationParams.BackgroundSpriteAmount); #if CLIENT @@ -545,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++) @@ -553,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); @@ -571,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); @@ -665,7 +725,7 @@ namespace Barotrauma return newCells; } - private void GenerateSeaFloor() + private void GenerateSeaFloor(bool mirror) { BottomPos = generationParams.SeaFloorDepth; SeaFloorTopPos = BottomPos; @@ -688,19 +748,27 @@ 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) }; + 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)), @@ -809,12 +877,13 @@ 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; + + int cellIndex = Rand.Int(cells.Count, Rand.RandSync.Server); + 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) @@ -824,26 +893,34 @@ namespace Barotrauma 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; 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; - if (iter > 10000) break; } @@ -858,10 +935,10 @@ namespace Barotrauma closestDist = dist; } } - - var ruin = new Ruin(closestPathCell, cells, new Rectangle(MathUtils.ToPoint(ruinPos - ruinSize * 0.5f), MathUtils.ToPoint(ruinSize))); + + 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)); for (int i = 0; i < 4; i++) { @@ -919,7 +996,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; } @@ -1011,15 +1088,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/LevelWall.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelWall.cs index 059301862..306a7ad40 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelWall.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelWall.cs @@ -18,10 +18,9 @@ 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++) { Vector2[] vertices = new Vector2[4]; @@ -31,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,14 +47,17 @@ namespace Barotrauma cells.Add(wallCell); } - List triangles; - bodies = CaveGenerator.GeneratePolygons(cells, out triangles, false); + 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); SetBodyVertices(bodyVertices.ToArray(), color); - SetWallVertices(CaveGenerator.GenerateWallShapes(cells), color); + SetWallVertices(CaveGenerator.GenerateWallShapes(cells, level), color); #endif } diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/Ruins/RuinGenerator.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/Ruins/RuinGenerator.cs index 1f7e758dd..cb5d15e0e 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,23 +131,40 @@ 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; } + + 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; + 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); @@ -183,7 +201,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 +212,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(); @@ -243,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; @@ -289,13 +312,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) { @@ -317,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) @@ -334,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); } @@ -345,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; @@ -389,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; @@ -401,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); @@ -423,6 +463,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; @@ -451,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/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() + ")"; + } } // للترتيب diff --git a/Barotrauma/BarotraumaShared/Source/Map/LinkedSubmarine.cs b/Barotrauma/BarotraumaShared/Source/Map/LinkedSubmarine.cs index 75a9b39f9..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(); @@ -261,22 +266,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/Map/Map.cs b/Barotrauma/BarotraumaShared/Source/Map/Map/Map.cs index eb7eb91af..f6272f365 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,16 @@ 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; + 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)); + mapElement.Add(connectionElement); + } - mapElement.Add(new XAttribute("passed", string.Join(",", passedConnections))); element.Add(mapElement); } @@ -467,7 +472,7 @@ namespace Barotrauma public bool Passed; - private int missionsCompleted; + public int MissionsCompleted; private Mission mission; public Mission Mission @@ -476,12 +481,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 +523,16 @@ namespace Barotrauma public LocationConnection(Location location1, Location location2) { locations = new Location[] { location1, location2 }; + MissionsCompleted = 0; + } - missionsCompleted = 0; + public void CheckMissionCompleted() + { + if (mission != null && mission.Completed) + { + MissionsCompleted++; + mission = null; + } } public Location OtherLocation(Location location) diff --git a/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs index 5f620dd21..a3dc6f4af 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs @@ -20,15 +20,8 @@ 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 protected Rectangle rect; @@ -45,7 +38,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; } } @@ -169,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) { @@ -180,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); } @@ -217,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); } @@ -353,15 +373,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 && rect.Height > 0); @@ -230,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; @@ -687,7 +693,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 +701,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 +726,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 +738,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 +868,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 +911,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 +924,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 +946,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 +962,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 63277d538..3b20ba24f 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]; @@ -71,6 +75,7 @@ namespace Barotrauma private static Vector2 lastPickedPosition; private static float lastPickedFraction; + private static Vector2 lastPickedNormal; private Md5Hash hash; @@ -116,6 +121,11 @@ namespace Barotrauma get { return lastPickedFraction; } } + public static Vector2 LastPickedNormal + { + get { return lastPickedNormal; } + } + public bool Loading { get; @@ -160,7 +170,7 @@ namespace Barotrauma { get { - return subBody.Borders; + return subBody == null ? Rectangle.Empty : subBody.Borders; } } @@ -214,10 +224,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 +254,7 @@ namespace Barotrauma public override string ToString() { - return "Barotrauma.Submarine ("+name+")"; + return "Barotrauma.Submarine (" + name + ")"; } public override bool Removed @@ -286,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) { @@ -296,9 +315,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 @@ -308,7 +337,7 @@ namespace Barotrauma DockedTo = new List(); ID = ushort.MaxValue; - base.Remove(); + FreeID(); } public bool HasTag(SubmarineTag tag) @@ -344,7 +373,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 +412,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 +536,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 +586,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) { @@ -579,30 +608,32 @@ namespace Barotrauma } float closestFraction = 1.0f; + Vector2 closestNormal = Vector2.Zero; Body closestBody = null; GameMain.World.RayCast((fixture, point, normal, fraction) => { 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; + closestNormal = normal; + if (fixture.Body != null) closestBody = fixture.Body; } return fraction; } @@ -610,6 +641,7 @@ namespace Barotrauma lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction; lastPickedFraction = closestFraction; + lastPickedNormal = closestNormal; return closestBody; } @@ -622,6 +654,7 @@ namespace Barotrauma { Body closestBody = null; float closestFraction = 1.0f; + Vector2 closestNormal = Vector2.Zero; if (Vector2.Distance(rayStart, rayEnd) < 0.01f) { @@ -650,6 +683,7 @@ namespace Barotrauma { closestBody = fixture.Body; closestFraction = fraction; + closestNormal = normal; } return closestFraction; } @@ -658,6 +692,7 @@ namespace Barotrauma lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction; lastPickedFraction = closestFraction; + lastPickedNormal = closestNormal; return closestBody; } @@ -669,7 +704,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); @@ -684,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) { @@ -729,7 +764,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 +786,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 +813,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 +902,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 +910,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 +965,7 @@ namespace Barotrauma foreach (string path in filePaths) { - SavedSubmarines.Add(new Submarine(path)); + savedSubmarines.Add(new Submarine(path)); } } @@ -971,7 +1015,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 +1191,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) @@ -1206,10 +1250,10 @@ 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.MoveWithLevel || e.Submarine != this) continue; + if (e.Submarine != this || !e.ShouldBeSaved) continue; e.Save(element); } } @@ -1285,6 +1329,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); @@ -1294,34 +1347,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)); - } } } diff --git a/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs b/Barotrauma/BarotraumaShared/Source/Map/SubmarineBody.cs index 6d623c73c..29fe901d8 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).UserData = item; + } + else if (radius != 0.0f && width != 0.0f) + { + 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).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).UserData = item; + } + } } - - - + farseerBody.BodyType = BodyType.Dynamic; farseerBody.CollisionCategories = Physics.CollisionWall; farseerBody.CollidesWith = @@ -190,6 +218,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) @@ -209,7 +242,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); @@ -254,7 +287,7 @@ namespace Barotrauma Vector2 totalForce = CalculateBuoyancy(); - if (Body.LinearVelocity.LengthSquared() > 0.000001f) + if (Body.LinearVelocity.LengthSquared() > 0.0001f) { float dragCoefficient = 0.01f; @@ -280,6 +313,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) { @@ -370,27 +404,24 @@ 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)); + Vector2 collisionNormal = Vector2.Normalize(ConvertUnits.ToDisplayUnits(Body.SimPosition) - cell.Center); + if (!MathUtils.IsValid(collisionNormal)) collisionNormal = Rand.Vector(1.0f); + HandleLevelCollision(contact, collisionNormal); 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 +431,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; @@ -446,7 +476,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); @@ -481,13 +513,11 @@ 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.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) @@ -503,9 +533,23 @@ namespace Barotrauma avgContactNormal /= levelContacts.Count; float contactDot = Vector2.Dot(Body.LinearVelocity, -avgContactNormal); - if (contactDot > 0.0f) + if (contactDot > 0.001f) { - 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; @@ -603,8 +647,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) @@ -623,7 +667,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/Map/WayPoint.cs b/Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs index 13fbcd339..46d9a7d05 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), @@ -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; } 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/Client.cs b/Barotrauma/BarotraumaShared/Source/Networking/Client.cs index 80965f69a..c77cd7ca2 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; } @@ -217,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/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/EntitySpawner.cs b/Barotrauma/BarotraumaShared/Source/Networking/EntitySpawner.cs index 961abccf2..4dc229269 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,30 +36,28 @@ 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() { Item spawnedItem = null; - 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); } - return spawnedItem; } } @@ -158,6 +156,10 @@ namespace Barotrauma if (spawnedEntity != null) { CreateNetworkEvent(spawnedEntity, false); + if (spawnedEntity is Item) + { + ((Item)spawnedEntity).Condition = ((ItemSpawnInfo)entitySpawnInfo).Condition; + } } } 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/GameServer.cs b/Barotrauma/BarotraumaShared/Source/Networking/GameServer.cs index 6dbd84ce9..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(); @@ -209,7 +202,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; } @@ -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); @@ -368,6 +361,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 && (!AllowSpectating || !owner.SpectateOnly)) + { + SetClientCharacter(owner, character); + } + } + bool isCrewDead = connectedClients.All(c => c.Character == null || c.Character.IsDead || c.Character.IsUnconscious) && (myCharacter == null || myCharacter.IsDead || myCharacter.IsUnconscious); @@ -504,7 +519,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); } } @@ -847,11 +865,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); @@ -939,8 +956,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(); @@ -1243,7 +1261,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 { @@ -1306,10 +1327,12 @@ 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; + spawnedCharacter.OwnerClientIP = teamClients[i].Connection.RemoteEndPoint.Address.ToString(); + spawnedCharacter.OwnerClientName = teamClients[i].Name; #if CLIENT GameMain.GameSession.CrewManager.AddCharacter(spawnedCharacter); @@ -1320,8 +1343,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; @@ -1334,13 +1357,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); @@ -1370,7 +1390,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); @@ -1429,6 +1449,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); @@ -1474,7 +1496,7 @@ namespace Barotrauma.Networking GameMain.NetLobbyScreen.LastUpdateID++; } - if (SaveServerLogs) log.Save(); + if (SaveServerLogs) ServerLog.Save(); Character.Controlled = null; @@ -1583,24 +1605,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; @@ -1627,8 +1632,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; @@ -1939,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; @@ -2023,6 +2028,8 @@ namespace Barotrauma.Networking if (client.Character != null) { client.Character.IsRemotePlayer = false; + client.Character.OwnerClientIP = null; + client.Character.OwnerClientName = null; } if (newCharacter == null) @@ -2032,16 +2039,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; @@ -2243,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() @@ -2264,13 +2282,10 @@ namespace Barotrauma.Networking if (SaveServerLogs) { Log("Shutting down the server...", ServerLog.MessageType.ServerMessage); - log.Save(); - } - - if (GameSettings.SendUserStatistics) - { - GameAnalyticsSDK.Net.GameAnalytics.AddDesignEvent("GameServer:ShutDown"); + ServerLog.Save(); } + + GameAnalyticsManager.AddDesignEvent("GameServer:ShutDown"); server.Shutdown("The server has been shut down"); } } 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 63af136a5..4a18ed99c 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; } } @@ -177,6 +177,13 @@ namespace Barotrauma.Networking set; } + [Serialize(true, true)] + public bool AllowDisguises + { + get; + set; + } + public YesNoMaybe TraitorsEnabled { get; @@ -219,6 +226,13 @@ namespace Barotrauma.Networking private set; } + [Serialize(30.0f, true)] + public float KillDisconnectedTime + { + get; + private set; + } + [Serialize(true, true)] public bool TraitorUseRatio { @@ -274,6 +288,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")); @@ -295,6 +318,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 @@ -354,6 +379,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[] { "32-33", "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/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, diff --git a/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEventManager.cs b/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEventManager.cs index fad368b6e..5c1d1d2c7 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,10 +52,20 @@ 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"); + //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 - 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 diff --git a/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/ServerEntityEventManager.cs b/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/ServerEntityEventManager.cs index 5c0986cd4..69413017a 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; @@ -301,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; diff --git a/Barotrauma/BarotraumaShared/Source/Networking/NetworkMember.cs b/Barotrauma/BarotraumaShared/Source/Networking/NetworkMember.cs index c0ecc0749..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(); @@ -162,7 +168,7 @@ namespace Barotrauma.Networking while (chatBox.CountChildren > 20) { - chatBox.RemoveChild(chatBox.children[1]); + chatBox.RemoveChild(chatBox.children[0]); } if (!string.IsNullOrWhiteSpace(message.SenderName)) diff --git a/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs b/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs index f32d873ce..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; } @@ -277,15 +274,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")) @@ -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; } } @@ -353,12 +350,21 @@ 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) { - 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; @@ -374,6 +380,8 @@ namespace Barotrauma.Networking shuttleTransportTimer = maxTransportTime; shuttleReturnTimer = maxTransportTime; + if (respawnShuttle == null) return; + foreach (Item item in Item.ItemList) { if (item.Submarine != respawnShuttle) continue; @@ -405,7 +413,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 +435,7 @@ namespace Barotrauma.Networking foreach (Item item in c.Inventory.Items) { if (item == null) continue; - Entity.Spawner.AddToRemoveQueue(item); + Spawner.AddToRemoveQueue(item); } } @@ -448,7 +456,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) @@ -508,37 +516,41 @@ 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 } #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 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; } } } diff --git a/Barotrauma/BarotraumaShared/Source/Networking/Voting.cs b/Barotrauma/BarotraumaShared/Source/Networking/Voting.cs index c78a1bede..3d2b98a25 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); @@ -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); diff --git a/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs b/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs index 7ce46113a..3829c257e 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; } @@ -185,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); + } } } } @@ -223,13 +227,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,6 +372,50 @@ namespace Barotrauma this.radius = radius; } + public bool IsValidValue(float value, string valueName, float? minValue = null, float? maxValue = null) + { + 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 + + "), value: " + value + "\n" + Environment.StackTrace; + + if (GameSettings.VerboseLogging) DebugConsole.ThrowError(errorMsg); + GameAnalyticsManager.AddErrorEventOnce( + "PhysicsBody.SetPosition:InvalidPosition" + userData, + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + errorMsg); + return false; + } + return true; + } + + private bool IsValidValue(Vector2 value, string valueName, float? minValue = null, float? maxValue = null) + { + 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 + + "), value: " + value + "\n" + Environment.StackTrace; + + if (GameSettings.VerboseLogging) DebugConsole.ThrowError(errorMsg); + GameAnalyticsManager.AddErrorEventOnce( + "PhysicsBody.SetPosition:InvalidPosition" + userData, + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + errorMsg); + return false; + } + return true; + } + public void ResetDynamics() { body.ResetDynamics(); @@ -367,6 +423,7 @@ namespace Barotrauma public void ApplyLinearImpulse(Vector2 impulse) { + if (!IsValidValue(impulse, "impulse", -1e10f, 1e10f)) return; body.ApplyLinearImpulse(impulse); } @@ -375,6 +432,9 @@ namespace Barotrauma /// public void ApplyLinearImpulse(Vector2 impulse, float maxVelocity) { + if (!IsValidValue(impulse, "impulse", -1e10f, 1e10f)) return; + if (!IsValidValue(maxVelocity, "max velocity")) return; + float currSpeed = body.LinearVelocity.Length(); Vector2 velocityAddition = impulse / Mass; Vector2 newVelocity = body.LinearVelocity + velocityAddition; @@ -385,47 +445,63 @@ namespace Barotrauma public void ApplyLinearImpulse(Vector2 impulse, Vector2 point) { + if (!IsValidValue(impulse, "impulse", -1e10f, 1e10f)) return; body.ApplyLinearImpulse(impulse, point); } public void ApplyForce(Vector2 force) { + if (!IsValidValue(force, "force", -1e10f, 1e10f)) return; body.ApplyForce(force); } public void ApplyForce(Vector2 force, Vector2 point) { + if (!IsValidValue(force, "force", -1e10f, 1e10f)) return; + if (!IsValidValue(point, "point")) return; body.ApplyForce(force, point); } public void ApplyTorque(float torque) { + if (!IsValidValue(torque, "torque")) return; 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 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 false; + if (!IsValidValue(rotation, "rotation")) return false; + body.SetTransformIgnoreContacts(ref simPosition, rotation); SetPrevTransform(simPosition, rotation); + return true; } - public void SetPrevTransform(Vector2 position, float rotation) + public void SetPrevTransform(Vector2 simPosition, float rotation) { - prevPosition = position; + if (!IsValidValue(simPosition, "position", -1e10f, 1e10f)) return; + if (!IsValidValue(rotation, "rotation")) return; + + prevPosition = simPosition; prevRotation = rotation; } @@ -440,16 +516,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", -1e10f, 1e10f)) 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 +553,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 +638,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); } } 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 { 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 7f6db5d43..16c1079d1 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. @@ -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); @@ -479,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; } @@ -487,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); } @@ -505,6 +515,8 @@ namespace Barotrauma public static void StopAll() { CoroutineManager.StopCoroutines("statuseffect"); + DelayedEffect.DelayList.Clear(); + DurationList.Clear(); } public void AddTag(string tag) 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 diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 3c33067bd..02c5daaaa 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,189 @@ +--------------------------------------------------------------------------------------------------------- +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 +--------------------------------------------------------------------------------------------------------- + +- 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 +--------------------------------------------------------------------------------------------------------- + +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 +--------------------------------------------------------------------------------------------------------- + +- 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 +--------------------------------------------------------------------------------------------------------- + +- 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 +--------------------------------------------------------------------------------------------------------- + +- 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 +--------------------------------------------------------------------------------------------------------- + +- Fixed crashes when trying to load submarines in the sub editor. + +--------------------------------------------------------------------------------------------------------- +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 --------------------------------------------------------------------------------------------------------- diff --git a/Barotrauma/BarotraumaShared/serversettings.xml b/Barotrauma/BarotraumaShared/serversettings.xml index c42cf2a7b..7bcf40f11 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" @@ -31,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" />