From de4726b70ae651f3cfb8e7d7888948f4df180cad Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 25 Oct 2023 20:17:44 +0300 Subject: [PATCH 1/3] Update bug_report.yml --- .github/ISSUE_TEMPLATE/bug_report.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 070903c0c..3293ce4d3 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -74,6 +74,7 @@ body: description: Which version of the game did the bug happen in? You can see the current version number in the bottom left corner of your screen in the main menu. options: - v1.1.18.1 (Treacherous Tides) + - v1.1.19.0 (Unstable) - Other validations: required: true From b968376247bc8957d41fbedf1da806485db9d0f1 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 30 Oct 2023 17:12:28 +0200 Subject: [PATCH 2/3] Update bug_report.yml --- .github/ISSUE_TEMPLATE/bug_report.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 3293ce4d3..a9673f38d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -73,8 +73,7 @@ body: label: Version description: Which version of the game did the bug happen in? You can see the current version number in the bottom left corner of your screen in the main menu. options: - - v1.1.18.1 (Treacherous Tides) - - v1.1.19.0 (Unstable) + - v1.1.19.3 (Treacherous Tides Hotfix 2) - Other validations: required: true From a8f9c97ddacca5f09a3e83bbaba9bb0d6b15c7be Mon Sep 17 00:00:00 2001 From: Regalis11 Date: Mon, 30 Oct 2023 17:38:29 +0200 Subject: [PATCH 3/3] v1.1.19.3 (Treacherous Tides Hotfix 2) --- .../ClientSource/Characters/Character.cs | 5 +- .../CircuitBox/CircuitBoxConnection.cs | 13 +- .../ClientSource/GameSession/RoundSummary.cs | 2 +- .../Items/Components/Signal/CircuitBox.cs | 2 +- .../ClientSource/Items/Inventory.cs | 22 +++- .../BarotraumaClient/ClientSource/Map/Hull.cs | 24 +++- .../BarotraumaClient/ClientSource/Program.cs | 3 + .../BarotraumaClient/LinuxClient.csproj | 2 +- Barotrauma/BarotraumaClient/MacClient.csproj | 2 +- .../BarotraumaClient/WindowsClient.csproj | 2 +- .../BarotraumaServer/LinuxServer.csproj | 2 +- Barotrauma/BarotraumaServer/MacServer.csproj | 2 +- .../Items/Components/Signal/CircuitBox.cs | 6 +- .../ServerSource/Networking/ChatMessage.cs | 115 ++++++++++-------- .../ServerSource/Networking/GameServer.cs | 12 +- .../ServerSource/Networking/Voting.cs | 12 +- .../BarotraumaServer/WindowsServer.csproj | 2 +- .../Characters/AI/HumanAIController.cs | 4 - .../SharedSource/Characters/Character.cs | 24 ++-- .../Characters/Health/CharacterHealth.cs | 13 +- .../Characters/Params/CharacterParams.cs | 3 + .../Events/EventActions/TagAction.cs | 65 ++++++---- .../GameSession/GameModes/CampaignMode.cs | 29 ++++- .../Items/Components/ItemComponent.cs | 6 + .../Items/Components/Machines/Fabricator.cs | 2 +- .../SharedSource/Items/Components/Planter.cs | 3 + .../Items/Components/Signal/CircuitBox.cs | 46 +++++-- .../SharedSource/Items/Inventory.cs | 8 +- .../SharedSource/Items/Item.cs | 94 +++++++++++--- .../SharedSource/Items/ItemInventory.cs | 4 +- .../SharedSource/Items/ItemPrefab.cs | 32 +++-- .../SharedSource/Map/Explosion.cs | 64 +++++++--- .../BarotraumaShared/SharedSource/Map/Gap.cs | 36 +++--- .../BarotraumaShared/SharedSource/Map/Hull.cs | 6 +- .../SharedSource/Map/Levels/LevelData.cs | 2 +- .../SharedSource/Map/Structure.cs | 12 +- .../StatusEffects/StatusEffect.cs | 47 +++---- Barotrauma/BarotraumaShared/changelog.txt | 45 ++++--- 38 files changed, 517 insertions(+), 256 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs index 115a95e77..a223fe878 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs @@ -336,7 +336,10 @@ namespace Barotrauma float pressure = AnimController.CurrentHull == null ? 100.0f : AnimController.CurrentHull.LethalPressure; if (pressure > 0.0f) { - float zoomInEffectStrength = MathHelper.Clamp(pressure / 100.0f, 0.1f, 1.0f); + //lerp in during the 1st second of the pressure timer so the zoom doesn't + //"flicker" in and out if the pressure fluctuates around the minimum threshold + float timerMultiplier = (PressureTimer / 100.0f); + float zoomInEffectStrength = MathHelper.Clamp(pressure / 100.0f * timerMultiplier, 0.0f, 1.0f); cam.Zoom = MathHelper.Lerp(cam.Zoom, cam.DefaultZoom + (Math.Max(pressure, 10) / 150.0f) * Rand.Range(0.9f, 1.1f), zoomInEffectStrength); diff --git a/Barotrauma/BarotraumaClient/ClientSource/CircuitBox/CircuitBoxConnection.cs b/Barotrauma/BarotraumaClient/ClientSource/CircuitBox/CircuitBoxConnection.cs index 7fea1d1f1..ea89c9f57 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/CircuitBox/CircuitBoxConnection.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/CircuitBox/CircuitBoxConnection.cs @@ -8,24 +8,25 @@ namespace Barotrauma { internal abstract partial class CircuitBoxConnection { - public string Name => Label.Value.Value; + public string Name => Connection.Name; + public CircuitBoxLabel Label { get; private set; } private Sprite? knobSprite, screwSprite, connectorSprite; - private static int padding => GUI.IntScale(8); + private static int Padding => GUI.IntScale(8); private Option tooltip = Option.None; private partial void InitProjSpecific(CircuitBox circuitBox) { - Label = new CircuitBoxLabel(Connection.Name, GUIStyle.SubHeadingFont); + Label = new CircuitBoxLabel(Connection.DisplayName, GUIStyle.SubHeadingFont); knobSprite = circuitBox.ConnectionSprite; screwSprite = circuitBox.ConnectionScrewSprite; connectorSprite = circuitBox.WireConnectorSprite; - Length = Rect.Width + padding + Label.Size.X; + Length = Rect.Width + Padding + Label.Size.X; } public void Draw(SpriteBatch spriteBatch, Vector2 drawPos, Vector2 parentPos, Color color) @@ -41,11 +42,11 @@ namespace Barotrauma float xPos; if (IsOutput) { - xPos = drawRect.Left - padding - Label.Size.X; + xPos = drawRect.Left - Padding - Label.Size.X; } else { - xPos = drawRect.Right + padding; + xPos = drawRect.Right + Padding; } Vector2 stringPos = new Vector2(xPos, drawRect.Center.Y - Label.Size.Y / 2f); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs index 96fb2522a..33f02d0fe 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs @@ -98,7 +98,7 @@ namespace Barotrauma if (gameSession.Missions.Any(m => m is CombatMission)) { crewHeader.Text = CombatMission.GetTeamName(CharacterTeamType.Team1); - GUIFrame crewFrame2 = new GUIFrame(new RectTransform(new Vector2(0.35f, 0.45f), background.RectTransform, Anchor.TopCenter, minSize: new Point(minWidth, minHeight))); + GUIFrame crewFrame2 = new GUIFrame(new RectTransform(crewFrame.RectTransform.RelativeSize, background.RectTransform, Anchor.TopCenter, minSize: new Point(minWidth, minHeight))); rightPanels.Add(crewFrame2); GUIFrame crewFrameInner2 = new GUIFrame(new RectTransform(new Point(crewFrame2.Rect.Width - padding * 2, crewFrame2.Rect.Height - padding * 2), crewFrame2.RectTransform, Anchor.Center), style: "InnerFrame"); var crewContent2 = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), crewFrameInner2.RectTransform, Anchor.Center)) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CircuitBox.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CircuitBox.cs index 7dafcf617..3f466be5c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CircuitBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CircuitBox.cs @@ -269,7 +269,7 @@ namespace Barotrauma.Items.Components resource = ItemPrefab.Prefabs[Tags.FPGACircuit]; } - AddComponentInternal(ICircuitBoxIdentifiable.FindFreeID(Components), prefab, resource, pos, static delegate { }); + AddComponentInternal(ICircuitBoxIdentifiable.FindFreeID(Components), prefab, resource, pos, Character.Controlled, onItemSpawned: null); return; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs index ac8f2e935..ecb897421 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs @@ -1505,14 +1505,28 @@ namespace Barotrauma int stackAmount = DraggingItems.Count; if (selectedSlot?.ParentInventory != null) { - stackAmount = Math.Min( - stackAmount, - selectedSlot.ParentInventory.HowManyCanBePut(draggedItem.Prefab, selectedSlot.SlotIndex, draggedItem.Condition)); + if (selectedSlot.Item?.OwnInventory != null) + { + int maxAmountPerSlot = 0; + for (int i = 0; i < SelectedSlot.Item.OwnInventory.Capacity; i++) + { + maxAmountPerSlot = Math.Max( + maxAmountPerSlot, + selectedSlot.Item.OwnInventory.HowManyCanBePut(draggedItem.Prefab, i, draggedItem.Condition, ignoreItemsInSlot: true)); + } + stackAmount = Math.Min(stackAmount, maxAmountPerSlot); + } + else + { + stackAmount = Math.Min( + stackAmount, + selectedSlot.ParentInventory.HowManyCanBePut(draggedItem.Prefab, selectedSlot.SlotIndex, draggedItem.Condition, ignoreItemsInSlot: true)); + } } Vector2 stackCountPos = itemPos + Vector2.One * iconSize * 0.25f; string stackCountText = "x" + stackAmount; GUIStyle.SmallFont.DrawString(spriteBatch, stackCountText, stackCountPos + Vector2.One, Color.Black); - GUIStyle.SmallFont.DrawString(spriteBatch, stackCountText, stackCountPos, GUIStyle.TextColorBright); + GUIStyle.SmallFont.DrawString(spriteBatch, stackCountText, stackCountPos, GUIStyle.TextColorBright); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs index 5eb9bcd95..25f13500c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs @@ -300,13 +300,27 @@ namespace Barotrauma " - Oxygen: " + ((int)OxygenPercentage), new Vector2(drawRect.X + 5, -drawRect.Y + 5), Color.White); GUIStyle.SmallFont.DrawString(spriteBatch, waterVolume + " / " + Volume, new Vector2(drawRect.X + 5, -drawRect.Y + 20), Color.White); - GUI.DrawRectangle(spriteBatch, new Rectangle(drawRect.Center.X, -drawRect.Y + drawRect.Height / 2, 10, (int)(100 * Math.Min(waterVolume / Volume, 1.0f))), Color.Cyan, true); - if (WaterVolume > Volume) + if (WaterVolume > 0) { - float maxExcessWater = Volume * MaxCompress; - GUI.DrawRectangle(spriteBatch, new Rectangle(drawRect.Center.X, -drawRect.Y + drawRect.Height / 2, 10, (int)(100 * (waterVolume - Volume) / maxExcessWater)), GUIStyle.Red, true); + drawProgressBar(50, new Point(0, 0), Math.Min(waterVolume / Volume, 1.0f), Color.Cyan); + if (WaterVolume > Volume) + { + float maxExcessWater = Volume * MaxCompress; + drawProgressBar(50, new Point(0, 0), (waterVolume - Volume) / maxExcessWater, GUIStyle.Red); + } + } + if (lethalPressure > 0) + { + drawProgressBar(50, new Point(20, 0), lethalPressure / 100.0f, Color.Red); + } + + void drawProgressBar(int height, Point offset, float fillAmount, Color color) + { + GUI.DrawRectangle(spriteBatch, new Rectangle(drawRect.Center.X - 2 + offset.X, -drawRect.Y - 2 + drawRect.Height / 2 + offset.Y, 14, height+4), Color.Black * 0.8f, depth: 0.01f, isFilled: true); + + int barHeight = (int)(fillAmount * height); + GUI.DrawRectangle(spriteBatch, new Rectangle(drawRect.Center.X + offset.X, -drawRect.Y + drawRect.Height / 2 + height - barHeight + offset.Y, 10, barHeight), color, isFilled: true); } - GUI.DrawRectangle(spriteBatch, new Rectangle(drawRect.Center.X, -drawRect.Y + drawRect.Height / 2, 10, 100), Color.Black); foreach (FireSource fs in FireSources) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Program.cs b/Barotrauma/BarotraumaClient/ClientSource/Program.cs index a3d44b5d6..4dd29adfa 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Program.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Program.cs @@ -261,6 +261,9 @@ namespace Barotrauma { crashHeader += " " + exception.TargetSite.ToString(); } + //log the message separately, so the same error messages get grouped as the same error in GA + //(the full crash report tends to always have some differences between clients, so they get displayed separately) + GameAnalyticsManager.AddErrorEvent(GameAnalyticsManager.ErrorSeverity.Critical, crashHeader); GameAnalyticsManager.AddErrorEvent(GameAnalyticsManager.ErrorSeverity.Critical, crashHeader + "\n\n" + sb.ToString()); GameAnalyticsManager.ShutDown(); } diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index 9a1fe727a..a5b67175d 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 1.1.18.1 + 1.1.19.3 Copyright © FakeFish 2018-2023 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index 7c9514990..12fa0df4c 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 1.1.18.1 + 1.1.19.3 Copyright © FakeFish 2018-2023 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index a388bc1f5..a469892ba 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 1.1.18.1 + 1.1.19.3 Copyright © FakeFish 2018-2023 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index 7fd3c7774..7b072552b 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 1.1.18.1 + 1.1.19.3 Copyright © FakeFish 2018-2023 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index 1f5c8ac99..16781acf4 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 1.1.18.1 + 1.1.19.3 Copyright © FakeFish 2018-2023 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/CircuitBox.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/CircuitBox.cs index 010111c87..c961063c5 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/CircuitBox.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/CircuitBox.cs @@ -1,11 +1,11 @@ #nullable enable +using Barotrauma.Networking; +using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using Barotrauma.Networking; -using Microsoft.Xna.Framework; namespace Barotrauma.Items.Components { @@ -138,7 +138,7 @@ namespace Barotrauma.Items.Components return; } - bool result = AddComponentInternal(id, prefab, resource.Prefab, data.Position, it => + bool result = AddComponentInternal(id, prefab, resource.Prefab, data.Position, c.Character, it => { CreateServerEvent(new CircuitBoxServerCreateComponentEvent(it.ID, resource.Prefab.UintIdentifier, id, data.Position)); }); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs index e9216ab7b..93151a357 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs @@ -80,58 +80,10 @@ namespace Barotrauma.Networking { c.LastSentChatMessages.RemoveRange(0, c.LastSentChatMessages.Count - 10); } - - float similarity = 0.0f; - for (int i = 0; i < c.LastSentChatMessages.Count; i++) - { - float closeFactor = 1.0f / (c.LastSentChatMessages.Count - i); - - if (string.IsNullOrEmpty(txt)) - { - similarity += closeFactor; - } - else - { - int levenshteinDist = ToolBox.LevenshteinDistance(txt, c.LastSentChatMessages[i]); - similarity += Math.Max((txt.Length - levenshteinDist) / (float)txt.Length * closeFactor, 0.0f); - } - } - //order/report messages can be sent a little faster than normal messages without triggering the spam filter - if (orderMsg != null) - { - similarity *= 0.25f; - } - - bool isSpamExempt = RateLimiter.IsExempt(c); - - if (similarity + c.ChatSpamSpeed > 5.0f && !isSpamExempt) - { - GameMain.Server.KarmaManager.OnSpamFilterTriggered(c); - - c.ChatSpamCount++; - if (c.ChatSpamCount > 3) - { - //kick for spamming too much - GameMain.Server.KickClient(c, TextManager.Get("SpamFilterKicked").Value); - } - else - { - ChatMessage denyMsg = Create("", TextManager.Get("SpamFilterBlocked").Value, ChatMessageType.Server, null); - c.ChatSpamTimer = 10.0f; - GameMain.Server.SendDirectChatMessage(denyMsg, c); - } - return; - } - - c.ChatSpamSpeed += similarity + 0.5f; - - if (c.ChatSpamTimer > 0.0f && !isSpamExempt) - { - ChatMessage denyMsg = Create("", TextManager.Get("SpamFilterBlocked").Value, ChatMessageType.Server, null); - c.ChatSpamTimer = 10.0f; - GameMain.Server.SendDirectChatMessage(denyMsg, c); - return; - } + //order/report messages can be sent a little faster than normal messages without triggering the spam filter; + float similarityMultiplier = orderMsg != null ? 0.25f : 1.0f; + HandleSpamFilter(c, txt, out bool flaggedAsSpam, similarityMultiplier); + if (flaggedAsSpam) { return; } if (type == ChatMessageType.Order) { @@ -177,6 +129,65 @@ namespace Barotrauma.Networking } } + /// + /// Increase the client's chat spam speed and check whether the spam filter should kick in + /// + public static void HandleSpamFilter(Client c, string messageText, out bool flaggedAsSpam, float similarityMultiplier = 1.0f) + { + float similarity = 0.0f; + for (int i = 0; i < c.LastSentChatMessages.Count; i++) + { + float closeFactor = 1.0f / (c.LastSentChatMessages.Count - i); + + if (string.IsNullOrEmpty(messageText)) + { + similarity += closeFactor; + } + else + { + int levenshteinDist = ToolBox.LevenshteinDistance(messageText, c.LastSentChatMessages[i]); + similarity += Math.Max((messageText.Length - levenshteinDist) / (float)messageText.Length * closeFactor, 0.0f); + } + } + + similarity *= similarityMultiplier; + + bool isSpamExempt = RateLimiter.IsExempt(c); + + if (similarity + c.ChatSpamSpeed > 5.0f && !isSpamExempt) + { + GameMain.Server.KarmaManager.OnSpamFilterTriggered(c); + + c.ChatSpamCount++; + if (c.ChatSpamCount > 3) + { + //kick for spamming too much + GameMain.Server.KickClient(c, TextManager.Get("SpamFilterKicked").Value); + } + else + { + ChatMessage denyMsg = Create("", TextManager.Get("SpamFilterBlocked").Value, ChatMessageType.Server, null); + c.ChatSpamTimer = 10.0f; + GameMain.Server.SendDirectChatMessage(denyMsg, c); + } + flaggedAsSpam = true; + return; + } + + c.ChatSpamSpeed += similarity + 0.5f; + + if (c.ChatSpamTimer > 0.0f && !isSpamExempt) + { + ChatMessage denyMsg = Create("", TextManager.Get("SpamFilterBlocked").Value, ChatMessageType.Server, null); + c.ChatSpamTimer = 10.0f; + GameMain.Server.SendDirectChatMessage(denyMsg, c); + flaggedAsSpam = true; + return; + } + + flaggedAsSpam = false; + } + public int EstimateLengthBytesServer(Client c) { int length = 1 + //(byte)ServerNetObject.CHAT_MESSAGE diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index 34f6cd497..c08936ba3 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -369,6 +369,9 @@ namespace Barotrauma.Networking if (!character.ClientDisconnected) { continue; } Client owner = connectedClients.Find(c => (c.Character == null || c.Character == character) && character.IsClientOwner(c)); + bool canOwnerTakeControl = + owner != null && owner.InGame && !owner.NeedsMidRoundSync && + (!ServerSettings.AllowSpectating || !owner.SpectateOnly); if (!character.IsDead) { character.KillDisconnectedTimer += deltaTime; @@ -379,18 +382,19 @@ namespace Barotrauma.Networking character.Kill(CauseOfDeathType.Disconnected, null); continue; } - if (owner != null && owner.InGame && !owner.NeedsMidRoundSync && - (!ServerSettings.AllowSpectating || !owner.SpectateOnly)) + if (canOwnerTakeControl) { SetClientCharacter(owner, character); } } - else if (owner != null && + else if (canOwnerTakeControl && character.CauseOfDeath?.Type == CauseOfDeathType.Disconnected && character.CharacterHealth.VitalityDisregardingDeath > 0) { + //create network event immediately to ensure the character is revived client-side + //before the client gains control of it (normally status events are created periodically) + character.Revive(removeAfflictions: false, createNetworkEvent: true); SetClientCharacter(owner, character); - character.Revive(removeAfflictions: false); } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs index 5afb8d321..81c472caf 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs @@ -345,9 +345,15 @@ namespace Barotrauma sender.SetVote(voteType, client); if (client?.Character != null) { - GameMain.Server.SendChatMessage( - TextManager.GetWithVariable("traitor.blamebutton.dialog", "[name]", client.Character.DisplayName).Value, - ChatMessageType.Radio, senderClient: sender, senderCharacter: sender.Character); + string msg = TextManager.GetWithVariable("traitor.blamebutton.dialog", "[name]", client.Character.DisplayName).Value; + ChatMessage.HandleSpamFilter(sender, msg, out bool flaggedAsSpam); + if (!flaggedAsSpam) + { + GameMain.Server.SendChatMessage( + msg, + ChatMessageType.Radio, senderClient: sender, senderCharacter: sender.Character); + sender.LastSentChatMessages.Add(msg); + } } } break; diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index 72495dd7f..6948b9453 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 1.1.18.1 + 1.1.19.3 Copyright © FakeFish 2018-2023 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs index 43378e91a..698f938b4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs @@ -167,10 +167,6 @@ namespace Barotrauma public HumanAIController(Character c) : base(c) { - if (!c.IsHuman) - { - throw new Exception($"Tried to create a human ai controller for a non-human: {c.SpeciesName}!"); - } insideSteering = new IndoorsSteeringManager(this, true, false); outsideSteering = new SteeringManager(this); objectiveManager = new AIObjectiveManager(c); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index 2aeee3c03..679e76a1b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -1223,19 +1223,15 @@ namespace Barotrauma public static Character Create(CharacterPrefab prefab, Vector2 position, string seed, CharacterInfo characterInfo = null, ushort id = Entity.NullEntityID, bool isRemotePlayer = false, bool hasAi = true, bool createNetworkEvent = true, RagdollParams ragdoll = null, bool spawnInitialItems = true) { Character newCharacter = null; - if (prefab.Identifier != CharacterPrefab.HumanSpeciesName) + if (prefab.Identifier != CharacterPrefab.HumanSpeciesName || hasAi) { var aiCharacter = new AICharacter(prefab, position, seed, characterInfo, id, isRemotePlayer, ragdoll, spawnInitialItems); - var ai = new EnemyAIController(aiCharacter, seed); + + var ai = (prefab.Identifier == CharacterPrefab.HumanSpeciesName || aiCharacter.Params.UseHumanAI) ? + new HumanAIController(aiCharacter) as AIController : + new EnemyAIController(aiCharacter, seed); aiCharacter.SetAI(ai); - newCharacter = aiCharacter; - } - else if (hasAi) - { - var aiCharacter = new AICharacter(prefab, position, seed, characterInfo, id, isRemotePlayer, ragdoll, spawnInitialItems); - var ai = new HumanAIController(aiCharacter); - aiCharacter.SetAI(ai); - newCharacter = aiCharacter; + newCharacter = aiCharacter; } else { @@ -4656,7 +4652,7 @@ namespace Barotrauma } partial void KillProjSpecific(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction, bool log); - public void Revive(bool removeAfflictions = true) + public void Revive(bool removeAfflictions = true, bool createNetworkEvent = false) { if (Removed) { @@ -4705,7 +4701,11 @@ namespace Barotrauma limb.IsSevered = false; } - GameMain.GameSession?.ReviveCharacter(this); + GameMain.GameSession?.ReviveCharacter(this); + if (createNetworkEvent && GameMain.NetworkMember is { IsServer: true }) + { + GameMain.NetworkMember.CreateEntityEvent(this, new CharacterStatusEventData()); + } } public override void Remove() diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs index d7567b91c..d1092d476 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -148,11 +148,6 @@ namespace Barotrauma return minVitality; } return vitality; - - } - private set - { - vitality = value; } } @@ -254,7 +249,7 @@ namespace Barotrauma public CharacterHealth(Character character) { this.Character = character; - Vitality = 100.0f; + vitality = 100.0f; DoesBleed = true; UseHealthWindow = false; @@ -271,7 +266,7 @@ namespace Barotrauma this.Character = character; InitIrremovableAfflictions(); - Vitality = UnmodifiedMaxVitality; + vitality = UnmodifiedMaxVitality; minVitality = character.IsHuman ? -100.0f : 0.0f; @@ -971,7 +966,7 @@ namespace Barotrauma public void CalculateVitality() { - Vitality = MaxVitality; + vitality = MaxVitality; IsParalyzed = false; if (Unkillable || Character.GodMode) { return; } @@ -984,7 +979,7 @@ namespace Barotrauma { vitalityDecrease *= GetVitalityMultiplier(affliction, limbHealth); } - Vitality -= vitalityDecrease; + vitality -= vitalityDecrease; affliction.CalculateDamagePerSecond(vitalityDecrease); if (affliction.Strength >= affliction.Prefab.MaxStrength && diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs index 0fedaedf3..c3042739a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs @@ -50,6 +50,9 @@ namespace Barotrauma [Serialize(false, IsPropertySaveable.Yes, description: "Can the creature live without water or does it die on dry land?"), Editable] public bool NeedsWater { get; set; } + [Serialize(false, IsPropertySaveable.Yes, description: "Note: non-humans with a human AI aren't fully supported. Enabling this on a non-human character may lead to issues.")] + public bool UseHumanAI { get; set; } + [Serialize(false, IsPropertySaveable.Yes, description: "Is this creature an artificial creature, like robot or machine that shouldn't be affected by afflictions that affect only organic creatures? Overrides DoesBleed."), Editable] public bool IsMachine { get; set; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs index e608b7cde..508ff6a43 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs @@ -36,7 +36,19 @@ namespace Barotrauma private bool isFinished = false; - private bool targetNotFound = false; + /// + /// If the action tags some entities directly (not trying to find targets on the fly), + /// we may be able to determine that targets can not be found even if we'd recheck + /// + private bool cantFindTargets = false; + + /// + /// If the TagAction adds a target predicate (a criteria that keeps finding targets on the fly), + /// we must keep checking if targets have been found to determine if the action can continue or not + /// + private bool mustRecheckTargets = false; + + private bool taggingDone = false; public TagAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { @@ -198,6 +210,7 @@ namespace Barotrauma else { ParentEvent.AddTargetPredicate(tag, predicate); + mustRecheckTargets = true; } } @@ -205,7 +218,7 @@ namespace Barotrauma { if (entities.None()) { - targetNotFound = true; + cantFindTargets = true; return; } if (ChoosePercentage > 0.0f) @@ -230,7 +243,7 @@ namespace Barotrauma { if (entities.None()) { - targetNotFound = true; + cantFindTargets = true; return; } @@ -250,7 +263,7 @@ namespace Barotrauma { if (entities.None()) { - targetNotFound = true; + cantFindTargets = true; return; } ParentEvent.AddTarget(tag, entities.GetRandomUnsynced()); @@ -260,26 +273,29 @@ namespace Barotrauma public override void Update(float deltaTime) { - if (isFinished || targetNotFound) { return; } + if (isFinished || cantFindTargets) { return; } - string[] criteriaSplit = Criteria.Split(';'); - - targetNotFound = false; - foreach (string entry in criteriaSplit) + if (!taggingDone) { - string[] kvp = entry.Split(':'); - Identifier key = kvp[0].Trim().ToIdentifier(); - Identifier value = kvp.Length > 1 ? kvp[1].Trim().ToIdentifier() : Identifier.Empty; - if (Taggers.TryGetValue(key, out Action tagger)) + cantFindTargets = false; + string[] criteriaSplit = Criteria.Split(';'); + foreach (string entry in criteriaSplit) { - tagger(value); - } - else - { - string errorMessage = $"Error in TagAction (event \"{ParentEvent.Prefab.Identifier}\") - unrecognized target criteria \"{key}\"."; - DebugConsole.ThrowError(errorMessage); - GameAnalyticsManager.AddErrorEventOnce($"TagAction.Update:InvalidCriteria_{ParentEvent.Prefab.Identifier}_{key}", GameAnalyticsManager.ErrorSeverity.Error, errorMessage); + string[] kvp = entry.Split(':'); + Identifier key = kvp[0].Trim().ToIdentifier(); + Identifier value = kvp.Length > 1 ? kvp[1].Trim().ToIdentifier() : Identifier.Empty; + if (Taggers.TryGetValue(key, out Action tagger)) + { + tagger(value); + } + else + { + string errorMessage = $"Error in TagAction (event \"{ParentEvent.Prefab.Identifier}\") - unrecognized target criteria \"{key}\"."; + DebugConsole.ThrowError(errorMessage); + GameAnalyticsManager.AddErrorEventOnce($"TagAction.Update:InvalidCriteria_{ParentEvent.Prefab.Identifier}_{key}", GameAnalyticsManager.ErrorSeverity.Error, errorMessage); + } } + taggingDone = true; } if (ContinueIfNoTargetsFound) @@ -288,7 +304,14 @@ namespace Barotrauma } else { - isFinished = !targetNotFound; + if (mustRecheckTargets) + { + isFinished = ParentEvent.GetTargets(Tag).Any(); + } + else + { + isFinished = !cantFindTargets; + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs index fcf412f27..2722ee955 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs @@ -1353,6 +1353,7 @@ namespace Barotrauma if (item.HiddenInGame) { continue; } if (!connectedSubs.Contains(item.Submarine)) { continue; } if (item.Prefab.DontTransferBetweenSubs) { continue; } + if (AnyParentInventoryDisableTransfer(item)) { continue; } var rootOwner = item.GetRootInventoryOwner(); if (rootOwner is Character) { continue; } if (rootOwner is Item ownerItem && (ownerItem.NonInteractable || item.NonPlayerTeamInteractable || ownerItem.HiddenInGame)) { continue; } @@ -1362,6 +1363,15 @@ namespace Barotrauma if (item.Components.Any(c => c is Wire w && w.Connections.Any(c => c != null))) { continue; } itemsToTransfer.Add((item, item.Container)); item.Submarine = null; + + static bool AnyParentInventoryDisableTransfer(Item item) + { + if (item.ParentInventory?.Owner is not Item parentOwner) { return false; } + return HasProblematicComponent(parentOwner) || AnyParentInventoryDisableTransfer(parentOwner); + + static bool HasProblematicComponent(Item it) + => it.Components.Any(static c => c.DontTransferInventoryBetweenSubs); + } } foreach (var (item, container) in itemsToTransfer) { @@ -1369,8 +1379,15 @@ namespace Barotrauma { // Drop the item if it's not inside another item set to be transferred. item.Drop(null, createNetworkEvent: false, setTransform: false); + //dropping items sets the sub, set it back to null + item.Submarine = null; + foreach (var itemContainer in item.GetComponents()) + { + itemContainer.Inventory.FindAllItems((_) => true, recursive: true).ForEach(it => it.Submarine = null); + } } } + System.Diagnostics.Debug.Assert(itemsToTransfer.None(it => it.item.Submarine != null), "Item that was set to be transferred was not removed from the sub!"); currentSub.Info.NoItems = true; } // Serialize the current sub @@ -1408,6 +1425,7 @@ namespace Barotrauma { newContainer = newSub.FindContainerFor(item, onlyPrimary: true, checkTransferConditions: true, allowConnectedSubs: true); } + string newContainerName = newContainer == null ? "(null)" : $"{newContainer.Prefab.Identifier} ({newContainer.Tags})"; if (item.Container == null && (newContainer == null || !newContainer.OwnInventory.TryPutItem(item, user: null, createNetworkEvent: false))) { var cargoContainer = CargoManager.GetOrCreateCargoContainerFor(item.Prefab, spawnHull, ref availableContainers); @@ -1416,13 +1434,16 @@ namespace Barotrauma Vector2 simPos = ConvertUnits.ToSimUnits(CargoManager.GetCargoPos(spawnHull, item.Prefab)); item.SetTransform(simPos, 0.0f, findNewHull: false, setPrevTransform: false); } - else if (cargoContainer.Item.Submarine is Submarine containerSub) + else { - // Use the item's sub in case the sub consists of multiple linked subs. - item.Submarine = containerSub; + if (cargoContainer.Item.Submarine is Submarine containerSub) + { + // Use the item's sub in case the sub consists of multiple linked subs. + item.Submarine = containerSub; + } + newContainerName = cargoContainer.Item.Prefab.Identifier.ToString(); } } - string newContainerName = newContainer == null ? "(null)" : $"{newContainer.Prefab.Identifier} ({newContainer.Tags})"; string msg = "Item transfer log error."; if (oldContainer != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs index c73ea2f41..8930f1603 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs @@ -71,6 +71,12 @@ namespace Barotrauma.Items.Components protected const float CorrectionDelay = 1.0f; protected CoroutineHandle delayedCorrectionCoroutine; + /// + /// If enabled, the contents of the item are not transferred when the player transfers items between subs. + /// Use this if this component uses item containers in a way where removing the item from the container via external means would cause problems. + /// + public virtual bool DontTransferInventoryBetweenSubs => false; + [Editable, Serialize(0.0f, IsPropertySaveable.No, description: "How long it takes to pick up the item (in seconds).")] public float PickingTime { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs index ff6719a73..0ef1b1dc2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs @@ -416,7 +416,7 @@ namespace Barotrauma.Items.Components if (requiredItem.UseCondition && suitableIngredient.ConditionPercentage - requiredItem.MinCondition * 100 > 0.0f) { suitableIngredient.Condition -= suitableIngredient.Prefab.Health * requiredItem.MinCondition; - continue; + break; } if (suitableIngredient.OwnInventory != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Planter.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Planter.cs index 2e432496a..4c7cb459e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Planter.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Planter.cs @@ -82,6 +82,9 @@ namespace Barotrauma.Items.Components private List? lightComponents; + // We don't want the seeds to be transferred to a new submarine as seeds are not supposed to leave the container after they have been planted. + public override bool DontTransferInventoryBetweenSubs => true; + public Planter(Item item, ContentXElement element) : base(item, element) { canBePicked = true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/CircuitBox.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/CircuitBox.cs index f02fbebae..2cfd5b3da 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/CircuitBox.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/CircuitBox.cs @@ -26,19 +26,37 @@ namespace Barotrauma.Items.Components public override bool IsActive => true; + // We don't want the components and wires to transfer between subs as it would cause issues. + public override bool DontTransferInventoryBetweenSubs => true; + public Option FindInputOutputConnection(Identifier connectionName) { foreach (CircuitBoxInputConnection input in Inputs) { if (input.Name != connectionName) { continue; } - return Option.Some(input); } foreach (CircuitBoxOutputConnection output in Outputs) { if (output.Name != connectionName) { continue; } + return Option.Some(output); + } + return Option.None; + } + + public Option FindInputOutputConnection(Connection connection) + { + foreach (CircuitBoxInputConnection input in Inputs) + { + if (input.Connection != connection) { continue; } + return Option.Some(input); + } + + foreach (CircuitBoxOutputConnection output in Outputs) + { + if (output.Connection != connection) { continue; } return Option.Some(output); } @@ -338,7 +356,7 @@ namespace Barotrauma.Items.Components return; } - SpawnItem(this, prefab, WireContainer, wire => + SpawnItem(prefab, user: null, container: WireContainer, onSpawned: wire => { AddWireDirect(wireId, prefab, Option.Some(wire), one, two); onItemSpawned(wire); @@ -359,7 +377,7 @@ namespace Barotrauma.Items.Components private void AddWireDirect(ushort id, ItemPrefab prefab, Option backingItem, CircuitBoxConnection one, CircuitBoxConnection two) => Wires.Add(new CircuitBoxWire(this, id, backingItem, one, two, prefab)); - private bool AddComponentInternal(ushort id, ItemPrefab prefab, ItemPrefab usedResource, Vector2 pos, Action onItemSpawned) + private bool AddComponentInternal(ushort id, ItemPrefab prefab, ItemPrefab usedResource, Vector2 pos, Character? user, Action? onItemSpawned) { if (id is ICircuitBoxIdentifiable.NullComponentID) { @@ -373,10 +391,10 @@ namespace Barotrauma.Items.Components return false; } - SpawnItem(this, prefab, ComponentContainer, spawnedItem => + SpawnItem(prefab, user, ComponentContainer, spawnedItem => { Components.Add(new CircuitBoxComponent(id, spawnedItem, pos, this, usedResource)); - onItemSpawned(spawnedItem); + onItemSpawned?.Invoke(spawnedItem); }); OnViewUpdateProjSpecific(); @@ -646,7 +664,7 @@ namespace Barotrauma.Items.Components return Option.None; } - public static void SpawnItem(CircuitBox circuitBox, ItemPrefab prefab, ItemContainer? container, Action onSpawned) + public static void SpawnItem(ItemPrefab prefab, Character? user, ItemContainer? container, Action onSpawned) { if (container is null) { @@ -655,13 +673,27 @@ namespace Barotrauma.Items.Components if (IsInGame()) { - Entity.Spawner?.AddItemToSpawnQueue(prefab, container.Inventory, onSpawned: onSpawned); + Entity.Spawner?.AddItemToSpawnQueue(prefab, container.Inventory, onSpawned: it => + { + AssignWifiComponentTeam(it, user); + onSpawned(it); + }); return; } Item forceSpawnedItem = new Item(prefab, Vector2.Zero, null); container.Inventory.TryPutItem(forceSpawnedItem, null); onSpawned(forceSpawnedItem); + AssignWifiComponentTeam(forceSpawnedItem, user); + + static void AssignWifiComponentTeam(Item item, Character? user) + { + if (user == null) { return; } + foreach (WifiComponent wifiComponent in item.GetComponents()) + { + wifiComponent.TeamID = user.TeamID; + } + } } public static void RemoveItem(Item item) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs index af251d6ce..4c4c9c0a8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs @@ -98,11 +98,11 @@ namespace Barotrauma } /// Defaults to if null - public int HowManyCanBePut(ItemPrefab itemPrefab, int? maxStackSize = null, float? condition = null) + public int HowManyCanBePut(ItemPrefab itemPrefab, int? maxStackSize = null, float? condition = null, bool ignoreItemsInSlot = false) { if (itemPrefab == null) { return 0; } maxStackSize ??= itemPrefab.GetMaxStackSize(inventory); - if (items.Count > 0) + if (items.Count > 0 && !ignoreItemsInSlot) { if (condition.HasValue) { @@ -517,10 +517,10 @@ namespace Barotrauma return count; } - public virtual int HowManyCanBePut(ItemPrefab itemPrefab, int i, float? condition) + public virtual int HowManyCanBePut(ItemPrefab itemPrefab, int i, float? condition, bool ignoreItemsInSlot = false) { if (i < 0 || i >= slots.Length) { return 0; } - return slots[i].HowManyCanBePut(itemPrefab, condition: condition); + return slots[i].HowManyCanBePut(itemPrefab, condition: condition, ignoreItemsInSlot: ignoreItemsInSlot); } /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index 81d0cf2f0..379a616a7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -862,7 +862,25 @@ namespace Barotrauma { get { - return ownInventory?.AllItems ?? Enumerable.Empty(); + if (OwnInventories.Length < 2) + { + if (OwnInventory == null) { yield break; } + + foreach (var item in OwnInventory.AllItems) + { + yield return item; + } + } + else + { + foreach (var inventory in OwnInventories) + { + foreach (var item in inventory.AllItems) + { + yield return item; + } + } + } } } @@ -871,6 +889,8 @@ namespace Barotrauma get { return ownInventory; } } + public readonly ImmutableArray OwnInventories = ImmutableArray.Empty; + [Editable, Serialize(false, IsPropertySaveable.Yes, description: "Enable if you want to display the item HUD side by side with another item's HUD, when linked together. " + "Disclaimer: It's possible or even likely that the views block each other, if they were not designed to be viewed together!")] @@ -1192,6 +1212,8 @@ namespace Barotrauma ownInventory = itemContainer.Inventory; } + OwnInventories = GetComponents().Select(ic => ic.Inventory).ToImmutableArray(); + qualityComponent = GetComponent(); IsLadder = GetComponent() != null; @@ -2533,8 +2555,7 @@ namespace Barotrauma foreach (Connection c in connectionPanel.Connections) { if (connectionFilter != null && !connectionFilter.Invoke(c)) { continue; } - var recipients = c.Recipients; - foreach (Connection recipient in recipients) + foreach (Connection recipient in c.Recipients) { var component = recipient.Item.GetComponent(); if (component != null && !connectedComponents.Contains(component)) @@ -2587,9 +2608,20 @@ namespace Barotrauma private void GetConnectedComponentsRecursive(Connection c, HashSet alreadySearched, List connectedComponents, bool ignoreInactiveRelays, bool allowTraversingBackwards = true) where T : ItemComponent { alreadySearched.Add(c); - - var recipients = c.Recipients; - foreach (Connection recipient in recipients) + static IEnumerable GetRecipients(Connection c) + { + foreach (Connection recipient in c.Recipients) + { + yield return recipient; + } + //check circuit box inputs/outputs this connection is connected to + foreach (var circuitBoxConnection in c.CircuitBoxConnections) + { + yield return circuitBoxConnection.Connection; + } + } + + foreach (Connection recipient in GetRecipients(c)) { if (alreadySearched.Contains(recipient)) { continue; } var component = recipient.Item.GetComponent(); @@ -2598,23 +2630,53 @@ namespace Barotrauma connectedComponents.Add(component); } - //connected to a wifi component -> see which other wifi components it can communicate with - var wifiComponent = recipient.Item.GetComponent(); - if (wifiComponent != null && wifiComponent.CanTransmit()) + var circuitBox = recipient.Item.GetComponent(); + if (circuitBox != null) { - foreach (var wifiReceiver in wifiComponent.GetTransmittersInRange()) + //if this is a circuit box, check what the connection is connected to inside the box + var potentialCbConnection = circuitBox.FindInputOutputConnection(recipient); + if (potentialCbConnection.TryUnwrap(out var cbConnection)) { - var receiverConnections = wifiReceiver.Item.Connections; - if (receiverConnections == null) { continue; } - foreach (Connection wifiOutput in receiverConnections) + if (cbConnection is CircuitBoxInputConnection inputConnection) { - if ((wifiOutput.IsOutput == recipient.IsOutput) || alreadySearched.Contains(wifiOutput)) { continue; } - GetConnectedComponentsRecursive(wifiOutput, alreadySearched, connectedComponents, ignoreInactiveRelays, allowTraversingBackwards); + foreach (var connectedTo in inputConnection.ExternallyConnectedTo) + { + if (alreadySearched.Contains(connectedTo.Connection)) { continue; } + CheckRecipient(connectedTo.Connection); + } + } + else + { + foreach (var connectedFrom in cbConnection.ExternallyConnectedFrom) + { + if (alreadySearched.Contains(connectedFrom.Connection) || !allowTraversingBackwards) { continue; } + CheckRecipient(connectedFrom.Connection); + } } } } + CheckRecipient(recipient); - recipient.Item.GetConnectedComponentsRecursive(recipient, alreadySearched, connectedComponents, ignoreInactiveRelays, allowTraversingBackwards); + void CheckRecipient(Connection recipient) + { + //connected to a wifi component -> see which other wifi components it can communicate with + var wifiComponent = recipient.Item.GetComponent(); + if (wifiComponent != null && wifiComponent.CanTransmit()) + { + foreach (var wifiReceiver in wifiComponent.GetTransmittersInRange()) + { + var receiverConnections = wifiReceiver.Item.Connections; + if (receiverConnections == null) { continue; } + foreach (Connection wifiOutput in receiverConnections) + { + if ((wifiOutput.IsOutput == recipient.IsOutput) || alreadySearched.Contains(wifiOutput)) { continue; } + GetConnectedComponentsRecursive(wifiOutput, alreadySearched, connectedComponents, ignoreInactiveRelays, allowTraversingBackwards); + } + } + } + + recipient.Item.GetConnectedComponentsRecursive(recipient, alreadySearched, connectedComponents, ignoreInactiveRelays, allowTraversingBackwards); + } } if (ignoreInactiveRelays) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs index 0b8aea197..bc8f2f41f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs @@ -58,12 +58,12 @@ namespace Barotrauma return itemPrefab != null && slots[i].CanBePut(itemPrefab, condition, quality) && slots[i].Items.Count < container.GetMaxStackSize(i); } - public override int HowManyCanBePut(ItemPrefab itemPrefab, int i, float? condition) + public override int HowManyCanBePut(ItemPrefab itemPrefab, int i, float? condition, bool ignoreItemsInSlot = false) { if (itemPrefab == null) { return 0; } if (i < 0 || i >= slots.Length) { return 0; } if (!container.CanBeContained(itemPrefab, i)) { return 0; } - return slots[i].HowManyCanBePut(itemPrefab, maxStackSize: Math.Min(itemPrefab.GetMaxStackSize(this), container.GetMaxStackSize(i)), condition); + return slots[i].HowManyCanBePut(itemPrefab, maxStackSize: Math.Min(itemPrefab.GetMaxStackSize(this), container.GetMaxStackSize(i)), condition, ignoreItemsInSlot); } public override bool IsFull(bool takeStacksIntoAccount = false) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs index dd3abc0ad..42cefb7bb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs @@ -843,7 +843,7 @@ namespace Barotrauma } private int maxStackSizeCharacterInventory; - [Serialize(-1, IsPropertySaveable.No)] + [Serialize(-1, IsPropertySaveable.No, description: "Maximum stack size when the item is in a character inventory.")] public int MaxStackSizeCharacterInventory { get { return maxStackSizeCharacterInventory; } @@ -851,7 +851,9 @@ namespace Barotrauma } private int maxStackSizeHoldableOrWearableInventory; - [Serialize(-1, IsPropertySaveable.No)] + [Serialize(-1, IsPropertySaveable.No, description: + "Maximum stack size when the item is inside a holdable or wearable item. "+ + "If not set, defaults to MaxStackSizeCharacterInventory.")] public int MaxStackSizeHoldableOrWearableInventory { get { return maxStackSizeHoldableOrWearableInventory; } @@ -864,15 +866,20 @@ namespace Barotrauma { return maxStackSizeCharacterInventory; } - else if (maxStackSizeHoldableOrWearableInventory > 0 && - inventory?.Owner is Item item && (item.GetComponent() != null || item.GetComponent() != null)) + else if (inventory?.Owner is Item item && + (item.GetComponent() is { Attachable: false } || item.GetComponent() != null)) { - return maxStackSizeHoldableOrWearableInventory; - } - else - { - return maxStackSize; + if (maxStackSizeHoldableOrWearableInventory > 0) + { + return maxStackSizeHoldableOrWearableInventory; + } + else if (maxStackSizeCharacterInventory > 0) + { + //if maxStackSizeHoldableOrWearableInventory is not set, it defaults to maxStackSizeCharacterInventory + return maxStackSizeCharacterInventory; + } } + return maxStackSize; } [Serialize(false, IsPropertySaveable.No)] @@ -880,7 +887,7 @@ namespace Barotrauma public ImmutableHashSet AllowDroppingOnSwapWith { get; private set; } - [Serialize(false, IsPropertySaveable.No)] + [Serialize(false, IsPropertySaveable.No, "If enabled, the item is not transferred when the player transfers items between subs.")] public bool DontTransferBetweenSubs { get; private set; } [Serialize(true, IsPropertySaveable.No)] @@ -1032,6 +1039,7 @@ namespace Barotrauma var levelCommonness = new Dictionary(); var levelQuantity = new Dictionary(); + List loadedRecipes = new List(); foreach (ContentXElement subElement in ConfigElement.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) @@ -1114,9 +1122,10 @@ namespace Barotrauma var newRecipe = new FabricationRecipe(subElement, Identifier); if (fabricationRecipes.TryGetValue(newRecipe.RecipeHash, out var prevRecipe)) { + int prevRecipeIndex = loadedRecipes.IndexOf(prevRecipe); DebugConsole.ThrowError( $"Error in item prefab \"{ToString()}\": " + - $"{prevRecipe.TargetItemPrefabIdentifier} has the same hash as {newRecipe.TargetItemPrefabIdentifier}. " + + $"Fabrication recipe #{loadedRecipes.Count + 1} has the same hash as recipe #{prevRecipeIndex + 1}. This is most likely caused by identical, duplicate recipes." + $"This will cause issues with fabrication." ); } @@ -1124,6 +1133,7 @@ namespace Barotrauma { fabricationRecipes.Add(newRecipe.RecipeHash, newRecipe); } + loadedRecipes.Add(newRecipe); break; case "preferredcontainer": var preferredContainer = new PreferredContainer(subElement); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs index db32a7ec5..3fab10249 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs @@ -130,10 +130,17 @@ namespace Barotrauma /// /// When set to true, the explosion don't deal less damage when the target is behind a solid object. /// - public bool IgnoreCover - { - get; set; - } + public bool IgnoreCover { get; set; } + + /// + /// Does the damage from the explosion decrease with distance from the origin of the explosion? + /// + public bool DistanceFalloff { get; set; } = true; + + /// + /// Structures that don't count as "cover" that reduces damage from the explosion. Only relevant if IgnoreCover is set to false. + /// + public IEnumerable IgnoredCover; /// /// How long the light source created by the explosion lasts. @@ -311,12 +318,15 @@ namespace Barotrauma if (!MathUtils.NearlyEqual(Attack.GetStructureDamage(1.0f), 0.0f) || !MathUtils.NearlyEqual(Attack.GetLevelWallDamage(1.0f), 0.0f)) { - RangedStructureDamage(worldPosition, displayRange, Attack.GetStructureDamage(1.0f), Attack.GetLevelWallDamage(1.0f), attacker, IgnoredSubmarines, Attack.EmitStructureDamageParticles); + RangedStructureDamage(worldPosition, displayRange, Attack.GetStructureDamage(1.0f), Attack.GetLevelWallDamage(1.0f), attacker, + IgnoredSubmarines, + Attack.EmitStructureDamageParticles, + DistanceFalloff); } if (BallastFloraDamage > 0.0f) { - RangedBallastFloraDamage(worldPosition, displayRange, BallastFloraDamage, attacker); + RangedBallastFloraDamage(worldPosition, displayRange, BallastFloraDamage, attacker, DistanceFalloff); } if (EmpStrength > 0.0f) @@ -326,7 +336,7 @@ namespace Barotrauma { float distSqr = Vector2.DistanceSquared(item.WorldPosition, worldPosition); if (distSqr > displayRangeSqr) { continue; } - float distFactor = CalculateDistanceFactor(distSqr, displayRange); + float distFactor = DistanceFalloff ? CalculateDistanceFactor(distSqr, displayRange) : 1.0f; //damage repairable power-consuming items var powered = item.GetComponent(); @@ -362,7 +372,10 @@ namespace Barotrauma float distSqr = Vector2.DistanceSquared(item.WorldPosition, worldPosition); if (distSqr > displayRangeSqr) { continue; } - float distFactor = 1.0f - (float)Math.Sqrt(distSqr) / displayRange; + float distFactor = + DistanceFalloff ? + 1.0f - (float)Math.Sqrt(distSqr) / displayRange : + 1.0f; //repair repairable items if (item.Repairables.Any()) { @@ -415,13 +428,16 @@ namespace Barotrauma if (item.Prefab.DamagedByExplosions && !item.Indestructible) { - float distFactor = 1.0f - dist / displayRange; + float distFactor = + DistanceFalloff ? + 1.0f - dist / displayRange : + 1.0f; float damageAmount = Attack.GetItemDamage(1.0f, item.Prefab.ExplosionDamageMultiplier); Vector2 explosionPos = worldPosition; if (item.Submarine != null) { explosionPos -= item.Submarine.Position; } - damageAmount *= GetObstacleDamageMultiplier(ConvertUnits.ToSimUnits(explosionPos), worldPosition, item.SimPosition); + damageAmount *= GetObstacleDamageMultiplier(ConvertUnits.ToSimUnits(explosionPos), worldPosition, item.SimPosition, IgnoredCover); item.Condition -= damageAmount * distFactor; } } @@ -482,12 +498,15 @@ namespace Barotrauma if (dist > attack.Range) { continue; } - float distFactor = 1.0f - dist / attack.Range; + float distFactor = + DistanceFalloff ? + 1.0f - dist / attack.Range : + 1.0f; //solid obstacles between the explosion and the limb reduce the effect of the explosion if (!IgnoreCover) { - distFactor *= GetObstacleDamageMultiplier(explosionPos, worldPosition, limb.SimPosition); + distFactor *= GetObstacleDamageMultiplier(explosionPos, worldPosition, limb.SimPosition, IgnoredCover); } if (distFactor > 0) { @@ -602,7 +621,8 @@ namespace Barotrauma /// /// Returns a dictionary where the keys are the structures that took damage and the values are the amount of damage taken /// - public static Dictionary RangedStructureDamage(Vector2 worldPosition, float worldRange, float damage, float levelWallDamage, Character attacker = null, IEnumerable ignoredSubmarines = null, bool emitWallDamageParticles = true) + public static Dictionary RangedStructureDamage(Vector2 worldPosition, float worldRange, float damage, float levelWallDamage, Character attacker = null, IEnumerable ignoredSubmarines = null, + bool emitWallDamageParticles = true, bool distanceFalloff = true) { float dist = 600.0f; damagedStructures.Clear(); @@ -616,7 +636,10 @@ namespace Barotrauma { for (int i = 0; i < structure.SectionCount; i++) { - float distFactor = 1.0f - (Vector2.Distance(structure.SectionPosition(i, true), worldPosition) / worldRange); + float distFactor = + distanceFalloff ? + 1.0f - (Vector2.Distance(structure.SectionPosition(i, true), worldPosition) / worldRange) : + 1.0f; if (distFactor <= 0.0f) { continue; } structure.AddDamage(i, damage * distFactor, attacker, emitParticles: emitWallDamageParticles); @@ -680,7 +703,7 @@ namespace Barotrauma return damagedStructures; } - public static void RangedBallastFloraDamage(Vector2 worldPosition, float worldRange, float damage, Character attacker = null) + public static void RangedBallastFloraDamage(Vector2 worldPosition, float worldRange, float damage, Character attacker = null, bool distanceFalloff = true) { List ballastFlorae = new List(); @@ -698,7 +721,10 @@ namespace Barotrauma float branchDist = Vector2.Distance(branchWorldPos, worldPosition); if (branchDist < worldRange) { - float distFactor = 1.0f - (branchDist / worldRange); + float distFactor = + distanceFalloff ? + 1.0f - (branchDist / worldRange) : + 1.0f; if (distFactor <= 0.0f) { return; } Vector2 explosionPos = worldPosition; @@ -715,7 +741,7 @@ namespace Barotrauma } } - private static float GetObstacleDamageMultiplier(Vector2 explosionSimPos, Vector2 explosionWorldPos, Vector2 targetSimPos) + private static float GetObstacleDamageMultiplier(Vector2 explosionSimPos, Vector2 explosionWorldPos, Vector2 targetSimPos, IEnumerable ignoredCover = null) { float damageMultiplier = 1.0f; var obstacles = Submarine.PickBodies(targetSimPos, explosionSimPos, collisionCategory: Physics.CollisionItem | Physics.CollisionItemBlocking | Physics.CollisionWall); @@ -728,6 +754,10 @@ namespace Barotrauma } else if (body.UserData is Structure structure) { + if (ignoredCover != null) + { + if (ignoredCover.Contains(structure)) { continue; } + } int sectionIndex = structure.FindSectionIndex(explosionWorldPos, world: true, clamp: true); if (structure.SectionBodyDisabled(sectionIndex)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs index 4c93da0cc..40e897dea 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs @@ -109,6 +109,8 @@ namespace Barotrauma public float Size => IsHorizontal ? Rect.Height : Rect.Width; + public float PressureDistributionSpeed => Size / 100.0f * open; + private Door connectedDoor; public Door ConnectedDoor { @@ -427,11 +429,9 @@ namespace Barotrauma if (hull1.WaterVolume <= 0.0 && hull2.WaterVolume <= 0.0) { return; } - float size = IsHorizontal ? rect.Height : rect.Width; - //a variable affecting the water flow through the gap //the larger the gap is, the faster the water flows - float sizeModifier = size / 100.0f * open; + float sizeModifier = Size / 100.0f * open; //horizontal gap (such as a regular door) if (IsHorizontal) @@ -440,7 +440,7 @@ namespace Barotrauma float delta = 0.0f; //water level is above the lower boundary of the gap - if (Math.Max(hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1], hull2.Surface + subOffset.Y + hull2.WaveY[0]) > rect.Y - size) + if (Math.Max(hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1], hull2.Surface + subOffset.Y + hull2.WaveY[0]) > rect.Y - Size) { int dir = (hull1.Pressure > hull2.Pressure + subOffset.Y) ? 1 : -1; @@ -569,27 +569,35 @@ namespace Barotrauma if (open > 0.0f) { - if (hull1.WaterVolume > hull1.Volume / Hull.MaxCompress && hull2.WaterVolume > hull2.Volume / Hull.MaxCompress) + if (hull1.WaterVolume > hull1.Volume / Hull.MaxCompress && + hull2.WaterVolume > hull2.Volume / Hull.MaxCompress) { + //both hulls full -> distribute pressure float avgLethality = (hull1.LethalPressure + hull2.LethalPressure) / 2.0f; - hull1.LethalPressure = avgLethality; - hull2.LethalPressure = avgLethality; + changePressure(hull1, avgLethality, PressureDistributionSpeed, deltaTime); + changePressure(hull2, avgLethality, PressureDistributionSpeed, deltaTime); + + static void changePressure(Hull hull, float target, float speed, float deltaTime) + { + float diff = target - hull.LethalPressure; + float maxChange = Hull.PressureBuildUpSpeed * speed * deltaTime; + hull.LethalPressure += MathHelper.Clamp(diff, -maxChange, maxChange); + } } else { - hull1.LethalPressure -= Hull.PressureDropSpeed * deltaTime; - hull2.LethalPressure -= Hull.PressureDropSpeed * deltaTime; + //either hull not full -> pressure drops + hull1.LethalPressure -= Hull.PressureDropSpeed * PressureDistributionSpeed * deltaTime; + hull2.LethalPressure -= Hull.PressureDropSpeed * PressureDistributionSpeed * deltaTime; } } } void UpdateRoomToOut(float deltaTime, Hull hull1) { - float size = IsHorizontal ? rect.Height : rect.Width; - //a variable affecting the water flow through the gap //the larger the gap is, the faster the water flows - float sizeModifier = size * open * open; + float sizeModifier = Size * open * open; float delta = 500.0f * sizeModifier * deltaTime; @@ -642,7 +650,7 @@ namespace Barotrauma } else { - hull1.LethalPressure += ((Submarine != null && Submarine.AtDamageDepth) ? 100.0f : Hull.PressureBuildUpSpeed) * deltaTime; + hull1.LethalPressure += ((Submarine != null && Submarine.AtDamageDepth) ? 100.0f : Hull.PressureBuildUpSpeed) * PressureDistributionSpeed * deltaTime; } } else @@ -657,7 +665,7 @@ namespace Barotrauma } if (hull1.WaterVolume >= hull1.Volume / Hull.MaxCompress) { - hull1.LethalPressure += ((Submarine != null && Submarine.AtDamageDepth) ? 100.0f : Hull.PressureBuildUpSpeed) * deltaTime; + hull1.LethalPressure += ((Submarine != null && Submarine.AtDamageDepth) ? 100.0f : Hull.PressureBuildUpSpeed) * PressureDistributionSpeed * deltaTime; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs index 7de5bf629..7f899899a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs @@ -1016,7 +1016,11 @@ namespace Barotrauma if (waterVolume < Volume) { - LethalPressure -= PressureDropSpeed * deltaTime; + //pressure drop speed is inversely proportionate to water percentage + //= pressure drops very fast if the hull is nowhere near full + float waterVolumeFactor = Math.Max((100.0f - WaterPercentage) / 10.0f, 1.0f); + LethalPressure -= + PressureDropSpeed * waterVolumeFactor * deltaTime; if (WaterVolume <= 0.0f) { #if CLIENT diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs index 3e3d51b4c..77c3a7cf0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs @@ -365,7 +365,7 @@ namespace Barotrauma if (FinishedEvents.Any()) { var finishedEventsElement = new XElement(nameof(FinishedEvents)); - foreach (var (set, count) in FinishedEvents.DistinctBy(f => f.Key.Identifier)) + foreach (var (set, count) in FinishedEvents) { var element = new XElement(nameof(FinishedEvents), new XAttribute("set", set.Identifier), diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs index a2abd562e..2d4ce70e3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs @@ -1245,7 +1245,7 @@ namespace Barotrauma private static void CreateWallDamageExplosion(Gap gap, Character attacker) { - const float explosionRange = 750.0f; + const float explosionRange = 500.0f; float explosionStrength = gap.Open; var linkedHull = gap.linkedTo.FirstOrDefault() as Hull; @@ -1264,20 +1264,22 @@ namespace Barotrauma if (explosionOnBroken == null) { - explosionOnBroken = new Explosion(explosionRange, force: 10.0f, damage: 0.0f, structureDamage: 0.0f, itemDamage: 0.0f); + explosionOnBroken = new Explosion(explosionRange, force: 5.0f, damage: 0.0f, structureDamage: 0.0f, itemDamage: 0.0f); if (AfflictionPrefab.Prefabs.TryGet("lacerations".ToIdentifier(), out AfflictionPrefab lacerations)) { - explosionOnBroken.Attack.Afflictions.Add(lacerations.Instantiate(3.0f), null); + explosionOnBroken.Attack.Afflictions.Add(lacerations.Instantiate(5.0f), null); } else { - explosionOnBroken.Attack.Afflictions.Add(AfflictionPrefab.InternalDamage.Instantiate(3.0f), null); + explosionOnBroken.Attack.Afflictions.Add(AfflictionPrefab.InternalDamage.Instantiate(5.0f), null); } - explosionOnBroken.IgnoreCover = true; + explosionOnBroken.IgnoreCover = false; explosionOnBroken.OnlyInside = true; + explosionOnBroken.DistanceFalloff = false; explosionOnBroken.DisableParticles(); } + explosionOnBroken.IgnoredCover = gap.ConnectedWall?.ToEnumerable(); explosionOnBroken.Attack.Range = explosionRange * gap.Open; explosionOnBroken.Attack.DamageMultiplier = explosionStrength; explosionOnBroken.Attack.Stun = MathHelper.Clamp(explosionStrength, 0.5f, 1.0f); diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index 0f9370041..3af1f3aca 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -1606,28 +1606,7 @@ namespace Barotrauma } } } - if (removeItem) - { - for (int i = 0; i < targets.Count; i++) - { - if (targets[i] is Item item) { Entity.Spawner?.AddItemToRemoveQueue(item); } - } - } - if (removeCharacter) - { - for (int i = 0; i < targets.Count; i++) - { - var target = targets[i]; - if (target is Character character) - { - Entity.Spawner?.AddEntityToRemoveQueue(character); - } - else if (target is Limb limb) - { - Entity.Spawner?.AddEntityToRemoveQueue(limb.character); - } - } - } + if (breakLimb || hideLimb) { for (int i = 0; i < targets.Count; i++) @@ -2292,6 +2271,30 @@ namespace Barotrauma ApplyProjSpecific(deltaTime, entity, targets, hull, position, playSound: true); + //do this last - the entities spawned by the effect might need the entity for something, so better to remove it last + if (removeItem) + { + for (int i = 0; i < targets.Count; i++) + { + if (targets[i] is Item item) { Entity.Spawner?.AddItemToRemoveQueue(item); } + } + } + if (removeCharacter) + { + for (int i = 0; i < targets.Count; i++) + { + var target = targets[i]; + if (target is Character character) + { + Entity.Spawner?.AddEntityToRemoveQueue(character); + } + else if (target is Limb limb) + { + Entity.Spawner?.AddEntityToRemoveQueue(limb.character); + } + } + } + if (oneShot) { Disabled = true; diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 4210b90eb..f325714d8 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,29 @@ +------------------------------------------------------------------------------------------------------------------------------------------------- +v1.1.19.3 +------------------------------------------------------------------------------------------------------------------------------------------------- + +- Fixed shrapnel from flak cannon ammo always launching upwards. +- Fixed wall damage shrapnel going through walls and being a little excessive overall. +- Fixes to pressure distribution logic: fixes pressure sometimes being lethal in breached rooms that weren't full of water. +- Fixed clients not regaining control of their braindead character after rejoining if the character's taken any amount of damage while braindead. +- Fixes to inconsistent stack sizes (e.g. certain ammo types stacking up to 12 in character inventories, but only 8 holdable/wearable items like backpacks and toolbelts. +- Fixed console errors when switching subs with circuit boxes on board. +- Fixed items that are inside a container that doesn't get transferred getting duplicated during item transfer when switching subs. +- Fixed bots not cleaning up circuit boxes from the floor. +- Fixed stack size being displayed incorrectly in the tooltip when dragging a stack of items to an already-occupied slot. +- Fixed nav terminal's docking button not working if the signal is routed from the terminal to the docking port through a circuit box. +- Fixed double-clicking a component while a circuit box is equipped making the component vanish inside the circuit box. +- Fixed chat messages sent when accusing someone as a traitor not triggering the spam filter. +- Fixed connection names not being translated in circuit boxes (always showed up in English). +- Fixed right-side crew panel overlapping with the mission panel in the PvP mode round summary. +- Fixed depleted and fulgurium fuel rods stacking up to 32 (should be 8 like all other fuel rods). + +Modding: +- Added "UseHumanAI" property to characters (can be used to enable the human AI on characters other than humans). While using the human AI on non-humans isn't a fully supported or tested feature, it was previously possible to do that by creating a human prefab using a different species than human, but that no longer worked as of the Treacherous Tides update. +- Attachable holdable items don't count as "HoldableOrWearableInventories". Fixes e.g. tanks only stacking up to 1 in things like movable cabinets and shelves. Does not affect any vanilla content. +- Fixed fabricator reducing the condition of all the available ingredients if the crafting recipe reduces condition instead of consuming the whole item. +- Fixed TagAction continuing in some situations when it can't find targets, even if it's been configured not to with the "ContinueIfNoTargetsFound" attribute. + ------------------------------------------------------------------------------------------------------------------------------------------------- v1.1.18.1 ------------------------------------------------------------------------------------------------------------------------------------------------- @@ -336,25 +362,6 @@ Fixes: - Fixed crashing when you e.g. use a pet from some mod in the campaign, disable the mod and reload the save. - Waypoint adjustments to most submarines, outposts, wrecks, and beacons. Especially on ladders. Should take care of the remaining AI issues on ladders (the old subs in the saves don't get updated, but the fixes apply to new subs that you don't yet own. And ofc all the subs in a new game!) ---------------------------------------------------------------------------------------------------------- -v1.0.21.0 ---------------------------------------------------------------------------------------------------------- - -Fixes: -- Fixed LOS effect sometimes "lagging behind" when the sub is moving fast. -- Fixed some minor visual issues (occasional jitter/flickering) on the LOS effect. -- Fixed some issues in the bot AI that we're causing a large performance hit particularly in situations when there's lots of bots in a sub with leaks. -- Fixed bots abandoning their orders (such as operating a turret) if the room is unsafe (e.g. flooded). -- Fixed an issue in character syncing that occasionally caused disconnects with the error message "Exception thrown while reading segment EntityPosition, tried to read too much data from segment". -- Fixed wires set to be hidden in-game (e.g. invisible circuits built outside the sub) being visible on the Electrician's Goggles. -- Fixed an issue with level resources that caused crashes with certain mods (e.g. ones that include subs with piezo crystals). -- Fixed NPCs waiting on some outpost modules never reaching their targets, causing peculiar behavior. -- Fixed waypoints sometimes not getting connected between outpost modules if there's a very short hallway between them. Addresses some cities missing connections between waypoints, causing AI to be unable to navigate through the modules. -- Fixed some UI layout issues (most noticeably, ultra-wide crew list) on certain resolutions like 3440x1440. -- Fixed campaign saves occasionally failing to load with the error "an item with the same key has already been added". Seemed to only occur when using certain mods. -- Fixed crashing when you e.g. use a pet from some mod in the campaign, disable the mod and reload the save. -- Waypoint adjustments to most submarines, outposts, wrecks, and beacons. Especially on ladders. Should take care of the remaining AI issues on ladders (the old subs in the saves don't get updated, but the fixes apply to new subs that you don't yet own. And ofc all the subs in a new game!) - --------------------------------------------------------------------------------------------------------- v1.0.20.1 ---------------------------------------------------------------------------------------------------------