From b2d91cde7c8f3bdcf0df12365a838dcaf809a1c2 Mon Sep 17 00:00:00 2001 From: Regalis11 Date: Thu, 25 Sep 2025 11:11:35 +0300 Subject: [PATCH] Release 1.10.6.0 - Autumn Update 2025 Hotfix 1 --- .github/DISCUSSION_TEMPLATE/bug-reports.yml | 2 +- .../ClientSource/GameSession/CrewManager.cs | 2 +- .../Items/Components/Holdable/Sprayer.cs | 4 +- .../BarotraumaClient/ClientSource/Map/Hull.cs | 8 +- .../BarotraumaClient/LinuxClient.csproj | 2 +- Barotrauma/BarotraumaClient/MacClient.csproj | 2 +- .../BarotraumaClient/WindowsClient.csproj | 2 +- .../BarotraumaServer/LinuxServer.csproj | 2 +- Barotrauma/BarotraumaServer/MacServer.csproj | 2 +- .../GameModes/MultiPlayerCampaign.cs | 364 ++++++++++-------- .../BarotraumaServer/ServerSource/Map/Hull.cs | 76 +++- .../ServerSource/Networking/GameServer.cs | 19 +- .../Peers/Server/LidgrenServerPeer.cs | 23 +- .../Primitives/Peers/Server/P2PServerPeer.cs | 31 +- .../BarotraumaServer/WindowsServer.csproj | 2 +- .../[DebugOnlyTest]testGapSub/filelist.xml | 2 +- .../[DebugOnlyTest]testGapSub/testGapSub.sub | Bin 74550 -> 75959 bytes .../Items/Components/Holdable/Sprayer.cs | 10 +- .../BarotraumaShared/SharedSource/Map/Gap.cs | 26 +- .../BarotraumaShared/SharedSource/Map/Hull.cs | 34 +- .../SharedSource/Map/MapEntity.cs | 4 + .../SharedSource/NetStructBitField.cs | 4 +- .../Networking/INetSerializableStruct.cs | 19 +- .../SharedSource/Networking/ServerSettings.cs | 11 + Barotrauma/BarotraumaShared/changelog.txt | 10 + 25 files changed, 435 insertions(+), 226 deletions(-) diff --git a/.github/DISCUSSION_TEMPLATE/bug-reports.yml b/.github/DISCUSSION_TEMPLATE/bug-reports.yml index e9c69188b..86b2fa804 100644 --- a/.github/DISCUSSION_TEMPLATE/bug-reports.yml +++ b/.github/DISCUSSION_TEMPLATE/bug-reports.yml @@ -73,7 +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.10.5.0 (Autumn Update 2025) + - v1.10.6.0 (Autumn Update 2025 Hotfix 1) - Other validations: required: true diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs index e3a8efb27..70b843e89 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs @@ -2866,7 +2866,7 @@ namespace Barotrauma } contextualOrders.RemoveAll(o => !IsOrderAvailable(o)); var offsets = MathUtils.GetPointsOnCircumference(Vector2.Zero, nodeDistance, contextualOrders.Count, MathHelper.ToRadians(90f + 180f / contextualOrders.Count)); - bool canCharacterBeHeard = !CanCharacterBeHeard(); + bool canCharacterBeHeard = CanCharacterBeHeard(); for (int i = 0; i < contextualOrders.Count; i++) { var order = contextualOrders[i]; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/Sprayer.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/Sprayer.cs index cb72f3474..c878a6495 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/Sprayer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/Sprayer.cs @@ -238,11 +238,11 @@ namespace Barotrauma.Items.Components public void Spray(Character user, float deltaTime, bool applyColors) { - Item liquidItem = liquidContainer?.Inventory.FirstOrDefault(); + Item liquidItem = LiquidContainer?.Inventory.FirstOrDefault(); if (liquidItem == null) { return; } bool isCleaning = false; - liquidColors.TryGetValue(liquidItem.Prefab.Identifier, out color); + LiquidColors.TryGetValue(liquidItem.Prefab.Identifier, out color); if (applyColors && targetSections.Any()) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs index 4c6eee08a..09e467505 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs @@ -183,7 +183,7 @@ namespace Barotrauma networkUpdateTimer += deltaTime; if (networkUpdateTimer > 0.2f) { - if (!pendingSectionUpdates.Any() && !pendingDecalUpdates.Any()) + if (!pendingSectorUpdates.Any() && !pendingDecalUpdates.Any()) { //these are used to modify the amount water/fire in the hull with console commands //they should be usable even when not controlling a character @@ -194,11 +194,11 @@ namespace Barotrauma GameMain.Client?.CreateEntityEvent(this, new DecalEventData(decal)); } pendingDecalUpdates.Clear(); - foreach (int pendingSectionUpdate in pendingSectionUpdates) + foreach (int pendingSectorUpdate in pendingSectorUpdates) { - GameMain.Client?.CreateEntityEvent(this, new BackgroundSectionsEventData(pendingSectionUpdate)); + GameMain.Client?.CreateEntityEvent(this, new BackgroundSectionsEventData(pendingSectorUpdate)); } - pendingSectionUpdates.Clear(); + pendingSectorUpdates.Clear(); networkUpdatePending = false; networkUpdateTimer = 0.0f; } diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index dfc77450d..5934083da 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 1.10.5.0 + 1.10.6.0 Copyright © FakeFish 2018-2024 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index 011b3992c..87f35ddb7 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 1.10.5.0 + 1.10.6.0 Copyright © FakeFish 2018-2024 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index 0ef2232a4..5eca98d1c 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 1.10.5.0 + 1.10.6.0 Copyright © FakeFish 2018-2024 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index 68d267482..118b45e61 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 1.10.5.0 + 1.10.6.0 Copyright © FakeFish 2018-2023 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index fd8876d56..11e1f7329 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 1.10.5.0 + 1.10.6.0 Copyright © FakeFish 2018-2023 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs index 7adb32f32..c55f018f1 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -933,207 +933,235 @@ namespace Barotrauma CheckTooManyMissions(Map.CurrentLocation, sender); } - var prevBuyCrateItems = new Dictionary>(); - foreach (var kvp in CargoManager.ItemsInBuyCrate) + if (HasCampaignInteractionAvailable(sender, InteractionType.Store)) { - prevBuyCrateItems.Add(kvp.Key, new List(kvp.Value)); - } - foreach (var store in prevBuyCrateItems) - { - foreach (var item in store.Value.ToList()) + var prevBuyCrateItems = new Dictionary>(); + foreach (var kvp in CargoManager.ItemsInBuyCrate) { - CargoManager.ModifyItemQuantityInBuyCrate(store.Key, item.ItemPrefab, -item.Quantity, sender); + prevBuyCrateItems.Add(kvp.Key, new List(kvp.Value)); } - } - foreach (var store in buyCrateItems) - { - foreach (var item in store.Value.ToList()) + foreach (var store in prevBuyCrateItems) { - if (map?.CurrentLocation?.Stores == null || !map.CurrentLocation.Stores.ContainsKey(store.Key)) { continue; } - int availableQuantity = map.CurrentLocation.Stores[store.Key].Stock.Find(s => s.ItemPrefab == item.ItemPrefab)?.Quantity ?? 0; - int alreadyPurchasedQuantity = - CargoManager.GetBuyCrateItem(store.Key, item.ItemPrefab)?.Quantity ?? 0 + - CargoManager.GetPurchasedItemCount(store.Key, item.ItemPrefab); - item.Quantity = MathHelper.Clamp(item.Quantity, 0, availableQuantity - alreadyPurchasedQuantity); - CargoManager.ModifyItemQuantityInBuyCrate(store.Key, item.ItemPrefab, item.Quantity, sender); - } - } - - var prevPurchasedItems = new Dictionary>(); - foreach (var kvp in CargoManager.PurchasedItems) - { - prevPurchasedItems.Add(kvp.Key, new List(kvp.Value)); - } - - foreach (var storeId in purchasedItems.Keys) - { - DebugConsole.Log($"Purchased items ({storeId}):\n"); - if (prevPurchasedItems.TryGetValue(storeId, out var alreadyPurchased)) - { - var delivered = alreadyPurchased.Where(it => it.Delivered); - var notDelivered = alreadyPurchased.Where(it => !it.Delivered); - if (delivered.Any()) + foreach (var item in store.Value.ToList()) { - DebugConsole.Log($" Already delivered:\n" + string.Concat(delivered.Select(it => $" - {it.ItemPrefab.Name} (x{it.Quantity})"))); - } - if (notDelivered.Any()) - { - DebugConsole.Log($" Already purchased:\n" + string.Concat(notDelivered.Where(it => !it.Delivered).Select(it => $" - {it.ItemPrefab.Name} (x{it.Quantity})"))); + CargoManager.ModifyItemQuantityInBuyCrate(store.Key, item.ItemPrefab, -item.Quantity, sender); } } - DebugConsole.Log($" New purchases:"); - foreach (var purchasedItem in purchasedItems[storeId]) + foreach (var store in buyCrateItems) { - if (purchasedItem.Delivered) { continue; } - int quantity = purchasedItem.Quantity; - if (alreadyPurchased != null) + foreach (var item in store.Value.ToList()) { - quantity -= alreadyPurchased.Where(it => it.DeliverImmediately == purchasedItem.DeliverImmediately && it.ItemPrefab == purchasedItem.ItemPrefab).Sum(it => it.Quantity); - } - if (quantity > 0) - { - DebugConsole.Log($" - {purchasedItem.ItemPrefab.Name} (x{quantity})"); + if (map?.CurrentLocation?.Stores == null || !map.CurrentLocation.Stores.ContainsKey(store.Key)) { continue; } + int availableQuantity = map.CurrentLocation.Stores[store.Key].Stock.Find(s => s.ItemPrefab == item.ItemPrefab)?.Quantity ?? 0; + int alreadyPurchasedQuantity = + CargoManager.GetBuyCrateItem(store.Key, item.ItemPrefab)?.Quantity ?? 0 + + CargoManager.GetPurchasedItemCount(store.Key, item.ItemPrefab); + item.Quantity = MathHelper.Clamp(item.Quantity, 0, availableQuantity - alreadyPurchasedQuantity); + CargoManager.ModifyItemQuantityInBuyCrate(store.Key, item.ItemPrefab, item.Quantity, sender); } } - } - foreach (var storeId in soldItems.Keys) - { - DebugConsole.Log($"Sold items:\n" + string.Concat(soldItems[storeId].Select(it => $" - {it.ItemPrefab.Name}"))); - } - foreach (var kvp in purchasedItems) - { - var storeId = kvp.Key; - var purchasedItemList = kvp.Value; - foreach (var purchasedItem in purchasedItemList) + var prevPurchasedItems = new Dictionary>(); + foreach (var kvp in CargoManager.PurchasedItems) { - int desiredQuantity = purchasedItem.Quantity; - if (prevPurchasedItems.TryGetValue(storeId, out var alreadyPurchasedList) && - alreadyPurchasedList.FirstOrDefault(p => p.ItemPrefab == purchasedItem.ItemPrefab && p.DeliverImmediately == purchasedItem.DeliverImmediately) is { } alreadyPurchased) + prevPurchasedItems.Add(kvp.Key, new List(kvp.Value)); + } + + foreach (var storeId in purchasedItems.Keys) + { + DebugConsole.Log($"Purchased items ({storeId}):\n"); + if (prevPurchasedItems.TryGetValue(storeId, out var alreadyPurchased)) { - desiredQuantity -= alreadyPurchased.Quantity; + var delivered = alreadyPurchased.Where(it => it.Delivered); + var notDelivered = alreadyPurchased.Where(it => !it.Delivered); + if (delivered.Any()) + { + DebugConsole.Log($" Already delivered:\n" + string.Concat(delivered.Select(it => $" - {it.ItemPrefab.Name} (x{it.Quantity})"))); + } + if (notDelivered.Any()) + { + DebugConsole.Log($" Already purchased:\n" + string.Concat(notDelivered.Where(it => !it.Delivered).Select(it => $" - {it.ItemPrefab.Name} (x{it.Quantity})"))); + } } - int availableQuantity = map.CurrentLocation.Stores[storeId].Stock.Find(s => s.ItemPrefab == purchasedItem.ItemPrefab)?.Quantity ?? 0; - purchasedItem.Quantity = Math.Min(desiredQuantity, availableQuantity); - } - CargoManager.PurchaseItems(storeId, purchasedItemList, removeFromCrate: false, client: sender); - } - - foreach (var (storeIdentifier, items) in CargoManager.PurchasedItems) - { - if (!prevPurchasedItems.ContainsKey(storeIdentifier)) - { - CargoManager.LogNewItemPurchases(storeIdentifier, items, sender); - continue; - } - - List newItems = new List(); - List prevItems = prevPurchasedItems[storeIdentifier]; - - foreach (PurchasedItem item in items) - { - PurchasedItem matching = prevItems.FirstOrDefault(ppi => ppi.ItemPrefab == item.ItemPrefab); - if (matching is null) + DebugConsole.Log($" New purchases:"); + foreach (var purchasedItem in purchasedItems[storeId]) { - newItems.Add(item); + if (purchasedItem.Delivered) { continue; } + int quantity = purchasedItem.Quantity; + if (alreadyPurchased != null) + { + quantity -= alreadyPurchased.Where(it => it.DeliverImmediately == purchasedItem.DeliverImmediately && it.ItemPrefab == purchasedItem.ItemPrefab).Sum(it => it.Quantity); + } + if (quantity > 0) + { + DebugConsole.Log($" - {purchasedItem.ItemPrefab.Name} (x{quantity})"); + } + } + } + foreach (var storeId in soldItems.Keys) + { + DebugConsole.Log($"Sold items:\n" + string.Concat(soldItems[storeId].Select(it => $" - {it.ItemPrefab.Name}"))); + } + foreach (var kvp in purchasedItems) + { + var storeId = kvp.Key; + var purchasedItemList = kvp.Value; + foreach (var purchasedItem in purchasedItemList) + { + int desiredQuantity = purchasedItem.Quantity; + if (prevPurchasedItems.TryGetValue(storeId, out var alreadyPurchasedList) && + alreadyPurchasedList.FirstOrDefault(p => p.ItemPrefab == purchasedItem.ItemPrefab && p.DeliverImmediately == purchasedItem.DeliverImmediately) is { } alreadyPurchased) + { + desiredQuantity -= alreadyPurchased.Quantity; + } + int availableQuantity = map.CurrentLocation.Stores[storeId].Stock.Find(s => s.ItemPrefab == purchasedItem.ItemPrefab)?.Quantity ?? 0; + purchasedItem.Quantity = Math.Min(desiredQuantity, availableQuantity); + } + CargoManager.PurchaseItems(storeId, purchasedItemList, removeFromCrate: false, client: sender); + } + + foreach (var (storeIdentifier, items) in CargoManager.PurchasedItems) + { + if (!prevPurchasedItems.ContainsKey(storeIdentifier)) + { + CargoManager.LogNewItemPurchases(storeIdentifier, items, sender); continue; } - if (matching.Quantity < item.Quantity) + + List newItems = new List(); + List prevItems = prevPurchasedItems[storeIdentifier]; + + foreach (PurchasedItem item in items) { - newItems.Add(new PurchasedItem(item.ItemPrefab, item.Quantity - matching.Quantity, sender)); + PurchasedItem matching = prevItems.FirstOrDefault(ppi => ppi.ItemPrefab == item.ItemPrefab); + if (matching is null) + { + newItems.Add(item); + continue; + } + if (matching.Quantity < item.Quantity) + { + newItems.Add(new PurchasedItem(item.ItemPrefab, item.Quantity - matching.Quantity, sender)); + } + } + + if (newItems.Any()) + { + CargoManager.LogNewItemPurchases(storeIdentifier, newItems, sender); } } - if (newItems.Any()) + bool allowedToSellSubItems = AllowedToManageCampaign(sender, ClientPermissions.SellSubItems); + if (allowedToSellSubItems) { - CargoManager.LogNewItemPurchases(storeIdentifier, newItems, sender); - } - } - - - bool allowedToSellSubItems = AllowedToManageCampaign(sender, ClientPermissions.SellSubItems); - if (allowedToSellSubItems) - { - var prevSubSellCrateItems = new Dictionary>(CargoManager.ItemsInSellFromSubCrate); - foreach (var store in prevSubSellCrateItems) - { - foreach (var item in store.Value.ToList()) + var prevSubSellCrateItems = new Dictionary>(CargoManager.ItemsInSellFromSubCrate); + foreach (var store in prevSubSellCrateItems) { - CargoManager.ModifyItemQuantityInSubSellCrate(store.Key, item.ItemPrefab, -item.Quantity, sender); + foreach (var item in store.Value.ToList()) + { + CargoManager.ModifyItemQuantityInSubSellCrate(store.Key, item.ItemPrefab, -item.Quantity, sender); + } + } + foreach (var store in subSellCrateItems) + { + foreach (var item in store.Value.ToList()) + { + CargoManager.ModifyItemQuantityInSubSellCrate(store.Key, item.ItemPrefab, item.Quantity, sender); + } } } - foreach (var store in subSellCrateItems) + + bool allowedToSellInventoryItems = AllowedToManageCampaign(sender, ClientPermissions.SellInventoryItems); + if (allowedToSellInventoryItems && allowedToSellSubItems) { - foreach (var item in store.Value.ToList()) + // for some reason CargoManager.SoldItem is never cleared by the server, I've added a check to SellItems that ignores all + // sold items that are removed so they should be discarded on the next message + var prevSoldItems = new Dictionary>(CargoManager.SoldItems); + foreach (var store in prevSoldItems) { - CargoManager.ModifyItemQuantityInSubSellCrate(store.Key, item.ItemPrefab, item.Quantity, sender); + CargoManager.BuyBackSoldItems(store.Key, store.Value.ToList(), sender); + } + foreach (var store in soldItems) + { + CargoManager.SellItems(store.Key, store.Value.ToList(), sender); + } + } + else if (allowedToSellInventoryItems || allowedToSellSubItems) + { + var prevSoldItems = new Dictionary>(CargoManager.SoldItems); + foreach (var store in prevSoldItems) + { + store.Value.RemoveAll(predicate); + CargoManager.BuyBackSoldItems(store.Key, store.Value.ToList(), sender); + } + foreach (var store in soldItems) + { + store.Value.RemoveAll(predicate); + } + foreach (var store in soldItems) + { + CargoManager.SellItems(store.Key, store.Value.ToList(), sender); + } + bool predicate(SoldItem i) => allowedToSellInventoryItems != (i.Origin == SoldItem.SellOrigin.Character); + } + } + else + { + GameServer.Log($"{sender.Name} attempted to buy or sell items without having access to a store NPC.", ServerLog.MessageType.Error); + } + + if ((purchasedUpgrades.Any() || purchasedItemSwaps.Any()) && + HasCampaignInteractionAvailable(sender, InteractionType.Upgrade)) + { + var characterList = GameSession.GetSessionCrewCharacters(CharacterType.Both); + foreach (var (prefab, category, _) in purchasedUpgrades) + { + UpgradeManager.TryPurchaseUpgrade(prefab, category, client: sender); + // unstable logging + int price = prefab.Price.GetBuyPrice(prefab, UpgradeManager.GetUpgradeLevel(prefab, category), Map?.CurrentLocation, characterList); + int level = UpgradeManager.GetUpgradeLevel(prefab, category); + GameServer.Log($"SERVER: Purchased level {level} {category.Identifier}.{prefab.Identifier} for {price}", ServerLog.MessageType.ServerMessage); + } + foreach (var purchasedItemSwap in purchasedItemSwaps) + { + if (purchasedItemSwap.ItemToInstall == null) + { + UpgradeManager.CancelItemSwap(purchasedItemSwap.ItemToRemove, client: sender); + } + else + { + UpgradeManager.PurchaseItemSwap(purchasedItemSwap.ItemToRemove, purchasedItemSwap.ItemToInstall, client: sender); + } + } + foreach (Item item in Item.ItemList) + { + if (item.PendingItemSwap != null && !purchasedItemSwaps.Any(it => it.ItemToRemove == item)) + { + UpgradeManager.CancelItemSwap(item); + item.PendingItemSwap = null; } } } + else + { + GameServer.Log($"{sender.Name} attempted to buy upgrades without having access to an NPC offering upgrades.", ServerLog.MessageType.Error); + } + } - bool allowedToSellInventoryItems = AllowedToManageCampaign(sender, ClientPermissions.SellInventoryItems); - if (allowedToSellInventoryItems && allowedToSellSubItems) + private bool HasCampaignInteractionAvailable(Client sender, InteractionType interactionType) + { + if (sender.Character == null || sender.Character.IsIncapacitated) { return false; } + if (GameMain.Server?.ServerSettings is { AllowRemoteCampaignInteractions: true }) { return true; } + foreach (var otherCharacter in Character.CharacterList) { - // for some reason CargoManager.SoldItem is never cleared by the server, I've added a check to SellItems that ignores all - // sold items that are removed so they should be discarded on the next message - var prevSoldItems = new Dictionary>(CargoManager.SoldItems); - foreach (var store in prevSoldItems) + if (otherCharacter.CampaignInteractionType != interactionType) { continue; } + //larger-than default maximum distance to give legit clients some leeway if their position is a bit off + if (sender.Character.CanInteractWith(otherCharacter, maxDist: 250.0f)) { - CargoManager.BuyBackSoldItems(store.Key, store.Value.ToList(), sender); - } - foreach (var store in soldItems) - { - CargoManager.SellItems(store.Key, store.Value.ToList(), sender); - } - } - else if (allowedToSellInventoryItems || allowedToSellSubItems) - { - var prevSoldItems = new Dictionary>(CargoManager.SoldItems); - foreach (var store in prevSoldItems) - { - store.Value.RemoveAll(predicate); - CargoManager.BuyBackSoldItems(store.Key, store.Value.ToList(), sender); - } - foreach (var store in soldItems) - { - store.Value.RemoveAll(predicate); - } - foreach (var store in soldItems) - { - CargoManager.SellItems(store.Key, store.Value.ToList(), sender); - } - bool predicate(SoldItem i) => allowedToSellInventoryItems != (i.Origin == SoldItem.SellOrigin.Character); - } - - var characterList = GameSession.GetSessionCrewCharacters(CharacterType.Both); - foreach (var (prefab, category, _) in purchasedUpgrades) - { - UpgradeManager.TryPurchaseUpgrade(prefab, category, client: sender); - - // unstable logging - int price = prefab.Price.GetBuyPrice(prefab, UpgradeManager.GetUpgradeLevel(prefab, category), Map?.CurrentLocation, characterList); - int level = UpgradeManager.GetUpgradeLevel(prefab, category); - GameServer.Log($"SERVER: Purchased level {level} {category.Identifier}.{prefab.Identifier} for {price}", ServerLog.MessageType.ServerMessage); - } - foreach (var purchasedItemSwap in purchasedItemSwaps) - { - if (purchasedItemSwap.ItemToInstall == null) - { - UpgradeManager.CancelItemSwap(purchasedItemSwap.ItemToRemove, client: sender); - } - else - { - UpgradeManager.PurchaseItemSwap(purchasedItemSwap.ItemToRemove, purchasedItemSwap.ItemToInstall, client: sender); - } - } - foreach (Item item in Item.ItemList) - { - if (item.PendingItemSwap != null && !purchasedItemSwaps.Any(it => it.ItemToRemove == item)) - { - UpgradeManager.CancelItemSwap(item); - item.PendingItemSwap = null; + return true; } } + return false; } public void ServerReadMoney(IReadMessage msg, Client sender) @@ -1265,7 +1293,7 @@ namespace Barotrauma CharacterInfo firedCharacter = null; (ushort id, string newName) appliedRename = (Entity.NullEntityID, string.Empty); - if (location != null) + if (location != null && HasCampaignInteractionAvailable(sender, InteractionType.Crew)) { if (fireCharacter && AllowedToManageCampaign(sender, ClientPermissions.ManageHires)) { @@ -1369,6 +1397,10 @@ namespace Barotrauma }); } } + else + { + GameServer.Log($"{sender.Name} attempted to manage hires without having access to an appropriate NPC.", ServerLog.MessageType.Error); + } // bounce back if (renameCharacter && existingCrewMember) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Map/Hull.cs b/Barotrauma/BarotraumaServer/ServerSource/Map/Hull.cs index 86a40a127..cfb0592cd 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Map/Hull.cs @@ -64,15 +64,15 @@ namespace Barotrauma decalUpdateTimer = 0; decalUpdatePending = false; } - if (pendingSectionUpdates.Count > 0 && backgroundSectionUpdateTimer > NetConfig.HullUpdateInterval) + if (pendingSectorUpdates.Count > 0 && backgroundSectionUpdateTimer > NetConfig.HullUpdateInterval) { - foreach (int pendingSectionUpdate in pendingSectionUpdates) + foreach (int pendingSectorUpdate in pendingSectorUpdates) { - GameMain.NetworkMember.CreateEntityEvent(this, new BackgroundSectionsEventData(pendingSectionUpdate)); + GameMain.NetworkMember.CreateEntityEvent(this, new BackgroundSectionsEventData(pendingSectorUpdate)); } backgroundSectionUpdateTimer = 0; - pendingSectionUpdates.Clear(); + pendingSectorUpdates.Clear(); } } @@ -177,6 +177,8 @@ namespace Barotrauma } break; case EventType.BackgroundSections: + bool addPendingSectorUpdate = false; + SharedBackgroundSectionRead( msg, bsnu => @@ -185,17 +187,67 @@ namespace Barotrauma Color color = bsnu.Color; float colorStrength = bsnu.ColorStrength; - #warning TODO: verify the client is close enough to this hull to paint it, that the sprayer is functional and that the color matches - if (!(c.Character is { AllowInput: true })) { return; } - if (c.Character.HeldItems.All(it => it.GetComponent() == null)) { return; } + if (c.Character is not { AllowInput: true }) { return; } + //ideally the server would just run the painting logic the same way as clients instead of relying on the clients setting colors on the hull, + //but that's non-trivial because the server doesn't know the client's exact cursor position, just the direction they're aiming at + //and we want the painting to be precise, lag shouldn't cause the paint to end up in the wrong place, etc. + //but now that clients set the colors themselves, we need to do some sanity checks: + + var sprayer = c.Character.HeldItems + .Select(it => it.GetComponent()) + .FirstOrDefault(component => component != null); + if (sprayer == null) { return; } + + Item liquidItem = sprayer.LiquidContainer?.Inventory?.FirstOrDefault(); + if (liquidItem == null) { return; } + + if (!sprayer.LiquidColors.TryGetValue(liquidItem.Prefab.Identifier, out Color paintColor)) { return; } + + bool isCleaning = paintColor.A == 0; + + var backgroundSectionPos = GetBackgroundSectionWorldPos(BackgroundSections[i]); + //rough distance check to disallow painting from very far away + //(slightly longer range than the normal range of the sprayer to give the client some leeway) + if (Vector2.Distance(backgroundSectionPos, sprayer.Item.WorldPosition) > sprayer.Range * 1.1f) + { + return; + } + + //if we get to this point (client can paint this section), let's sync the changes + //the color change below may fail if the color is out of sync client-side, even if the client isn't doing anything malicious, + //in which case we want to get the client back in sync + addPendingSectorUpdate = true; + + if (isCleaning) + { + //if we're cleaning, strength of the color must go down + if (colorStrength >= BackgroundSections[i].ColorStrength) { return; } + } + else + { + Vector3 colorChange = color.ToVector3() - BackgroundSections[i].Color.ToVector3(); + Vector3 expectedColorChange = paintColor.ToVector3() - BackgroundSections[i].Color.ToVector3(); + + //color should be going towards the color of the paint, if it's not, don't allow changing it + if (Math.Sign(colorChange.X) != Math.Sign(expectedColorChange.X) || + Math.Sign(colorChange.Y) != Math.Sign(expectedColorChange.Y) || + Math.Sign(colorChange.Z) != Math.Sign(expectedColorChange.Z)) + { + return; + } + BackgroundSections[i].SetColor(color); + } BackgroundSections[i].SetColorStrength(colorStrength); - BackgroundSections[i].SetColor(color); }, out int sectorToUpdate); - RefreshAveragePaintedColor(); - //add to pending updates to notify other clients as well - pendingSectionUpdates.Add(sectorToUpdate); + + if (addPendingSectorUpdate) + { + RefreshAveragePaintedColor(); + //add to pending updates to notify other clients as well + pendingSectorUpdates.Add(sectorToUpdate); + } break; case EventType.Decal: byte decalIndex = msg.ReadByte(); @@ -209,7 +261,7 @@ namespace Barotrauma break; default: throw new Exception($"Malformed incoming hull event: {eventType} is not a supported event type"); - } + } } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index 4ce36cbcf..61482754a 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -4384,9 +4384,22 @@ namespace Barotrauma.Networking moustacheIndex: netInfo.MoustacheIndex, faceAttachmentIndex: netInfo.FaceAttachmentIndex); - sender.CharacterInfo.Head.SkinColor = netInfo.SkinColor; - sender.CharacterInfo.Head.HairColor = netInfo.HairColor; - sender.CharacterInfo.Head.FacialHairColor = netInfo.FacialHairColor; + sender.CharacterInfo.Head.SkinColor = validateColor(netInfo.SkinColor, "skin color", sender.CharacterInfo.SkinColors.Select(kvp => kvp.Color)); + sender.CharacterInfo.Head.HairColor = validateColor(netInfo.HairColor, "hair color", sender.CharacterInfo.HairColors.Select(kvp => kvp.Color)); + sender.CharacterInfo.Head.FacialHairColor = validateColor(netInfo.FacialHairColor, "facial hair color", sender.CharacterInfo.FacialHairColors.Select(kvp => kvp.Color)); + + Color validateColor(Color newColor, string colorName, IEnumerable supportedColors) + { + if (!supportedColors.Contains(newColor)) + { + DebugConsole.AddWarning($"Client {sender.Name} attempted to set their {colorName} to an unsupported value ({newColor})."); + return supportedColors.First(); + } + else + { + return newColor; + } + } if (netInfo.JobVariants.Length > 0) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs index 81083c420..79bfedd93 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs @@ -269,8 +269,27 @@ namespace Barotrauma.Networking return; } - var packet = INetSerializableStruct.Read(inc); - callbacks.OnMessageReceived.Invoke(conn, packet.GetReadMessage(packetHeader.IsCompressed(), conn)); + try + { + var packet = INetSerializableStruct.Read(inc); + callbacks.OnMessageReceived.Invoke(conn, packet.GetReadMessage(packetHeader.IsCompressed(), conn)); + } + + catch (NetStructReadException) + { + //kick the client if we fail to parse their message + if (conn != OwnerConnection) + { + if (connectedClients.Find(c => c.Connection == conn) is { } connectedClient) + { + Disconnect(connectedClient.Connection, PeerDisconnectPacket.WithReason(DisconnectReason.MalformedData)); + } + } + else + { + throw; + } + } } LidgrenConnection? FindConnection(NetConnection ligdrenConn) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/P2PServerPeer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/P2PServerPeer.cs index c170aef88..16e309491 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/P2PServerPeer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/P2PServerPeer.cs @@ -73,11 +73,31 @@ namespace Barotrauma.Networking try { - foreach (var incBuf in ChildServerRelay.Read()) + foreach (byte[] incBuf in ChildServerRelay.Read()) { - IReadMessage inc = new ReadOnlyMessage(incBuf, false, 0, incBuf.Length, OwnerConnection); - - HandleDataMessage(inc); + P2PEndpoint? senderEndpoint = null; + try + { + IReadMessage inc = new ReadOnlyMessage(incBuf, false, 0, incBuf.Length, OwnerConnection); + HandleDataMessage(inc, out senderEndpoint); + } + catch (NetStructReadException) + { + //kick the client if we fail to parse their message + if (senderEndpoint != null && senderEndpoint != ownerEndpoint) + { + if (pendingClients.Find(c => c.Connection.Endpoint == senderEndpoint) is { } pendingClient) + { + RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.MalformedData)); + } + if (connectedClients.Find(c => c.Connection.Endpoint == senderEndpoint) is { } connectedClient) + { + Disconnect(connectedClient.Connection, PeerDisconnectPacket.WithReason(DisconnectReason.MalformedData)); + } + break; + } + throw; + } } } @@ -100,8 +120,9 @@ namespace Barotrauma.Networking } } - private void HandleDataMessage(IReadMessage inc) + private void HandleDataMessage(IReadMessage inc, out P2PEndpoint? senderEndPoint) { + senderEndPoint = null; if (!started) { return; } var senderInfo = INetSerializableStruct.Read(inc); diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index 808dc379b..3e24cc9c7 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 1.10.5.0 + 1.10.6.0 Copyright © FakeFish 2018-2023 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]testGapSub/filelist.xml b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]testGapSub/filelist.xml index bb59ee8d3..5caadb4a3 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]testGapSub/filelist.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]testGapSub/filelist.xml @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]testGapSub/testGapSub.sub b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]testGapSub/testGapSub.sub index 9f730ea4c709c8e9cb9dabb8e902213d7dc246c6..77747cf3ddde55ea79cd1ed7e49b7b00f365c1a7 100644 GIT binary patch delta 21058 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 a27zw9Qh%i%N;2vN5cgc$S@m%vBl-^lbZ^`M delta 19638 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�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 diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Sprayer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Sprayer.cs index cbf773a34..5ccc6a433 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Sprayer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Sprayer.cs @@ -1,5 +1,6 @@ using Microsoft.Xna.Framework; using System.Collections.Generic; +using System.Collections.Immutable; using System.Xml.Linq; namespace Barotrauma.Items.Components @@ -12,8 +13,8 @@ namespace Barotrauma.Items.Components [Serialize(1.0f, IsPropertySaveable.No, description: "How fast the item changes the color of the walls.")] public float SprayStrength { get; set; } - private readonly Dictionary liquidColors; - private ItemContainer liquidContainer; + public readonly ImmutableDictionary LiquidColors; + public ItemContainer LiquidContainer { get; private set; } public Sprayer(Item item, ContentXElement element) : base(item, element) { @@ -26,7 +27,7 @@ namespace Barotrauma.Items.Components { case "paintcolors": { - liquidColors = new Dictionary(); + var liquidColors = new Dictionary(); foreach (XElement paintElement in subElement.Elements()) { Identifier paintName = paintElement.GetAttributeIdentifier("paintitem", Identifier.Empty); @@ -37,6 +38,7 @@ namespace Barotrauma.Items.Components liquidColors.Add(paintName, paintColor); } } + LiquidColors = liquidColors.ToImmutableDictionary(); } break; } @@ -46,7 +48,7 @@ namespace Barotrauma.Items.Components public override void OnItemLoaded() { - liquidContainer = item.GetComponent(); + LiquidContainer = item.GetComponent(); } partial void InitProjSpecific(ContentXElement element); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs index 07e187866..bcd4a8a37 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs @@ -59,6 +59,8 @@ namespace Barotrauma private float higherSurface; private float lowerSurface; + private float waterFlowThisFrame; + private Vector2 lerpedFlowForce; //if set to true, hull connections of this gap won't be updated when changes are being done to hulls @@ -503,6 +505,7 @@ namespace Barotrauma delta = Math.Min(delta, hull1.Volume * Hull.MaxCompress - hull1.WaterVolume); hull1.WaterVolume += delta; hull2.WaterVolume -= delta; + waterFlowThisFrame += delta; if (hull1.WaterVolume > hull1.Volume) { hull1.Pressure = Math.Max(hull1.Pressure, (hull1.Pressure + hull2.Pressure+subOffset.Y) / 2); @@ -529,7 +532,7 @@ namespace Barotrauma { hull2.Pressure = Math.Max(hull2.Pressure, ((hull1.Pressure-subOffset.Y) + hull2.Pressure) / 2); } - + waterFlowThisFrame += delta; flowForce = new Vector2(delta * (float)(Timing.Step / deltaTime), 0.0f); } @@ -569,6 +572,7 @@ namespace Barotrauma delta = Math.Max(delta, 0.0f); hull1.WaterVolume += delta; hull2.WaterVolume -= delta; + waterFlowThisFrame += delta; flowForce = new Vector2( 0.0f, @@ -597,6 +601,7 @@ namespace Barotrauma } hull1.WaterVolume -= delta; hull2.WaterVolume += delta; + waterFlowThisFrame += delta; flowForce = new Vector2( hull1.WaveY[hull1.GetWaveIndex(rect.X)] - hull1.WaveY[hull1.GetWaveIndex(rect.Right)], @@ -734,6 +739,11 @@ namespace Barotrauma return (linkedTo[0] == hull1 ? linkedTo[1] : linkedTo[0]) as Hull; } + public void ResetWaterFlowThisFrame() + { + waterFlowThisFrame = 0.0f; + } + private static readonly HashSet checkedHulls = new HashSet(); /// @@ -758,10 +768,24 @@ namespace Barotrauma const float decay = 0.95f; maxFlow = Math.Min(maxFlow, gap.GetWaterFlowFromOutside(targetHull, deltaTime, ignoreCurrentWater: true)) * decay; + + //if the hulls are not linked (i.e. not parts of the same room), limit the flow a bit + var sourceHull = gap.GetOtherLinkedHull(targetHull); + if (sourceHull != null && !sourceHull.linkedTo.Contains(targetHull)) + { + maxFlow *= 0.5f; + } + + //take the amount of water that has already passed through this gap into account + //(if there's multiple leaks to the outside recursively passing water through the same gap, the flow should not go above the maximum flow through this gap) + maxFlow -= gap.waterFlowThisFrame; + if (maxFlow <= 0.001f) { return; } checkedHulls.Add(targetHull); + gap.waterFlowThisFrame += maxFlow; + //don't multiply by deltatime here, we already did that in GetWaterFlowFromOutside targetHull.WaterVolume += maxFlow; //lerp lethal pressure up very fast diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs index 2a79f9482..1bba2e53f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs @@ -401,7 +401,11 @@ namespace Barotrauma private set; } - private readonly HashSet pendingSectionUpdates = new HashSet(); + + /// + /// Note that sector != section: a sector is a group of sections that are updated together in a single network event. + /// + private readonly HashSet pendingSectorUpdates = new HashSet(); public int xBackgroundMax, yBackgroundMax; @@ -413,8 +417,8 @@ namespace Barotrauma } } - private const int sectorWidth = 4; - private const int sectorHeight = 4; + private const int SectionWidth = 4; + private const int SectionHeight = 4; private const float minColorStrength = 0.0f; private const float maxColorStrength = 0.7f; @@ -808,7 +812,7 @@ namespace Barotrauma int start = sectorToUpdate * BackgroundSectionsPerNetworkEvent; int end = Math.Min((sectorToUpdate + 1) * BackgroundSectionsPerNetworkEvent, BackgroundSections.Count - 1); msg.WriteRangedInteger(sectorToUpdate, 0, BackgroundSections.Count - 1); - for (int i = start; i < end; i++) + for (int i = start; i <= end; i++) { msg.WriteRangedSingle(BackgroundSections[i].ColorStrength, 0.0f, 1.0f, 8); msg.WriteUInt32(BackgroundSections[i].Color.PackedValue); @@ -859,12 +863,12 @@ namespace Barotrauma } } - private void SharedBackgroundSectionRead(IReadMessage msg, Action action, out int sectorToUpdate) + private void SharedBackgroundSectionRead(IReadMessage msg, Action action, out int sectionToUpdate) { - sectorToUpdate = msg.ReadRangedInteger(0, BackgroundSections.Count - 1); - int start = sectorToUpdate * BackgroundSectionsPerNetworkEvent; - int end = Math.Min((sectorToUpdate + 1) * BackgroundSectionsPerNetworkEvent, BackgroundSections.Count - 1); - for (int i = start; i < end; i++) + sectionToUpdate = msg.ReadRangedInteger(0, BackgroundSections.Count - 1); + int start = sectionToUpdate * BackgroundSectionsPerNetworkEvent; + int end = Math.Min((sectionToUpdate + 1) * BackgroundSectionsPerNetworkEvent, BackgroundSections.Count - 1); + for (int i = start; i <= end; i++) { float colorStrength = msg.ReadRangedSingle(0.0f, 1.0f, 8); Color color = new Color(msg.ReadUInt32()); @@ -1460,14 +1464,14 @@ namespace Barotrauma BackgroundSections = new List(xBackgroundMax * yBackgroundMax); int sections = xBackgroundMax * yBackgroundMax; - float xSectors = xBackgroundMax / (float)sectorWidth; + float xSectors = xBackgroundMax / (float)SectionWidth; for (int y = 0; y < yBackgroundMax; y++) { for (int x = 0; x < xBackgroundMax; x++) { ushort index = (ushort)BackgroundSections.Count; - int sector = (int)Math.Floor(index / (float)sectorWidth - xSectors * y) + y / sectorHeight * (int)Math.Ceiling(xSectors); + int sector = (int)Math.Floor(index / (float)SectionWidth - xSectors * y) + y / SectionHeight * (int)Math.Ceiling(xSectors); BackgroundSections.Add(new BackgroundSection(new Rectangle(x * sectionWidth, y * -sectionHeight, sectionWidth, sectionHeight), index, (ushort)y)); } } @@ -1504,6 +1508,12 @@ namespace Barotrauma return BackgroundSections[xIndex + yIndex * xBackgroundMax]; } + public Vector2 GetBackgroundSectionWorldPos(BackgroundSection backgroundSection) + { + Vector2 subOffset = Submarine == null ? Vector2.Zero : Submarine.Position; + return Rect.Location.ToVector2() + subOffset + new Vector2(backgroundSection.Rect.X, backgroundSection.Rect.Y); + } + public IEnumerable GetBackgroundSectionsViaContaining(Rectangle rectArea) { if (BackgroundSections == null || BackgroundSections.Count == 0) @@ -1573,7 +1583,7 @@ namespace Barotrauma if (sectionUpdated && GameMain.NetworkMember != null && requiresUpdate) { networkUpdatePending = true; - pendingSectionUpdates.Add((int)Math.Floor(section.Index / (float)BackgroundSectionsPerNetworkEvent)); + pendingSectorUpdates.Add((int)Math.Floor(section.Index / (float)BackgroundSectionsPerNetworkEvent)); #if CLIENT serverUpdateDelay = 0.5f; #endif diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs index f0d5d35d7..43157965c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs @@ -654,6 +654,10 @@ namespace Barotrauma structure.Update(deltaTime, cam); } + foreach (Gap gap in Gap.GapList) + { + gap.ResetWaterFlowThisFrame(); + } //update gaps in random order, because otherwise in rooms with multiple gaps //the water/air will always tend to flow through the first gap in the list, //which may lead to weird behavior like water draining down only through diff --git a/Barotrauma/BarotraumaShared/SharedSource/NetStructBitField.cs b/Barotrauma/BarotraumaShared/SharedSource/NetStructBitField.cs index 7398a7f21..73cd53d8b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/NetStructBitField.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/NetStructBitField.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System; using System.Collections.Generic; @@ -99,7 +99,7 @@ namespace Barotrauma { if (inc.BitPosition >= inc.LengthBits) { - throw new Exception("Failed to find the end of the bit field: end of the message reached."); + throw new NetStructReadException("Failed to find the end of the bit field: end of the message reached."); } currentByte = inc.ReadByte(); bytes.Add(currentByte); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/INetSerializableStruct.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/INetSerializableStruct.cs index 69d121f72..ebb9572ab 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/INetSerializableStruct.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/INetSerializableStruct.cs @@ -1,5 +1,4 @@ #nullable enable - using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -13,6 +12,11 @@ using Microsoft.Xna.Framework; namespace Barotrauma { + public class NetStructReadException : Exception + { + public NetStructReadException(string message, Exception? innerException = null) : base(message, innerException) { } + } + /// /// Marks fields and properties as to be serialized and deserialized by . /// Also contains settings for some types like maximum and minimum values for numbers to reduce bits used. @@ -730,8 +734,15 @@ namespace Barotrauma /// A new struct of type T with fields and properties deserialized public static T Read(IReadMessage inc) where T : INetSerializableStruct { - ReadOnlyBitField bitField = new ReadOnlyBitField(inc); - return ReadInternal(inc, bitField); + try + { + ReadOnlyBitField bitField = new ReadOnlyBitField(inc); + return ReadInternal(inc, bitField); + } + catch (Exception e) + { + throw new NetStructReadException($"Failed to read {nameof(INetSerializableStruct)}", e); + } } public static T ReadInternal(IReadMessage inc, ReadOnlyBitField bitField) where T : INetSerializableStruct @@ -749,7 +760,7 @@ namespace Barotrauma } catch (Exception exception) { - throw new Exception($"Failed to assign" + + throw new NetStructReadException($"Failed to assign" + $" {value ?? "[NULL]"} ({value?.GetType().Name ?? "[NULL]"})" + $" to {typeof(T).Name}.{property.Name} ({property.Type.Name})", exception); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs index a4ef0b132..09573c126 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs @@ -648,6 +648,17 @@ namespace Barotrauma.Networking private set; } + /// + /// Does the server allow interacting with NPCs that offer services (e.g. stores) remotely? + /// Can be enabled if you're using mods that allow remote interactions - disabled by default to prevent modified clients from cheating. + /// + [Serialize(false, IsPropertySaveable.Yes)] + public bool AllowRemoteCampaignInteractions + { + get; + private set; + } = false; + private bool voiceChatEnabled; [Serialize(true, IsPropertySaveable.Yes)] public bool VoiceChatEnabled diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index de28279f8..105a78eed 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,4 +1,14 @@ ------------------------------------------------------------------------------------------------------------------------------------------------- +v1.10.6.0 +------------------------------------------------------------------------------------------------------------------------------------------------- + +- Fixed contextual orders no longer working even if you were within radio/speaking range of a bot. +- Fixes to several multiplayer exploits, including one that allowed clients to cause server-side lag by sending specifically crafted messages to the server, even if the server didn't allow them to join. +- Fixed a bug caused by the water flow changes in the previous update: water flow through gaps in inside the submarine was much faster than intended when there were multiple breaches on the submarine's outer hull. +- Reduced the water flow through gaps when the hulls on are not linked. Linked hulls are intended to behave as if they were one room, and should allow unrestricted water flow. +- Railgun shells now disappear shortly after the first impact: the change in the previous update (which allowed them to penetrate multiple limbs/targets) had the side-effect that a shell that went through a single limb could fall back on the submarine and still do full damage. + +------------------------------------------------------------------------------------------------------------------------------------------------- v1.10.5.0 -------------------------------------------------------------------------------------------------------------------------------------------------