From 513872c1b013df2f02929b4e6147465bb536183c Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 20 Aug 2025 13:30:45 +0300 Subject: [PATCH 1/4] Update bug-reports.yml --- .github/DISCUSSION_TEMPLATE/bug-reports.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/DISCUSSION_TEMPLATE/bug-reports.yml b/.github/DISCUSSION_TEMPLATE/bug-reports.yml index 1b70e7070..180509477 100644 --- a/.github/DISCUSSION_TEMPLATE/bug-reports.yml +++ b/.github/DISCUSSION_TEMPLATE/bug-reports.yml @@ -73,7 +73,8 @@ 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.9.8.0 (Summer Update Hotfix 1) + - v1.9.8.0 (Summer Update Hotfix 1) + - v1.10.1.0 (unstable) - Other validations: required: true From d13836ce878b3f319dc044b9ed8f5204c926262c Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 28 Aug 2025 11:47:29 +0300 Subject: [PATCH 2/4] Update bug-reports.yml --- .github/DISCUSSION_TEMPLATE/bug-reports.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/DISCUSSION_TEMPLATE/bug-reports.yml b/.github/DISCUSSION_TEMPLATE/bug-reports.yml index 180509477..ef6294c28 100644 --- a/.github/DISCUSSION_TEMPLATE/bug-reports.yml +++ b/.github/DISCUSSION_TEMPLATE/bug-reports.yml @@ -74,7 +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.9.8.0 (Summer Update Hotfix 1) - - v1.10.1.0 (unstable) + - v1.10.2.0 (unstable) - Other validations: required: true From caa0326cf87f7d1f55e04e7e7bcfc8055dc2f62e Mon Sep 17 00:00:00 2001 From: Regalis11 Date: Wed, 17 Sep 2025 13:44:21 +0300 Subject: [PATCH 3/4] Release 1.10.5.0 - Autumn Update 2025 --- .../ClientSource/Characters/CharacterInfo.cs | 15 +- .../DebugConsole.ShowSoldItems.cs | 174 ++++++++ .../ClientSource/DebugConsole.cs | 21 +- .../Events/Missions/GoToMission.cs | 5 +- .../Events/Missions/SalvageMission.cs | 4 +- .../ClientSource/GUI/LoadingScreen.cs | 26 +- .../ClientSource/GUI/Store.cs | 36 +- .../ClientSource/GUI/UpgradeStore.cs | 8 +- .../BarotraumaClient/ClientSource/GameMain.cs | 7 + .../ClientSource/GameSession/CrewManager.cs | 3 +- .../GameModes/SinglePlayerCampaign.cs | 13 +- .../Items/Components/ItemContainer.cs | 47 ++- .../Items/Components/Machines/MiniMap.cs | 17 +- .../Items/Components/Machines/Pump.cs | 12 +- .../ClientSource/Items/Components/Rope.cs | 4 +- .../Items/Components/StatusHUD.cs | 9 +- .../ClientSource/Items/Inventory.cs | 16 +- .../ClientSource/Map/Structure.cs | 8 +- .../ClientSource/Map/Submarine.cs | 47 ++- .../Networking/FileTransfer/FileReceiver.cs | 9 + .../ClientSource/Networking/GameClient.cs | 6 +- .../P2PSocket/DualStackP2PSocket.cs | 27 +- .../Primitives/P2PSocket/EosP2PSocket.cs | 19 +- .../Primitives/P2PSocket/P2PSocket.cs | 15 +- .../P2PSocket/SteamConnectSocket.cs | 6 +- .../Primitives/P2PSocket/SteamListenSocket.cs | 50 ++- .../Primitives/Peers/P2PClientPeer.cs | 13 +- .../Primitives/Peers/P2POwnerPeer.cs | 27 +- .../Networking/ServerList/ServerInfo.cs | 2 +- .../ClientSource/Networking/ServerLog.cs | 64 ++- .../ClientSource/Physics/PhysicsBody.cs | 2 +- .../Screens/EventEditor/EditorNode.cs | 32 +- .../EventEditor/EventConversationNode.cs | 398 ++++++++++++++++++ .../Screens/EventEditor/EventEditorScreen.cs | 202 +++++++-- .../ClientSource/Screens/GameScreen.cs | 19 +- .../ClientSource/Screens/NetLobbyScreen.cs | 17 +- .../ClientSource/Screens/SlideshowPlayer.cs | 6 +- .../ClientSource/Screens/SubEditorScreen.cs | 162 ++++--- .../ClientSource/SpamServerFilter.cs | 32 +- .../Utils/P2POwnerDoSProtection.cs | 93 ++++ .../BarotraumaClient/LinuxClient.csproj | 2 +- Barotrauma/BarotraumaClient/MacClient.csproj | 2 +- .../BarotraumaClient/WindowsClient.csproj | 2 +- .../BarotraumaServer/LinuxServer.csproj | 2 +- Barotrauma/BarotraumaServer/MacServer.csproj | 2 +- .../ServerSource/DebugConsole.cs | 4 + .../GameModes/MultiPlayerCampaign.cs | 7 +- .../Items/Components/Machines/Pump.cs | 3 +- .../Items/Components/Signal/WifiComponent.cs | 19 +- .../ServerSource/Items/Inventory.cs | 13 +- .../ServerSource/Networking/GameServer.cs | 11 +- .../Primitives/Peers/Server/P2PServerPeer.cs | 52 ++- .../Primitives/Peers/Server/ServerPeer.cs | 58 ++- .../BarotraumaServer/WindowsServer.csproj | 2 +- .../Dugong_PipeTest.sub | Bin 0 -> 216810 bytes .../[DebugOnlyTest]PipeTestSub/filelist.xml | 4 + .../[DebugOnlyTest]testGapSub/filelist.xml | 4 + .../[DebugOnlyTest]testGapSub/testGapSub.sub | Bin 0 -> 74550 bytes .../ModLists/Release checklist mods.xml | 1 - .../Characters/AI/AIController.cs | 3 +- .../Characters/AI/HumanAIController.cs | 34 ++ .../Characters/AI/NPCConversation.cs | 3 +- .../AI/Objectives/AIObjectiveCleanupItems.cs | 1 + .../AI/Objectives/AIObjectiveCombat.cs | 19 +- .../AI/Objectives/AIObjectiveManager.cs | 7 + .../SharedSource/Characters/AI/Order.cs | 2 + .../SharedSource/Characters/AI/PetBehavior.cs | 16 + .../SharedSource/Characters/AICharacter.cs | 2 +- .../Characters/Animation/AnimController.cs | 14 +- .../SharedSource/Characters/Character.cs | 18 +- .../Characters/Health/CharacterHealth.cs | 3 + .../ContentFile/CharacterFile.cs | 2 + .../SharedSource/DebugConsole.cs | 6 +- .../Events/EventActions/MissionStateAction.cs | 10 +- .../Events/EventActions/SpawnAction.cs | 45 +- .../Events/Missions/BeaconMission.cs | 26 ++ .../SharedSource/Events/Missions/Mission.cs | 60 ++- .../Events/Missions/SalvageMission.cs | 32 +- .../SharedSource/GameSession/CargoManager.cs | 10 +- .../GameSession/GameModes/CampaignMode.cs | 5 +- .../SharedSource/GameSession/GameSession.cs | 17 +- .../SharedSource/Items/CharacterInventory.cs | 21 +- .../Items/Components/Holdable/Throwable.cs | 12 +- .../Items/Components/ItemComponent.cs | 3 + .../Items/Components/ItemContainer.cs | 6 + .../Items/Components/Machines/Controller.cs | 5 +- .../Components/Machines/OxygenGenerator.cs | 5 +- .../Items/Components/Machines/Pump.cs | 108 +++-- .../Components/Power/PowerDistributor.cs | 5 + .../Items/Components/TriggerComponent.cs | 59 ++- .../SharedSource/Items/Item.cs | 76 ++-- .../SharedSource/Items/ItemPrefab.cs | 9 + .../SharedSource/Items/RelatedItem.cs | 20 +- .../SharedSource/Map/Explosion.cs | 5 +- .../BarotraumaShared/SharedSource/Map/Gap.cs | 118 +++++- .../BarotraumaShared/SharedSource/Map/Hull.cs | 21 +- .../SharedSource/Map/Levels/Level.cs | 24 +- .../SharedSource/Map/Map/Location.cs | 8 +- .../Map/Map/LocationConnection.cs | 2 +- .../SharedSource/Map/Map/Map.cs | 8 +- .../Map/Outposts/ExtraSubmarineInfo.cs | 31 +- .../Map/Outposts/OutpostGenerator.cs | 6 +- .../SharedSource/Map/PriceInfo.cs | 69 ++- .../SharedSource/Map/Submarine.cs | 15 + .../SharedSource/Map/SubmarineBody.cs | 24 +- .../Networking/INetSerializableStruct.cs | 10 +- .../Primitives/NetworkExtensions.cs | 2 +- .../Primitives/NetworkPeerStructs.cs | 23 +- .../SharedSource/Networking/ServerLog.cs | 2 - .../Prefabs/IImplementsVariants.cs | 11 +- .../SerializableProperty.cs | 22 +- .../SharedSource/Settings/GameSettings.cs | 3 +- .../StatusEffects/PropertyConditional.cs | 5 + .../StatusEffects/StatusEffect.cs | 157 +++---- .../SharedSource/Text/RichString.cs | 3 +- .../SharedSource/Utils/SafeIO.cs | 21 +- .../SharedSource/Utils/SaveUtil.cs | 17 +- Barotrauma/BarotraumaShared/changelog.txt | 76 ++++ .../Networking/Primitives/NetworkEnums.cs | 8 +- .../Graphics/SpriteBatch.cs | 3 +- 120 files changed, 2584 insertions(+), 635 deletions(-) create mode 100644 Barotrauma/BarotraumaClient/ClientSource/DebugConsole.ShowSoldItems.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EventConversationNode.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/Utils/P2POwnerDoSProtection.cs create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]PipeTestSub/Dugong_PipeTest.sub create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]PipeTestSub/filelist.xml create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]testGapSub/filelist.xml create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]testGapSub/testGapSub.sub diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs index 028b373b2..fafc4aaf9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs @@ -419,7 +419,12 @@ namespace Barotrauma float scale = Math.Min(targetAreaSize.X / headSprite.size.X, targetAreaSize.Y / headSprite.size.Y); headSprite.SourceRect = new Rectangle(CalculateOffset(headSprite, Head.SheetIndex.ToPoint()), headSprite.SourceRect.Size); SetHeadEffect(spriteBatch); - headSprite.Draw(spriteBatch, screenPos, scale: scale, color: Head.SkinColor, spriteEffect: spriteEffects); + Vector2 origin = headSprite.Origin; + if (flip) + { + origin.X = headSprite.size.X - origin.X; + } + headSprite.Draw(spriteBatch, screenPos, origin: origin, scale: scale, color: Head.SkinColor, spriteEffect: spriteEffects); if (AttachmentSprites != null) { float depthStep = 0.000001f; @@ -467,6 +472,14 @@ namespace Barotrauma { origin = head.Origin; attachment.Sprite.Origin = origin; + if (spriteEffects.HasFlag(SpriteEffects.FlipHorizontally)) + { + origin.X = head.size.X - origin.X; + } + if (spriteEffects.HasFlag(SpriteEffects.FlipVertically)) + { + origin.Y = head.size.Y - origin.Y; + } } else { diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.ShowSoldItems.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.ShowSoldItems.cs new file mode 100644 index 000000000..16550e581 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.ShowSoldItems.cs @@ -0,0 +1,174 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma +{ + internal static partial class DebugConsole + { + private static void InitShowSoldItems() + { + commands.Add(new Command("showsolditems", + "showsolditems [filter (no-defined/only-min/only-max/name:pattern)] [Include stores (true/false)] [Sold only (true/false)] [limit (number)] [Hide store overrides from output. (true/false)]: " + + "Lists items and their shop availability settings. Filter can be availability filter or name pattern (e.g. 'name:*rifle*'). Include stores controls whether to check store-specific overrides (default true). Sold only controls whether to show only sold items (default true). Limit parameter controls how many items to show (default 50). Hide store overrides from output, defaults to false.", + (string[] args) => + { + string filter = args.Length > 0 ? args[0].ToLowerInvariant() : null; + bool includeStores = args.Length <= 1 || !args[1].Equals("false", StringComparison.InvariantCultureIgnoreCase); + bool soldOnly = args.Length <= 2 || !args[2].Equals("false", StringComparison.InvariantCultureIgnoreCase); + int limit = 50; + if (args.Length > 3 && int.TryParse(args[3], out int parsedLimit)) + { + limit = Math.Max(1, parsedLimit); + } + bool hideStoreOverrides = args.Length > 4 && args[4].Equals("true", StringComparison.InvariantCultureIgnoreCase); + + var itemsWithPrice = ItemPrefab.Prefabs + .Where(item => item.ConfigElement.Element.Element("Price") != null); + + // apply filtering + var matchingItems = itemsWithPrice + .OrderBy(i => i.Name.Value) + .Where(item => MatchesFilter(item, filter, includeStores, soldOnly)) + .ToList(); + + // output results + NewMessage("=== Shop Item Availability ===", Color.Cyan); + NewMessage($"Filter: {filter ?? "all"}, IncludeStores: {includeStores}, SoldOnly: {soldOnly}, Limit: {limit}, HideStoreOverrides: {hideStoreOverrides}", Color.Yellow); + NewMessage($"Items: {matchingItems.Count} matching out of {itemsWithPrice.Count()} being sold (showing first {Math.Min(limit, matchingItems.Count)})", Color.LightGreen); + NewMessage("", Color.White); + + foreach (var item in matchingItems.Take(limit)) + { + PrintItemInfo(item, hideStoreOverrides); + } + }, + getValidArgs: () => + [ + ["all", "no-defined", "only-min", "only-max", "name:*"], // filter + ["true", "false"], // includeStores + ["true", "false"], // soldOnly + ["10", "25", "50", "100"], // limit suggestions + ["false", "true"] // hidestoreoverrides + ])); + } + + private static bool MatchesFilter(ItemPrefab item, string filter, bool includeStores, bool soldOnly) + { + var priceElement = item.ConfigElement.Element.Element("Price"); + if (priceElement == null) { return false; } // No price = not sold = don't include + if (!includeStores) { return MatchesPriceElement(priceElement, item, filter, soldOnly); } + + // Check if Base element matches first... + if (MatchesPriceElement(priceElement, item, filter, soldOnly)) { return true; } + + // ...then check store-specific price element matches + foreach (var storeElement in priceElement.Elements().Where(e => e.Name == "Price")) + { + if (MatchesPriceElement(storeElement, item, filter, soldOnly)) { return true; } + } + + return false; + } + + private static bool MatchesPriceElement(XElement priceEl, ItemPrefab itemPrefab, string filter, bool soldOnly) + { + bool isSold = PriceInfo.GetSold(priceEl, true); + if (soldOnly && !isSold) { return false; } + if (filter == null) { return true; } + + // Handle name pattern matching + if (filter.StartsWith("name:")) + { + string pattern = filter[5..]; + string name = itemPrefab.Name.Value.ToLowerInvariant(); + string identifier = itemPrefab.Identifier.Value.ToLowerInvariant(); + + // If pattern contains '*', treat as wildcard (convert to regex) + if (pattern.Contains('*')) + { + // Escape regex special chars except * + string regexPattern = System.Text.RegularExpressions.Regex.Escape(pattern).Replace("\\*", ".*"); + return System.Text.RegularExpressions.Regex.IsMatch(name, $"^{regexPattern}$") + || System.Text.RegularExpressions.Regex.IsMatch(identifier, $"^{regexPattern}$"); + } + else + { + // No wildcards: match exactly + return name == pattern || identifier == pattern; + } + } + + bool hasMin = PriceInfo.HasMinAmountDefined(priceEl); + bool hasMax = PriceInfo.HasMaxAmountDefined(priceEl); + + // Apply the filter logic + return filter switch + { + "no-defined" => !hasMin && !hasMax, // Neither min nor max defined + "only-min" => hasMin && !hasMax, // Only min defined + "only-max" => !hasMin && hasMax, // Only max defined + _ => true // No filter or show all + }; + } + + private static void PrintItemInfo(ItemPrefab item, bool hideStoreOverrides = false) + { + var priceElement = item.ConfigElement.Element.Element("Price"); + if (priceElement == null) { return; } + + bool hasMinDefined = PriceInfo.HasMinAmountDefined(priceElement); + bool hasMaxDefined = PriceInfo.HasMaxAmountDefined(priceElement); + + string minRaw = PriceInfo.GetMinAmountString(priceElement); + string maxRaw = PriceInfo.GetMaxAmountString(priceElement); + int minLevelDifficulty = PriceInfo.GetMinLevelDifficulty(priceElement, 0); + + // Get the resolved values (what PriceInfo would actually use) + var priceInfo = new PriceInfo(priceElement); + int resolvedMin = priceInfo.MinAvailableAmount; + int resolvedMax = priceInfo.MaxAvailableAmount; + + string minStatus = hasMinDefined ? $"XML:{minRaw}" : "DEFAULT:1"; + string maxStatus = hasMaxDefined ? $"XML:{maxRaw}" : "DEFAULT:5"; + + string minLevelInfo = minLevelDifficulty > 0 ? $" | MinLvl: {minLevelDifficulty}" : ""; + NewMessage($"{item.Name} ({item.Identifier}) | Min: {minStatus} → {resolvedMin} | Max: {maxStatus} → {resolvedMax}{minLevelInfo}", Color.White); + + if (hideStoreOverrides) { return; } + + var storeOverrides = priceElement.Elements().Where(e => e.Name == "Price") + .Select(p => { + string storeId = PriceInfo.GetStoreIdentifier(p, "unknown"); + string storeMin = PriceInfo.GetMinAmountString(p); + string storeMax = PriceInfo.GetMaxAmountString(p); + bool? storeSold = PriceInfo.HasSoldDefined(p) ? PriceInfo.GetSold(p, true) : null; + + // Check if this store overrides anything + if (storeMin != null || storeMax != null || storeSold != null) + { + var parts = new List(); + if (storeMin != null || storeMax != null) + { + parts.Add($"min:{storeMin ?? "base"}, max:{storeMax ?? "base"}"); + } + if (storeSold != null) + { + parts.Add($"sold:{storeSold.Value.ToString().ToLowerInvariant()}"); + } + return $"{storeId}({string.Join(", ", parts)})"; + } + return null; + }) + .Where(s => s != null) + .ToList(); + + if (storeOverrides.Count != 0) + { + NewMessage($" Store overrides: {string.Join(", ", storeOverrides)}", Color.Gray); + } + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index 1d00ad15f..360038eed 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs @@ -397,6 +397,8 @@ namespace Barotrauma private static void InitProjectSpecific() { + InitShowSoldItems(); + commands.Add(new Command("eosStat", "Query and display all logged in EOS users. Normally this is at most two users, but in a developer environment it could be more.", args => { if (!EosInterface.Core.IsInitialized) @@ -3466,6 +3468,13 @@ namespace Barotrauma } } })); + + commands.Add(new Command("multiclienttestmode", "Makes the client enable some special logic (such as using a client-specific folder for downloads) to prevent conflicts between multiple clients on the same machine. Useful for testing the campaign with multiple clients running locally.", (string[] args) => + { + GameClient.MultiClientTestMode = !GameClient.MultiClientTestMode; + NewMessage($"{(GameClient.MultiClientTestMode ? "Enabled" : "Disabled")} MultiClientTestMode on the client."); + })); + AssignRelayToServer("multiclienttestmode", false); #endif commands.Add(new Command("reloadcorepackage", "", (string[] args) => @@ -4204,8 +4213,12 @@ namespace Barotrauma NewMessage("Minimum main path width: " + (Level.Loaded.LevelData?.MinMainPathWidth?.ToString() ?? "unknown")); } }); + + } + + private static void ReloadWearables(Character character, int variant = 0) { foreach (var limb in character.AnimController.Limbs) @@ -4442,7 +4455,9 @@ namespace Barotrauma #endif System.Threading.Thread.Sleep(1000); } - +#if DEBUG + GameClient.MultiClientTestMode = true; +#endif GameMain.Client = new GameClient("client1", new LidgrenEndpoint(System.Net.IPAddress.Loopback, NetConfig.DefaultPort), "localhost", Option.None()); @@ -4454,9 +4469,9 @@ namespace Barotrauma { System.Threading.Thread.Sleep(1000); #if WINDOWS - Process.Start("Barotrauma.exe", arguments: "-connect server localhost -username client" + i); + Process.Start("Barotrauma.exe", arguments: "-connect server localhost -username client" + i + " -multiclienttestmode"); #else - Process.Start("./Barotrauma", arguments: "-connect server localhost -username client" + i); + Process.Start("./Barotrauma", arguments: "-connect server localhost -username client" + i + " -multiclienttestmode"); #endif } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/GoToMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/GoToMission.cs index a7a45837c..3559851cc 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/GoToMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/GoToMission.cs @@ -2,7 +2,10 @@ { partial class GoToMission : Mission { - public override bool DisplayAsCompleted => State >= Prefab.MaxProgressState; + public override bool DisplayAsCompleted => + State >= Prefab.MaxProgressState && + //if there's some additional check for completion, don't display as completed until we've checked it and set the mission as completed + (Completed || completeCheckDataAction == null); public override bool DisplayAsFailed => false; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/SalvageMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/SalvageMission.cs index 2009a3819..8b9d8c492 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/SalvageMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/SalvageMission.cs @@ -139,6 +139,8 @@ namespace Barotrauma } } - public override IEnumerable HudIconTargets => targets.Where(static t => !t.Retrieved && t.Item?.GetRootInventoryOwner() is not Character { IsLocalPlayer: true }).Select(static t => t.Item); + public override IEnumerable HudIconTargets => targets + .Where(static t => t.Item != null && !t.Retrieved && t.Item?.GetRootInventoryOwner() is not Character { IsLocalPlayer: true }) + .Select(static t => t.Item); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs index 3231c2c8f..90ca5d713 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs @@ -6,6 +6,7 @@ using Microsoft.Xna.Framework.Input; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; namespace Barotrauma @@ -50,9 +51,15 @@ namespace Barotrauma } private RichString selectedTip; + + private string selectedTipString; + private ImmutableArray? selectedTipRichTextData; + private void SetSelectedTip(LocalizedString tip) { selectedTip = RichString.Rich(tip); + selectedTipString = string.Empty; + selectedTipRichTextData = null; } public float LoadState; @@ -165,13 +172,20 @@ namespace Barotrauma textPos.Y += GUIStyle.LargeFont.MeasureString(loadText.ToUpper()).Y * 1.2f; } - if (GUIStyle.Font.HasValue && selectedTip != null) + if (GUIStyle.Font.HasValue && selectedTip != null && !selectedTip.SanitizedValue.IsNullOrEmpty()) { - string wrappedTip = ToolBox.WrapText(selectedTip.SanitizedValue, GameMain.GraphicsWidth * 0.3f, GUIStyle.Font.Value); - string[] lines = wrappedTip.Split('\n'); - float lineHeight = GUIStyle.Font.MeasureString(selectedTip).Y; + //store the string value of the LocalizedString to prevent the text from changing if/when new text packs are loaded during the loading screen + if (selectedTipString.IsNullOrEmpty()) + { + selectedTipString = selectedTip.SanitizedValue; + selectedTipRichTextData = selectedTip.RichTextData; + } - if (selectedTip.RichTextData != null) + string wrappedTip = ToolBox.WrapText(selectedTipString, GameMain.GraphicsWidth * 0.3f, GUIStyle.Font.Value); + string[] lines = wrappedTip.Split('\n'); + float lineHeight = GUIStyle.Font.MeasureString(selectedTipString).Y; + + if (selectedTipRichTextData != null) { int rtdOffset = 0; for (int i = 0; i < lines.Length; i++) @@ -179,7 +193,7 @@ namespace Barotrauma GUIStyle.Font.DrawStringWithColors(spriteBatch, lines[i], new Vector2(textPos.X, (int)(textPos.Y + i * lineHeight)), Color.White, - 0f, Vector2.Zero, 1f, SpriteEffects.None, 0f, selectedTip.RichTextData.Value, rtdOffset); + 0f, Vector2.Zero, 1f, SpriteEffects.None, 0f, selectedTipRichTextData.Value, rtdOffset); rtdOffset += lines[i].Length; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs index b635014ad..bb806e255 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs @@ -860,26 +860,22 @@ namespace Barotrauma FilterStoreItems(category, searchBox.Text); } - private static KeyValuePair? GetReputationRequirement(PriceInfo priceInfo) + private static float GetReputationRequirement(PriceInfo priceInfo, Identifier faction) { - return GameMain.GameSession?.Campaign is not null - ? priceInfo.MinReputation.FirstOrNull() - : null; + return priceInfo.MinReputation.GetValueOrDefault(faction); } - private static KeyValuePair? GetTooLowReputation(PriceInfo priceInfo) + private static bool ReputationRequirementsMet(PriceInfo priceInfo, Identifier faction) { + if (priceInfo.MinReputation.None()) { return true; } if (GameMain.GameSession?.Campaign is CampaignMode campaign) { - foreach (var minRep in priceInfo.MinReputation) + if (priceInfo.MinReputation.TryGetValue(faction, out float requirement)) { - if (MathF.Round(campaign.GetReputation(minRep.Key)) < minRep.Value) - { - return minRep; - } + return MathF.Round(campaign.GetReputation(faction)) >= requirement; } } - return null; + return false; } int prevDailySpecialCount, prevRequestedGoodsCount, prevSubRequestedGoodsCount; @@ -948,7 +944,7 @@ namespace Barotrauma SetPriceGetters(itemFrame, true); } - SetItemFrameStatus(itemFrame, hasPermissions && quantity > 0 && !GetTooLowReputation(priceInfo).HasValue); + SetItemFrameStatus(itemFrame, hasPermissions && quantity > 0 && ReputationRequirementsMet(priceInfo, ActiveStore.GetMerchantOrLocationFactionIdentifier())); existingItemFrames.Add(itemFrame); } } @@ -1464,8 +1460,9 @@ namespace Barotrauma PriceInfo priceInfo2 = item2.ItemPrefab.GetPriceInfo(ActiveStore); if (priceInfo1 != null && priceInfo2 != null) { - var requiredReputation1 = GetTooLowReputation(priceInfo1)?.Value ?? 0.0f; - var requiredReputation2 = GetTooLowReputation(priceInfo2)?.Value ?? 0.0f; + Identifier faction = ActiveStore.GetMerchantOrLocationFactionIdentifier(); + float requiredReputation1 = ReputationRequirementsMet(priceInfo1, faction) ? 0.0f : GetReputationRequirement(priceInfo1, faction); + float requiredReputation2 = ReputationRequirementsMet(priceInfo2, faction) ? 0.0f : GetReputationRequirement(priceInfo2, faction); return requiredReputation1.CompareTo(requiredReputation2); } return 0; @@ -1942,14 +1939,15 @@ namespace Barotrauma var campaign = GameMain.GameSession?.Campaign; if (priceInfo != null && campaign != null) { - var requiredReputation = GetReputationRequirement(priceInfo); - if (requiredReputation != null) + Identifier faction = ActiveStore.GetMerchantOrLocationFactionIdentifier(); + float requiredReputation = GetReputationRequirement(priceInfo, faction); + if (requiredReputation > 0) { var repStr = TextManager.GetWithVariables( "campaignstore.reputationrequired", - ("[amount]", ((int)requiredReputation.Value.Value).ToString()), - ("[faction]", TextManager.Get("faction." + requiredReputation.Value.Key).Value)); - Color color = MathF.Round(campaign.GetReputation(requiredReputation.Value.Key)) < requiredReputation.Value.Value ? + ("[amount]", ((int)requiredReputation).ToString()), + ("[faction]", TextManager.Get("faction." + faction).Value)); + Color color = MathF.Round(campaign.GetReputation(faction)) < requiredReputation ? GUIStyle.Orange : GUIStyle.Green; toolTip += $"\n‖color:{color.ToStringHex()}‖{repStr}‖color:end‖"; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs index e8e9dd49f..24d45bcdc 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs @@ -1743,12 +1743,12 @@ namespace Barotrauma } } - static void CreateMaterialCosts(GUIListBox list, UpgradePrefab prefab, int targetLevel) + static void CreateMaterialCosts(GUIListBox list, UpgradePrefab upgradePrefab, int targetLevel) { list.Content.ClearChildren(); var allItems = CargoManager.FindAllItemsOnPlayerAndSub(Character.Controlled); - var resources = prefab.GetApplicableResources(targetLevel); + var resources = upgradePrefab.GetApplicableResources(targetLevel); foreach (ApplicableResourceCollection collection in resources) { @@ -1769,7 +1769,7 @@ namespace Barotrauma bool hasItems = collection.Cost.Amount <= allItems.Count(collection.Cost.MatchesItem); - Sprite icon = defaultItemPrefab.InventoryIcon ?? prefab.Sprite; + Sprite icon = defaultItemPrefab.InventoryIcon ?? defaultItemPrefab.Sprite; Color iconColor = defaultItemPrefab.InventoryIcon is null ? defaultItemPrefab.SpriteColor : defaultItemPrefab.InventoryIconColor; GUIImage itemIcon = new GUIImage(new RectTransform(Vector2.One, itemFrame.RectTransform, scaleBasis: ScaleBasis.Smallest, anchor: Anchor.Center), sprite: icon, scaleToFit: true) @@ -1798,7 +1798,7 @@ namespace Barotrauma if (index > length) { index = 0; } ItemPrefab currentPrefab = collection.MatchingItems[(int)MathF.Floor(index)]; - Sprite icon = currentPrefab.InventoryIcon ?? prefab.Sprite; + Sprite icon = currentPrefab.InventoryIcon ?? currentPrefab.Sprite; Color iconColor = currentPrefab.InventoryIcon is null ? currentPrefab.SpriteColor : currentPrefab.InventoryIconColor; itemIcon.Sprite = icon; itemIcon.Color = hasItems ? iconColor : iconColor * 0.9f; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs index 38c159d25..36ee6950d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs @@ -270,6 +270,13 @@ namespace Barotrauma ConnectCommand = Option.None(); } +#if DEBUG + if (ConsoleArguments.Contains("-multiclienttestmode")) + { + DebugConsole.NewMessage("Enabled MultiClientTestMode on the client"); + GameClient.MultiClientTestMode = true; + } +#endif GUI.KeyboardDispatcher = new EventInput.KeyboardDispatcher(Window); PerformanceCounter = new PerformanceCounter(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs index 08bf2e2be..e3a8efb27 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs @@ -2866,10 +2866,11 @@ namespace Barotrauma } contextualOrders.RemoveAll(o => !IsOrderAvailable(o)); var offsets = MathUtils.GetPointsOnCircumference(Vector2.Zero, nodeDistance, contextualOrders.Count, MathHelper.ToRadians(90f + 180f / contextualOrders.Count)); - bool disableNode = !CanCharacterBeHeard(); + bool canCharacterBeHeard = !CanCharacterBeHeard(); for (int i = 0; i < contextualOrders.Count; i++) { var order = contextualOrders[i]; + bool disableNode = !canCharacterBeHeard && !order.TargetAllCharacters; int hotkey = (i + 1) % 10; var component = order.Option.IsEmpty ? CreateOrderNode(nodeSize, commandFrame.RectTransform, offsets[i].ToPoint(), order, hotkey, disableNode: disableNode, checkIfOrderCanBeHeard: false) : diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs index 3c49e47eb..1cec81758 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs @@ -258,9 +258,9 @@ namespace Barotrauma { SlideshowPlayer = new SlideshowPlayer(GUICanvas.Instance, slideshow); } - var outpost = GameMain.GameSession.Level.StartOutpost; - var borders = outpost.GetDockedBorders(); - borders.Location += outpost.WorldPosition.ToPoint(); + var subToFocusTo = GameMain.GameSession.Level.StartOutpost ?? Submarine.MainSub; + var borders = subToFocusTo.GetDockedBorders(); + borders.Location += subToFocusTo.WorldPosition.ToPoint(); GameMain.GameScreen.Cam.Position = new Vector2(borders.X + borders.Width / 2, borders.Y - borders.Height / 2); float startZoom = 0.8f / ((float)Math.Max(borders.Width, borders.Height) / (float)GameMain.GameScreen.Cam.Resolution.X); @@ -646,9 +646,12 @@ namespace Barotrauma modeElement.Add(GameMain.GameSession?.EventManager.Save()); } - foreach (Identifier unlockedRecipe in GameMain.GameSession.UnlockedRecipes) + foreach ((CharacterTeamType team, Identifier unlockedRecipe) in GameMain.GameSession.UnlockedRecipes) { - modeElement.Add(new XElement("unlockedrecipe", new XAttribute("identifier", unlockedRecipe))); + modeElement.Add( + new XElement("unlockedrecipe", + new XAttribute("identifier", unlockedRecipe), + new XAttribute("team", team))); } //save and remove all items that are in someone's inventory so they don't get included in the sub file as well diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs index da3a36e6a..c0a6ece27 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs @@ -197,8 +197,8 @@ namespace Barotrauma.Items.Components }; LocalizedString labelText = GetUILabel(); - GUITextBlock label = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform, Anchor.TopCenter), - labelText, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft, wrap: true) + GUITextBlock label = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform, Anchor.TopLeft), + labelText, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.TopLeft, wrap: true) { IgnoreLayoutGroups = true }; @@ -206,6 +206,8 @@ namespace Barotrauma.Items.Components int buttonSize = GUIStyle.ItemFrameTopBarHeight; Point margin = new Point(buttonSize / 4, buttonSize / 6); + int buttonCount = 0; + GUILayoutGroup buttonArea = new GUILayoutGroup(new RectTransform(new Point(content.Rect.Width, buttonSize - margin.Y * 2), content.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(0, margin.Y) }, isHorizontal: true, childAnchor: Anchor.TopRight) { @@ -213,24 +215,37 @@ namespace Barotrauma.Items.Components }; if (Inventory.Capacity > 1) { - new GUIButton(new RectTransform(Vector2.One, buttonArea.RectTransform, scaleBasis: ScaleBasis.Smallest), style: "SortItemsButton") + if (ShowSortButton) { - ToolTip = TextManager.Get("SortItemsAlphabetically"), - OnClicked = (btn, userdata) => + buttonCount++; + new GUIButton(new RectTransform(Vector2.One, buttonArea.RectTransform, scaleBasis: ScaleBasis.Smallest), style: "SortItemsButton") { - SortItems(); - return true; - } - }; - new GUIButton(new RectTransform(Vector2.One, buttonArea.RectTransform, scaleBasis: ScaleBasis.Smallest), style: "MergeStacksButton") + ToolTip = TextManager.Get("SortItemsAlphabetically"), + OnClicked = (btn, userdata) => + { + SortItems(); + return true; + } + }; + } + if (ShowMergeButton) { - ToolTip = TextManager.Get("MergeItemStacks"), - OnClicked = (btn, userdata) => + buttonCount++; + new GUIButton(new RectTransform(Vector2.One, buttonArea.RectTransform, scaleBasis: ScaleBasis.Smallest), style: "MergeStacksButton") { - MergeStacks(); - return true; - } - }; + ToolTip = TextManager.Get("MergeItemStacks"), + OnClicked = (btn, userdata) => + { + MergeStacks(); + return true; + } + }; + } + } + + if (buttonCount > 0) + { + label.RectTransform.MaxSize = new Point(label.Parent.Rect.Width - buttonCount * buttonSize, int.MaxValue); } float minInventoryAreaSize = 0.5f; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs index fafa2ee3f..3c08f7fe7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs @@ -1078,7 +1078,7 @@ namespace Barotrauma.Items.Components if (hullData is null) { hullData = new HullData(); - GetLinkedHulls(hull, hullData.LinkedHulls); + hull.GetLinkedHulls(hullData.LinkedHulls); hullDatas.Add(hull, hullData); } @@ -1586,19 +1586,6 @@ namespace Barotrauma.Items.Components } } - public static void GetLinkedHulls(Hull hull, List linkedHulls) - { - foreach (var linkedEntity in hull.linkedTo) - { - if (linkedEntity is Hull linkedHull) - { - if (linkedHulls.Contains(linkedHull) || linkedHull.IsHidden) { continue; } - linkedHulls.Add(linkedHull); - GetLinkedHulls(linkedHull, linkedHulls); - } - } - } - public static GUIFrame CreateMiniMap(Submarine sub, GUIComponent parent, MiniMapSettings settings) { return CreateMiniMap(sub, parent, settings, null, out _); @@ -1791,7 +1778,7 @@ namespace Barotrauma.Items.Components if (combinedHulls.ContainsKey(hull) || combinedHulls.Values.Any(hh => hh.Contains(hull))) { continue; } List linkedHulls = new List(); - GetLinkedHulls(hull, linkedHulls); + hull.GetLinkedHulls(linkedHulls); linkedHulls.Remove(hull); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Pump.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Pump.cs index 2c0adbcb6..e2afb09df 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Pump.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Pump.cs @@ -133,7 +133,7 @@ namespace Barotrauma.Items.Components partial void UpdateProjSpecific(float deltaTime) { - if (FlowPercentage < 0.0f) + if (currFlow < 0f) { foreach (var (position, emitter) in pumpOutEmitters) { @@ -154,10 +154,10 @@ namespace Barotrauma.Items.Components } emitter.Emit(deltaTime, item.WorldPosition + relativeParticlePos, item.CurrentHull, angle, - velocityMultiplier: MathHelper.Lerp(0.5f, 1.0f, -FlowPercentage / 100.0f)); + velocityMultiplier: MathHelper.Lerp(0.5f, 1.0f, -currFlow / maxFlow)); } } - else if (FlowPercentage > 0.0f) + else if (currFlow > 0f) { foreach (var (position, emitter) in pumpInEmitters) { @@ -174,7 +174,7 @@ namespace Barotrauma.Items.Components relativeParticlePos.Y = -relativeParticlePos.Y; } emitter.Emit(deltaTime, item.WorldPosition + relativeParticlePos, item.CurrentHull, angle, - velocityMultiplier: MathHelper.Lerp(0.5f, 1.0f, FlowPercentage / 100.0f)); + velocityMultiplier: MathHelper.Lerp(0.5f, 1.0f, currFlow / maxFlow)); } } } @@ -185,7 +185,7 @@ namespace Barotrauma.Items.Components { autoControlIndicator.Selected = IsAutoControlled; PowerButton.Enabled = isActiveLockTimer <= 0.0f; - if (HasPower) + if (HasPower && !Disabled) { flickerTimer = 0; powerLight.Selected = IsActive; @@ -229,6 +229,7 @@ namespace Barotrauma.Items.Components float flowPercentage = msg.ReadRangedInteger(-10, 10) * 10.0f; bool isActive = msg.ReadBoolean(); bool hijacked = msg.ReadBoolean(); + bool disabled = msg.ReadBoolean(); float? targetLevel; if (msg.ReadBoolean()) { @@ -250,6 +251,7 @@ namespace Barotrauma.Items.Components FlowPercentage = flowPercentage; IsActive = isActive; Hijacked = hijacked; + Disabled = disabled; TargetLevel = targetLevel; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Rope.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Rope.cs index 32a3d1528..34764275d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Rope.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Rope.cs @@ -34,8 +34,8 @@ namespace Barotrauma.Items.Components set; } - [Serialize("0.5,0.5)", IsPropertySaveable.No)] - public Vector2 Origin { get; set; } = new Vector2(0.5f, 0.5f); + [Serialize("0.5,0.5", IsPropertySaveable.No)] + public Vector2 Origin { get; set; } [Serialize(true, IsPropertySaveable.No, description: "")] public bool BreakFromMiddle diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs index 078f49ebd..9b999dbae 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs @@ -314,9 +314,12 @@ namespace Barotrauma.Items.Components textColors.Add(GUIStyle.Orange); } - int oxygenTextIndex = MathHelper.Clamp((int)Math.Floor((1.0f - (target.Oxygen / 100.0f)) * OxygenTexts.Length), 0, OxygenTexts.Length - 1); - texts.Add(OxygenTexts[oxygenTextIndex]); - textColors.Add(Color.Lerp(GUIStyle.Red, GUIStyle.Green, target.Oxygen / 100.0f)); + if (target.NeedsOxygen) + { + int oxygenTextIndex = MathHelper.Clamp((int)Math.Floor((1.0f - (target.Oxygen / 100.0f)) * OxygenTexts.Length), 0, OxygenTexts.Length - 1); + texts.Add(OxygenTexts[oxygenTextIndex]); + textColors.Add(Color.Lerp(GUIStyle.Red, GUIStyle.Green, target.Oxygen / 100.0f)); + } if (target.Bleeding > 0.0f) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs index fd2dc5ef0..21f681e5e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs @@ -359,7 +359,7 @@ namespace Barotrauma #endif if (!item.Prefab.UnlockedRecipeInToolTip.IsEmpty && GameMain.GameSession is { } GameSession) { - if (GameSession.UnlockedRecipes.Contains(item.Prefab.UnlockedRecipeInToolTip)) + if (GameSession.HasUnlockedRecipe(Character.Controlled, item.Prefab.UnlockedRecipeInToolTip)) { toolTip += TextManager.Get("unlockedrecipe.true"); } @@ -1435,8 +1435,20 @@ namespace Barotrauma { if (giver == null || receiver == null || draggedItems.None()) { return false; } if (receiver == giver) { return false; } + + CharacterInventory.AccessLevel accessLevel; + if (draggedItems.Any(it => it.HasTag(Tags.HandLockerItem))) + { + //handcuffs can't be given to players by dragging and dropping (because it can allow handcuffing them) + accessLevel = CharacterInventory.AccessLevel.AllowBotsAndPets; + } + else + { + accessLevel = IsDragAndDropGiveAllowed ? CharacterInventory.AccessLevel.AllowFriendly : CharacterInventory.AccessLevel.AllowBotsAndPets; + } + return - receiver.IsInventoryAccessibleTo(giver, IsDragAndDropGiveAllowed ? CharacterInventory.AccessLevel.Allowed : CharacterInventory.AccessLevel.Limited) && + receiver.IsInventoryAccessibleTo(giver, accessLevel) && receiver.Inventory.CanBePut(draggedItems.FirstOrDefault(), InvSlotType.Any); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs index a537a930c..e15d5bd48 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs @@ -501,10 +501,14 @@ namespace Barotrauma { float newCutoff = MathHelper.Lerp(0.0f, 0.65f, Sections[i].damage / MaxHealth); - if (Math.Abs(newCutoff - Submarine.DamageEffectCutoff) > 0.05f) + //change the parameters of the damage effect and start a new sprite batch if the damage is different by 5% or more + if (Math.Abs(newCutoff - Submarine.DamageEffectCutoff) > 0.01f || + //if we were previously rendering some small amount of damage but now 0 damage, make sure we update the parameters + //"no damage" vs "just a tiny fraction of damage" makes a difference, even though normally 5% differences in damage aren't noticeable + MathUtils.NearlyEqual(newCutoff, 0.0f) != MathUtils.NearlyEqual(Submarine.DamageEffectCutoff, 0.0f)) { spriteBatch.End(); - spriteBatch.Begin(SpriteSortMode.BackToFront, + spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.LinearWrap, null, null, damageEffect, diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs index aa50745c8..bbb921dcf 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs @@ -160,37 +160,39 @@ namespace Barotrauma public static float DamageEffectCutoff; + private static readonly List depthSortedDamageable = new List(); + public static void DrawDamageable(SpriteBatch spriteBatch, Effect damageEffect, bool editing = false, Predicate predicate = null) { - if (!editing && visibleEntities != null) + var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList; + + depthSortedDamageable.Clear(); + + //insertion sort according to draw depth + foreach (MapEntity e in entitiesToRender) { - foreach (MapEntity e in visibleEntities) + if (e is Structure structure && structure.DrawDamageEffect) { - if (e is Structure structure && structure.DrawDamageEffect) + if (predicate != null) { - if (predicate != null) - { - if (!predicate(structure)) { continue; } - } - structure.DrawDamage(spriteBatch, damageEffect, editing); + if (!predicate(e)) { continue; } } - } - } - else - { - foreach (Structure structure in Structure.WallList) - { - if (structure.DrawDamageEffect) + float drawDepth = structure.GetDrawDepth(); + int i = 0; + while (i < depthSortedDamageable.Count) { - if (predicate != null) - { - if (!predicate(structure)) { continue; } - } - structure.DrawDamage(spriteBatch, damageEffect, editing); + float otherDrawDepth = depthSortedDamageable[i].GetDrawDepth(); + if (otherDrawDepth < drawDepth) { break; } + i++; } + depthSortedDamageable.Insert(i, structure); } } + foreach (Structure s in depthSortedDamageable) + { + s.DrawDamage(spriteBatch, damageEffect, editing); + } } public static void DrawPaintedColors(SpriteBatch spriteBatch, bool editing = false, Predicate predicate = null) @@ -287,7 +289,7 @@ namespace Barotrauma if (combinedHulls.ContainsKey(hull) || combinedHulls.Values.Any(hh => hh.Contains(hull))) { continue; } List linkedHulls = new List(); - MiniMap.GetLinkedHulls(hull, linkedHulls); + hull.GetLinkedHulls(linkedHulls); linkedHulls.Remove(hull); @@ -297,7 +299,6 @@ namespace Barotrauma { combinedHulls.Add(hull, new HashSet()); } - combinedHulls[hull].Add(linkedHull); } } @@ -567,6 +568,8 @@ namespace Barotrauma { if (item.GetComponent() is not OxygenGenerator oxygenGenerator) { continue; } + oxygenGenerator.GetVents(); + Dictionary hullOxygenFlow = new Dictionary(); foreach (var linkedTo in item.linkedTo) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/FileTransfer/FileReceiver.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/FileTransfer/FileReceiver.cs index 5bda4ceb1..c47244bf5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/FileTransfer/FileReceiver.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/FileTransfer/FileReceiver.cs @@ -256,6 +256,15 @@ namespace Barotrauma.Networking } string downloadFolder = downloadFolders[(FileTransferType)fileType]; +#if CLIENT && DEBUG + if (GameClient.MultiClientTestMode) + { + //append the name of the client to the download folder to avoid multiple clients + //from trying to download a file into the same path at the same time + downloadFolder += "_" + GameMain.Client.Name; + } +#endif + if (!Directory.Exists(downloadFolder)) { try diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs index d8b8c7f54..3c6b7df5a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs @@ -11,7 +11,6 @@ using System.Globalization; using System.Linq; using System.Threading.Tasks; using System.Xml.Linq; -using Barotrauma.PerkBehaviors; namespace Barotrauma.Networking { @@ -26,6 +25,8 @@ namespace Barotrauma.Networking #if DEBUG public float DebugServerVoipAmplitude; + + public static bool MultiClientTestMode; #endif public override Voting Voting { get; } @@ -873,8 +874,9 @@ namespace Barotrauma.Networking ReadAchievement(inc); break; case ServerPacketHeader.UNLOCKRECIPE: + CharacterTeamType team = (CharacterTeamType)inc.ReadByte(); Identifier identifier = inc.ReadIdentifier(); - GameMain.GameSession.UnlockRecipe(identifier, showNotifications: true); + GameMain.GameSession?.UnlockRecipe(team, identifier, showNotifications: true); break; case ServerPacketHeader.ACHIEVEMENT_STAT: ReadAchievementStat(inc); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/P2PSocket/DualStackP2PSocket.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/P2PSocket/DualStackP2PSocket.cs index 613767851..9ac3b368f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/P2PSocket/DualStackP2PSocket.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/P2PSocket/DualStackP2PSocket.cs @@ -10,30 +10,31 @@ sealed class DualStackP2PSocket : P2PSocket private DualStackP2PSocket( Callbacks callbacks, Option eosSocket, - Option steamSocket) : - base(callbacks) + Option steamSocket, + OwnerOrClient type) : + base(callbacks, type) { this.eosSocket = eosSocket; this.steamSocket = steamSocket; } - public static Result Create(Callbacks callbacks) + public static Result Create(Callbacks callbacks, OwnerOrClient type) { - var eosP2PSocketResult = EosP2PSocket.Create(callbacks); - var steamP2PSocketResult = SteamListenSocket.Create(callbacks); + var eosP2PSocketResult = EosP2PSocket.Create(callbacks, type); + var steamP2PSocketResult = SteamListenSocket.Create(callbacks, type); if (eosP2PSocketResult.TryUnwrapFailure(out var eosError) && steamP2PSocketResult.TryUnwrapFailure(out var steamError)) { return Result.Failure(new Error(eosError, steamError)); } - return Result.Success((P2PSocket)new DualStackP2PSocket( - callbacks, - eosP2PSocketResult.TryUnwrapSuccess(out var eosP2PSocket) - ? Option.Some((EosP2PSocket)eosP2PSocket) - : Option.None, - steamP2PSocketResult.TryUnwrapSuccess(out var steamP2PSocket) - ? Option.Some((SteamListenSocket)steamP2PSocket) - : Option.None)); + return Result.Success(new DualStackP2PSocket( + callbacks, + eosP2PSocketResult.TryUnwrapSuccess(out var eosP2PSocket) + ? Option.Some((EosP2PSocket)eosP2PSocket) + : Option.None, + steamP2PSocketResult.TryUnwrapSuccess(out var steamP2PSocket) + ? Option.Some((SteamListenSocket)steamP2PSocket) + : Option.None, type)); } public override void ProcessIncomingMessages() diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/P2PSocket/EosP2PSocket.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/P2PSocket/EosP2PSocket.cs index 73531529b..985e84221 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/P2PSocket/EosP2PSocket.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/P2PSocket/EosP2PSocket.cs @@ -8,13 +8,14 @@ sealed class EosP2PSocket : P2PSocket private EosP2PSocket( Callbacks callbacks, - EosInterface.P2PSocket eosSocket) - : base(callbacks) + EosInterface.P2PSocket eosSocket, + OwnerOrClient type) + : base(callbacks, type) { this.eosSocket = eosSocket; } - public static Result Create(Callbacks callbacks) + public static Result Create(Callbacks callbacks, OwnerOrClient type) { if (!EosInterface.Core.IsInitialized) { return Result.Failure(new Error(ErrorCode.EosNotInitialized)); } @@ -26,19 +27,25 @@ sealed class EosP2PSocket : P2PSocket var socketCreateResult = EosInterface.P2PSocket.Create(puids[0], eosSocketId); if (!socketCreateResult.TryUnwrapSuccess(out var eosSocket)) { return Result.Failure(new Error(ErrorCode.FailedToCreateEosP2PSocket, socketCreateResult.ToString())); } - var retVal = new EosP2PSocket(callbacks, eosSocket); + var retVal = new EosP2PSocket(callbacks, eosSocket, type); eosSocket.HandleIncomingConnection.Register("Event".ToIdentifier(), retVal.OnIncomingConnection); eosSocket.HandleClosedConnection.Register("Event".ToIdentifier(), retVal.OnConnectionClosed); - return Result.Success((P2PSocket)retVal); + return Result.Success(retVal); } public override void ProcessIncomingMessages() { foreach (var msg in eosSocket.GetMessageBatch()) { - callbacks.OnData(new EosP2PEndpoint(msg.Sender), new ReadWriteMessage(msg.Buffer, 0, msg.ByteLength * 8, false)); + EosP2PEndpoint endpoint = new EosP2PEndpoint(msg.Sender); + callbacks.OnData(endpoint, new ReadWriteMessage(msg.Buffer, 0, msg.ByteLength * 8, false)); + + if (Type is OwnerOrClient.Owner) + { + dosProtection.OnPacket(endpoint); + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/P2PSocket/P2PSocket.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/P2PSocket/P2PSocket.cs index 4c54b406d..deccacb60 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/P2PSocket/P2PSocket.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/P2PSocket/P2PSocket.cs @@ -8,6 +8,15 @@ namespace Barotrauma.Networking; abstract class P2PSocket : IDisposable { + public readonly P2POwnerDoSProtection dosProtection; + public readonly OwnerOrClient Type; + + public enum OwnerOrClient + { + Client, + Owner + } + public enum ErrorCode { EosNotInitialized, @@ -38,12 +47,16 @@ abstract class P2PSocket : IDisposable public readonly record struct Callbacks( Predicate OnIncomingConnection, Action OnConnectionClosed, + P2POwnerDoSProtection.ExcessivePacketDelegate OnExcessivePackets, Action OnData); protected readonly Callbacks callbacks; - protected P2PSocket(Callbacks callbacks) + protected P2PSocket(Callbacks callbacks, OwnerOrClient type) { this.callbacks = callbacks; + Type = type; + + dosProtection = new P2POwnerDoSProtection(callbacks.OnExcessivePackets); } public abstract void ProcessIncomingMessages(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/P2PSocket/SteamConnectSocket.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/P2PSocket/SteamConnectSocket.cs index aceb50a95..21fdc3650 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/P2PSocket/SteamConnectSocket.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/P2PSocket/SteamConnectSocket.cs @@ -64,13 +64,13 @@ sealed class SteamConnectSocket : P2PSocket private readonly SteamP2PEndpoint expectedEndpoint; private readonly ConnectionManager connectionManager; - private SteamConnectSocket(SteamP2PEndpoint expectedEndpoint, Callbacks callbacks, ConnectionManager connectionManager) : base(callbacks) + private SteamConnectSocket(SteamP2PEndpoint expectedEndpoint, Callbacks callbacks, ConnectionManager connectionManager, OwnerOrClient type) : base(callbacks, type) { this.expectedEndpoint = expectedEndpoint; this.connectionManager = connectionManager; } - public static Result Create(SteamP2PEndpoint endpoint, Callbacks callbacks) + public static Result Create(SteamP2PEndpoint endpoint, Callbacks callbacks, OwnerOrClient type) { if (!SteamManager.IsInitialized) { return Result.Failure(new Error(ErrorCode.SteamNotInitialized)); } @@ -87,7 +87,7 @@ sealed class SteamConnectSocket : P2PSocket if (connectionManager is null) { return Result.Failure(new Error(ErrorCode.FailedToCreateSteamP2PSocket)); } connectionManager.SetEndpointAndCallbacks(endpoint, callbacks); - return Result.Success((P2PSocket)new SteamConnectSocket(endpoint, callbacks, connectionManager)); + return Result.Success(new SteamConnectSocket(endpoint, callbacks, connectionManager, type)); } public override void ProcessIncomingMessages() diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/P2PSocket/SteamListenSocket.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/P2PSocket/SteamListenSocket.cs index 6ca70f22d..078790285 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/P2PSocket/SteamListenSocket.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/P2PSocket/SteamListenSocket.cs @@ -10,12 +10,14 @@ sealed class SteamListenSocket : P2PSocket private sealed class SocketManager : Steamworks.SocketManager, Steamworks.ISocketManager { private Callbacks callbacks; + private P2PSocket socket; private readonly Dictionary endpointToConnection = new(); public void SetCallbacks(Callbacks callbacks) - { - this.callbacks = callbacks; - } + => this.callbacks = callbacks; + + public void SetSocket(P2PSocket socket) + => this.socket = socket; public override void OnConnecting(Steamworks.Data.Connection connection, Steamworks.Data.ConnectionInfo info) { @@ -65,7 +67,7 @@ sealed class SteamListenSocket : P2PSocket callbacks.OnConnectionClosed(remoteEndpoint, peerDisconnectPacket); base.OnDisconnected(connection, info); } - + public override void OnMessage(Steamworks.Data.Connection connection, Steamworks.Data.NetIdentity identity, IntPtr data, int size, long messageNum, long recvTime, int channel) { if (!identity.IsSteamId || data == IntPtr.Zero) { return; } @@ -75,6 +77,11 @@ sealed class SteamListenSocket : P2PSocket Marshal.Copy(source: data, destination: dataArray, startIndex: 0, length: size); callbacks.OnData(endpoint, new ReadWriteMessage(dataArray, bitPos: 0, lBits: size * 8, copyBuf: false)); + + if (socket?.Type is OwnerOrClient.Owner) + { + socket.dosProtection.OnPacket(endpoint); + } } internal bool SendMessage(SteamP2PEndpoint endpoint, IWriteMessage outMsg, DeliveryMethod deliveryMethod) @@ -107,26 +114,49 @@ sealed class SteamListenSocket : P2PSocket private SteamListenSocket( Callbacks callbacks, - SocketManager socketManager) - : base(callbacks) + SocketManager socketManager, + OwnerOrClient type) + : base(callbacks, type) { this.socketManager = socketManager; } - public static Result Create(Callbacks callbacks) + public static Result Create(Callbacks callbacks, OwnerOrClient type) { if (!SteamManager.IsInitialized) { return Result.Failure(new Error(ErrorCode.SteamNotInitialized)); } var socketManager = Steamworks.SteamNetworkingSockets.CreateRelaySocket(); if (socketManager is null) { return Result.Failure(new Error(ErrorCode.FailedToCreateSteamP2PSocket)); } - socketManager.SetCallbacks(callbacks); - return Result.Success((P2PSocket)new SteamListenSocket(callbacks, socketManager)); + socketManager.SetCallbacks(callbacks); + P2PSocket socket = new SteamListenSocket(callbacks, socketManager, type); + socketManager.SetSocket(socket); + + return Result.Success(socket); } public override void ProcessIncomingMessages() { - socketManager.Receive(); + const int bufferSize = 32; + const int maxIterations = 100; + + // could technically cause a stack overflow since the call is recursive, + // use a while loop instead + int iteration; + for (iteration = 0; iteration < maxIterations; iteration++) + { + int received = socketManager.Receive(bufferSize: bufferSize, receiveToEnd: false); + + if (received < bufferSize) + { + break; + } + } + + if (iteration >= maxIterations) + { + DebugConsole.ThrowError("Steam P2P socket received too many messages in a single frame."); + } } public override bool SendMessage(P2PEndpoint endpoint, IWriteMessage outMsg, DeliveryMethod deliveryMethod) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/P2PClientPeer.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/P2PClientPeer.cs index 9e0e1e3c3..7cccef8c1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/P2PClientPeer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/P2PClientPeer.cs @@ -59,11 +59,11 @@ namespace Barotrauma.Networking ServerConnection = ServerEndpoint.MakeConnectionFromEndpoint(); - var socketCallbacks = new P2PSocket.Callbacks(OnIncomingConnection, OnConnectionClosed, OnP2PData); + var socketCallbacks = new P2PSocket.Callbacks(OnIncomingConnection, OnConnectionClosed, OnExcessivePackets, OnP2PData); var socketCreateResult = ServerEndpoint switch { - EosP2PEndpoint => EosP2PSocket.Create(socketCallbacks), - SteamP2PEndpoint steamP2PEndpoint => SteamConnectSocket.Create(steamP2PEndpoint, socketCallbacks), + EosP2PEndpoint => EosP2PSocket.Create(socketCallbacks, P2PSocket.OwnerOrClient.Client), + SteamP2PEndpoint steamP2PEndpoint => SteamConnectSocket.Create(steamP2PEndpoint, socketCallbacks, P2PSocket.OwnerOrClient.Client), _ => throw new Exception($"Invalid server endpoint: {ServerEndpoint.GetType()} {ServerEndpoint}") }; socket = socketCreateResult.TryUnwrapSuccess(out var s) @@ -97,6 +97,11 @@ namespace Barotrauma.Networking isActive = true; } + private void OnExcessivePackets(P2PEndpoint endpoint, bool shouldBan) + { + // do nothing + } + private bool OnIncomingConnection(P2PEndpoint remoteEndpoint) { if (remoteEndpoint == ServerEndpoint) @@ -163,7 +168,7 @@ namespace Barotrauma.Networking int completeMessageLengthBits = completeMessage.Length * 8; incomingDataMessages.Add(new ReadWriteMessage(completeMessage.ToArray(), 0, completeMessageLengthBits, copyBuf: false)); } - else if (packetHeader.IsHeartbeatMessage()) + else if (packetHeader.IsHeartbeatMessage() || packetHeader.IsDoSProtectionMessage()) { return; //TODO: implement heartbeats } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/P2POwnerPeer.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/P2POwnerPeer.cs index f9b0d0081..3d4d7bdb7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/P2POwnerPeer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/P2POwnerPeer.cs @@ -88,8 +88,8 @@ namespace Barotrauma.Networking remotePeers.Clear(); - var socketCallbacks = new P2PSocket.Callbacks(OnIncomingConnection, OnConnectionClosed, OnP2PData); - var socketCreateResult = DualStackP2PSocket.Create(socketCallbacks); + var socketCallbacks = new P2PSocket.Callbacks(OnIncomingConnection, OnConnectionClosed, OnExcessivePackets, OnP2PData); + var socketCreateResult = DualStackP2PSocket.Create(socketCallbacks, type: P2PSocket.OwnerOrClient.Owner); socket = socketCreateResult.TryUnwrapSuccess(out var s) ? s : throw new Exception($"Failed to create dual-stack socket: {socketCreateResult}"); @@ -187,6 +187,29 @@ namespace Barotrauma.Networking } } + private void OnExcessivePackets(P2PEndpoint endpoint, bool shouldBan) + { + IWriteMessage msg = new WriteOnlyMessage(); + msg.WriteNetSerializableStruct(new P2POwnerToServerHeader + { + EndpointStr = selfPrimaryEndpoint.StringRepresentation, + AccountInfo = selfAccountInfo + }); + msg.WriteNetSerializableStruct(new PeerPacketHeaders + { + DeliveryMethod = DeliveryMethod.Reliable, + PacketHeader = PacketHeader.IsDoSProtectionMessage + }); + msg.WriteNetSerializableStruct(new DoSProtectionPacket(endpoint.StringRepresentation, shouldBan)); + string dcMsg = TextManager.Get(shouldBan ? "DoSProtectionBanned" : "DoSProtectionKicked") + .Fallback(TextManager.Get("DoSProtectionKicked")).Value; + + msg.WriteNetSerializableStruct(shouldBan + ? PeerDisconnectPacket.Banned(dcMsg) + : PeerDisconnectPacket.Kicked(dcMsg)); + ForwardToServerProcess(msg); + } + private void StartAuthTask(IReadMessage inc, RemotePeer remotePeer) { remotePeer.AuthStatus = RemotePeer.AuthenticationStatus.AuthenticationPending; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/ServerInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/ServerInfo.cs index 55a748c19..05376aa1b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/ServerInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/ServerInfo.cs @@ -612,7 +612,7 @@ namespace Barotrauma.Networking } public bool Equals(ServerInfo other) - => other.Endpoints.Any(e => Endpoints.Contains(e)); + => other != null && other.Endpoints.Any(Endpoints.Contains); public override int GetHashCode() => Endpoints.First().GetHashCode(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerLog.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerLog.cs index 76036545e..7b9ffffa0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerLog.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerLog.cs @@ -9,6 +9,8 @@ namespace Barotrauma.Networking { public partial class ServerLog { + const int MaxLines = 500; + public GUIButton LogFrame; private GUIListBox listBox; private GUIButton reverseButton; @@ -17,6 +19,8 @@ namespace Barotrauma.Networking private bool reverseOrder = false; + private readonly bool[] msgTypeHidden = new bool[Enum.GetValues(typeof(MessageType)).Length]; + private bool OnReverseClicked(GUIButton btn, object obj) { SetMessageReversal(!reverseOrder); @@ -105,7 +109,10 @@ namespace Barotrauma.Networking reverseButton.Children.ForEach(c => c.SpriteEffects = reverseOrder ? SpriteEffects.FlipVertically : SpriteEffects.None); reverseButton.OnClicked = OnReverseClicked; - listBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.95f), listBoxLayout.RectTransform)); + listBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.95f), listBoxLayout.RectTransform)) + { + AutoHideScrollBar = false + }; GUIButton closeButton = new GUIButton(new RectTransform(new Vector2(0.25f, 0.05f), rightColumn.RectTransform), TextManager.Get("Close")) { @@ -127,7 +134,8 @@ namespace Barotrauma.Networking listBox.UpdateScrollBarSize(); - if (listBox.BarScroll == 0.0f || listBox.BarScroll == 1.0f) { listBox.BarScroll = 1.0f; } + //scrolled all the way down by default + listBox.BarScroll = 1.0f; msgFilter = ""; } @@ -189,11 +197,19 @@ namespace Barotrauma.Networking { float prevSize = listBox.BarSize; + GUIComponent firstVisibleLine = listBox.Content.Children.FirstOrDefault(c => c.Rect.Y > listBox.Content.Rect.Y); + int firstVisibileYPos = firstVisibleLine?.Rect.Y ?? 0; + + while (listBox.Content.CountChildren > MaxLines) + { + listBox.Content.RemoveChild(reverseOrder ? listBox.Content.Children.Last() : listBox.Content.Children.First()); + } + GUIFrame textContainer = null; Anchor anchor = Anchor.TopLeft; Pivot pivot = Pivot.TopLeft; - RichString richString = line.Text as RichString; + RichString richString = line.Text; if (richString != null && richString.RichTextData.HasValue) { foreach (var data in richString.RichTextData.Value) @@ -217,7 +233,7 @@ namespace Barotrauma.Networking line.Text, wrap: true, font: GUIStyle.SmallFont) { TextColor = messageColor[line.Type], - Visible = !msgTypeHidden[(int)line.Type], + Visible = !ShouldFilterMessage(line), CanBeFocused = false, UserData = line }; @@ -247,31 +263,47 @@ namespace Barotrauma.Networking } } - if ((prevSize == 1.0f && listBox.BarScroll == 0.0f) || (prevSize < 1.0f && listBox.BarScroll == 1.0f)) listBox.BarScroll = 1.0f; + //if the list was scrolled to the bottom, or to the top while the list wasn't full yet, + //keep it scrolled to the bottom + if ((MathUtils.NearlyEqual(prevSize, 1.0f) && MathUtils.NearlyEqual(listBox.BarScroll, 0.0f)) || + (prevSize < 1.0f && MathUtils.NearlyEqual(listBox.BarScroll, 1.0f))) + { + listBox.BarScroll = 1.0f; + } + //otherwise modify the scroll so the topmost element stays where it was (list doesn't jump as new lines are added when scrolled up) + else if (firstVisibleLine != null) + { + listBox.UpdateScrollBarSize(); + listBox.RecalculateChildren(); + int diff = firstVisibleLine.Rect.Y - firstVisibileYPos; + if (diff != 0) + { + listBox.BarScroll += diff / listBox.TotalSize * (prevSize / listBox.BarSize); + } + } } private bool FilterMessages() { - string filter = msgFilter == null ? "" : msgFilter.ToLower(); - foreach (GUIComponent child in listBox.Content.Children) { - if (!(child is GUITextBlock textBlock)) { continue; } + if (child is not GUITextBlock) { continue; } child.Visible = true; - if (msgTypeHidden[(int)((LogMessage)child.UserData).Type]) - { - child.Visible = false; - continue; - } - - textBlock.Visible = string.IsNullOrEmpty(filter) || textBlock.Text.ToLower().Contains(filter); + child.Visible = !ShouldFilterMessage((LogMessage)child.UserData); } listBox.UpdateScrollBarSize(); - listBox.BarScroll = 0.0f; + listBox.BarScroll = 1.0f; return true; } + private bool ShouldFilterMessage(LogMessage message) + { + if (msgTypeHidden[(int)message.Type]) { return true; } + string text = message.Text.SanitizedValue; + return !string.IsNullOrEmpty(msgFilter) && !text.Contains(msgFilter, StringComparison.InvariantCultureIgnoreCase); + } + private void SetMessageReversal(bool reverse) { if (reverseOrder == reverse) { return; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Physics/PhysicsBody.cs b/Barotrauma/BarotraumaClient/ClientSource/Physics/PhysicsBody.cs index 26746a5ea..918849847 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Physics/PhysicsBody.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Physics/PhysicsBody.cs @@ -84,7 +84,7 @@ namespace Barotrauma } if (IsValidShape(Radius, Height, Width)) { - DrawShape(drawPosition, DrawRotation, color); + DrawShape(DrawPosition, DrawRotation, color); } if (LastServerState != null) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EditorNode.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EditorNode.cs index 3210e1122..a890d8d0d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EditorNode.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EditorNode.cs @@ -233,7 +233,7 @@ namespace Barotrauma protected virtual Color BackgroundColor => new Color(150, 150, 150); - private void DrawBack(SpriteBatch spriteBatch) + protected virtual void DrawBack(SpriteBatch spriteBatch) { Color outlineColor = Color.White * 0.8f; Color fontColor = Color.White; @@ -253,9 +253,19 @@ namespace Barotrauma GUI.DrawRectangle(spriteBatch, HeaderRectangle, outlineColor, isFilled: false, depth: 1.0f, thickness: (int) Math.Max(1, 1.25f / camZoom)); GUI.DrawRectangle(spriteBatch, bodyRect, outlineColor, isFilled: false, depth: 1.0f, thickness: (int) Math.Max(1, 1.25f / camZoom)); + DrawConnections(spriteBatch); + + Vector2 headerSize = GUIStyle.SubHeadingFont.MeasureString(Name); + GUIStyle.SubHeadingFont.DrawString(spriteBatch, Name, HeaderRectangle.Location.ToVector2() + (HeaderRectangle.Size.ToVector2() / 2) - (headerSize / 2), fontColor); + } + + protected virtual void DrawConnections(SpriteBatch spriteBatch) + { int x = 0, y = 0; foreach (EventEditorNodeConnection connection in Connections) { + if (!ShouldDrawConnection(connection)) { continue; } + switch (connection.Type.NodeSide) { case NodeConnectionType.Side.Left: @@ -268,9 +278,11 @@ namespace Barotrauma break; } } + } - Vector2 headerSize = GUIStyle.SubHeadingFont.MeasureString(Name); - GUIStyle.SubHeadingFont.DrawString(spriteBatch, Name, HeaderRectangle.Location.ToVector2() + (HeaderRectangle.Size.ToVector2() / 2) - (headerSize / 2), fontColor); + protected virtual bool ShouldDrawConnection(EventEditorNodeConnection connection) + { + return true; // Base implementation draws all connections } public void AddConnection(NodeConnectionType connectionType) @@ -337,6 +349,11 @@ namespace Barotrauma { private readonly Type type; + protected override Color BackgroundColor => + EventEditorScreen.ConversationMode && !IsInstanceOf(type, typeof(ConversationAction)) + ? new Color(80, 80, 80) // Darker for non-conversation nodes in conversation mode + : new Color(150, 150, 150); // Normal color + public EventNode(Type type, string name) : base(name) { this.type = type; @@ -387,8 +404,15 @@ namespace Barotrauma Type? t = Type.GetType(element.GetAttributeString("type", string.Empty)); if (t == null) { return null; } + + string name = element.GetAttributeString("name", string.Empty); + int id = element.GetAttributeInt("i", -1); - EventNode newNode = new EventNode(t, element.GetAttributeString("name", string.Empty)) { ID = element.GetAttributeInt("i", -1) }; + // Create the appropriate node type based on whether it's a conversation action + EditorNode newNode = IsInstanceOf(t, typeof(ConversationAction)) + ? new EventConversationNode(t, name) { ID = id } + : new EventNode(t, name) { ID = id }; + float posX = element.GetAttributeFloat("xpos", 0f); float posY = element.GetAttributeFloat("ypos", 0f); newNode.Position = new Vector2(posX, posY); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EventConversationNode.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EventConversationNode.cs new file mode 100644 index 000000000..2387cb239 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EventConversationNode.cs @@ -0,0 +1,398 @@ +#nullable enable +using System; +using System.Linq; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System.Collections.Generic; + +namespace Barotrauma +{ + /// + /// Base class for event nodes that display text content and can have inner Text nodes + /// + internal abstract class EventTextDisplayNode(Type type, string name) : EventNode(type, name) + { + protected virtual bool ShowOptions => false; + + private new Rectangle HeaderRectangle + { + get + { + if (!EventEditorScreen.ConversationMode) { return base.HeaderRectangle; } + + Rectangle drawRect = GetDrawRectangle(); + return new Rectangle(Position.ToPoint(), new Point(drawRect.Width, 32)); + } + } + + public override Rectangle GetDrawRectangle() + { + if (!EventEditorScreen.ConversationMode) { return base.GetDrawRectangle(); } + + var textConnection = Connections.Find(c => string.Equals(c.Attribute, "Text", StringComparison.OrdinalIgnoreCase)); + var optionConnections = ShowOptions ? Connections.Where(c => c.Type == NodeConnectionType.Option) : Enumerable.Empty(); + + const int width = 300; + int height = 50; + + // Calculate height for text section + if (textConnection != null) + { + string textContent = GetTextContent(textConnection); + if (!string.IsNullOrEmpty(textContent) && GUIStyle.Font.Value != null) + { + string wrappedText = ToolBox.WrapText(textContent, width - 16, GUIStyle.Font.Value); + Vector2 textSize = GUIStyle.Font.MeasureString(wrappedText); + height += (int)textSize.Y + 10; + } + else + { + height += 25; + } + } + + // Calculate height for each option (only for conversation nodes) + if (ShowOptions) + { + int optionIndex = 0; + foreach (var option in optionConnections) + { + string optionText = GetOptionText(option, optionIndex); + + if (GUIStyle.Font.Value != null) + { + string wrappedOption = ToolBox.WrapText(optionText, width - 40, GUIStyle.Font.Value); + Vector2 optionSize = GUIStyle.Font.MeasureString(wrappedOption); + height += (int)optionSize.Y + 20; + } + else + { + height += 40; + } + optionIndex++; + } + } + + Rectangle rect = Rectangle; + return new Rectangle(rect.X, rect.Y, width, height); + } + + protected override void DrawBack(SpriteBatch spriteBatch) + { + if (!EventEditorScreen.ConversationMode) + { + base.DrawBack(spriteBatch); + return; + } + + Rectangle bodyRect = GetDrawRectangle(); + + // Background colors + Color headerColor = IsSelected ? new Color(100, 150, 200) : new Color(120, 170, 220); + Color bodyColor = new Color(90, 120, 150); + Color borderColor = Color.LightBlue; + + // Draw background + GUI.DrawRectangle(spriteBatch, HeaderRectangle, headerColor, isFilled: true, depth: 1.0f); + GUI.DrawRectangle(spriteBatch, bodyRect, bodyColor, isFilled: true, depth: 1.0f); + GUI.DrawRectangle(spriteBatch, HeaderRectangle, borderColor, isFilled: false, depth: 1.0f); + GUI.DrawRectangle(spriteBatch, bodyRect, borderColor, isFilled: false, depth: 1.0f); + + // Draw header text + Vector2 headerSize = GUIStyle.SubHeadingFont.MeasureString(Name); + Vector2 headerPos = HeaderRectangle.Location.ToVector2() + (HeaderRectangle.Size.ToVector2() / 2) - (headerSize / 2); + GUIStyle.SubHeadingFont.DrawString(spriteBatch, Name, headerPos, Color.White); + + // Draw text content + DrawTextContent(spriteBatch, bodyRect); + + // Let base class handle standard connections (Activate, Next, etc.) + DrawConnections(spriteBatch); + } + + protected virtual void DrawTextContent(SpriteBatch spriteBatch, Rectangle bodyRect) + { + var textConnection = Connections.Find(c => string.Equals(c.Attribute, "Text", StringComparison.OrdinalIgnoreCase)); + var optionConnections = ShowOptions ? Connections.Where(c => c.Type == NodeConnectionType.Option) : Enumerable.Empty(); + + Vector2 mousePos = Screen.Selected.Cam.ScreenToWorld(PlayerInput.MousePosition); + mousePos.Y = -mousePos.Y; + + const int padding = 8; + int currentY = bodyRect.Y + padding + 30; + + // Draw text section + if (textConnection != null) + { + string textContent = GetTextContent(textConnection); + + // Wrap text and calculate height + string wrappedText = textContent; + int textHeight = 25; + if (GUIStyle.Font.Value != null) + { + wrappedText = ToolBox.WrapText(textContent, bodyRect.Width - 24, GUIStyle.Font.Value); + Vector2 textSize = GUIStyle.Font.MeasureString(wrappedText); + textHeight = (int)textSize.Y + 10; + } + + Rectangle textRect = new Rectangle(bodyRect.X + padding, currentY, bodyRect.Width - padding * 2, textHeight); + + // background + GUI.DrawRectangle(spriteBatch, textRect, new Color(70, 100, 130), isFilled: true); + GUI.DrawRectangle(spriteBatch, textRect, Color.CornflowerBlue, isFilled: false); + + // wrapped text + Vector2 textPos = new Vector2(textRect.X + 4, textRect.Y + 4); + GUI.DrawString(spriteBatch, textPos, wrappedText, Color.Yellow, font: GUIStyle.Font); + + // tooltip + if (textRect.Contains(mousePos)) + { + string rawTextKey = GetRawTextKey(textConnection); + if (!string.IsNullOrEmpty(rawTextKey)) + { + EventEditorScreen.DrawnTooltip = rawTextKey; + } + } + + currentY += textHeight + 5; + } + + // Draw options (only for conversation nodes) + if (ShowOptions) + { + DrawOptions(spriteBatch, bodyRect, optionConnections, currentY); + } + } + + protected static void DrawOptions(SpriteBatch spriteBatch, Rectangle bodyRect, IEnumerable optionConnections, int startY) + { + Vector2 mousePos = Screen.Selected.Cam.ScreenToWorld(PlayerInput.MousePosition); + mousePos.Y = -mousePos.Y; + const int padding = 8; + int currentY = startY; + + int optionIndex = 0; + foreach (var option in optionConnections) + { + string optionText = GetOptionText(option, optionIndex); + + // Wrap option text and calculate height + string wrappedOption = optionText; + int optionHeight = 30; + if (GUIStyle.Font.Value != null) + { + wrappedOption = ToolBox.WrapText(optionText, bodyRect.Width - 40, GUIStyle.Font.Value); + Vector2 optionSize = GUIStyle.Font.MeasureString(wrappedOption); + optionHeight = (int)optionSize.Y + 16; + } + + Rectangle optionRect = new Rectangle(bodyRect.X + padding, currentY, bodyRect.Width - padding * 2, optionHeight); + + // background - red for end conversation, blue for normal + Color optionBg = option.EndConversation ? new Color(120, 80, 80) : new Color(80, 80, 120); + GUI.DrawRectangle(spriteBatch, optionRect, optionBg, isFilled: true); + GUI.DrawRectangle(spriteBatch, optionRect, Color.White, isFilled: false); + + Vector2 optionPos = new Vector2(optionRect.X + 4, optionRect.Y + 4); + GUI.DrawString(spriteBatch, optionPos, wrappedOption, Color.White, font: GUIStyle.Font); + + // tooltip + if (optionRect.Contains(mousePos)) + { + string rawOptionKey = option.OptionText ?? ""; + if (!string.IsNullOrEmpty(rawOptionKey)) + { + EventEditorScreen.DrawnTooltip = rawOptionKey; + } + } + + // connection point + Rectangle connRect = new Rectangle(bodyRect.Right - 1, optionRect.Y + optionHeight / 2 - 8, 16, 16); + GUI.DrawRectangle(spriteBatch, connRect, Color.DarkGray, isFilled: true); + GUI.DrawRectangle(spriteBatch, connRect, Color.White, isFilled: false); + option.DrawRectangle = connRect; + + // connection lines + foreach (var connected in option.ConnectedTo) + { + Vector2 start = new Vector2(connRect.Right, connRect.Center.Y); + Vector2 end = new Vector2(connected.DrawRectangle.Left, connected.DrawRectangle.Center.Y); + + float knobLength = 24; + var (points, _) = ToolBox.GetSquareLineBetweenPoints(start, end, knobLength); + + Color lineColor = GUIStyle.Red; + float lineWidth = Math.Max(2.0f, 2.0f / (Screen.Selected is EventEditorScreen eventEditor ? eventEditor.Cam.Zoom : 1.0f)); + + GUI.DrawLine(spriteBatch, points[0], points[1], lineColor, width: (int)lineWidth); + GUI.DrawLine(spriteBatch, points[1], points[2], lineColor, width: (int)lineWidth); + GUI.DrawLine(spriteBatch, points[2], points[3], lineColor, width: (int)lineWidth); + GUI.DrawLine(spriteBatch, points[3], points[4], lineColor, width: (int)lineWidth); + GUI.DrawLine(spriteBatch, points[4], points[5], lineColor, width: (int)lineWidth); + } + + currentY += optionHeight + 5; + optionIndex++; + } + } + + private static string GetOptionText(EventEditorNodeConnection option, int optionIndex) + { + string optionTextKey = option.OptionText ?? $"Option {optionIndex + 1}"; + var allVariants = TextManager.GetAll(optionTextKey); + + int variantCount = allVariants.Count(); + return variantCount switch + { + > 1 => $"[{variantCount} variants] {string.Join(" / ", allVariants)}", + 1 => allVariants.First(), + _ => optionTextKey + }; + } + + private string GetTextContent(EventEditorNodeConnection textConnection) + { + string textContent = ""; + + // First check if there's a direct text attribute + if (textConnection.OverrideValue != null) + { + textContent = textConnection.OverrideValue.ToString() ?? ""; + } + else + { + object? connectedValue = textConnection.GetValue(); + if (connectedValue != null) + { + textContent = connectedValue.ToString() ?? ""; + } + } + + // If no direct text, check for inner Text nodes via Add connections + if (string.IsNullOrEmpty(textContent)) + { + var addConnection = Connections.FirstOrDefault(c => c.Type == NodeConnectionType.Add); + if (addConnection != null && addConnection.ConnectedTo.Any()) + { + var connectedNode = addConnection.ConnectedTo.First(); + if (connectedNode.Parent?.Name == "Text") + { + // Get the text content from the connected Text node + var textNodeConnection = connectedNode.Parent.Connections.FirstOrDefault(c => + string.Equals(c.Attribute, "tag", StringComparison.OrdinalIgnoreCase)); + + if (textNodeConnection?.OverrideValue != null) + { + textContent = textNodeConnection.OverrideValue.ToString() ?? ""; + } + } + } + } + + // Translate the text if we found any + if (!string.IsNullOrEmpty(textContent)) + { + var translated = TextManager.Get(textContent); + if (translated.Loaded) + { + textContent = translated.Value; + } + } + + return textContent; + } + + private string GetRawTextKey(EventEditorNodeConnection textConnection) + { + string textKey = ""; + + // First check if there's a direct text attribute + if (textConnection.OverrideValue != null) + { + textKey = textConnection.OverrideValue.ToString() ?? ""; + } + else + { + var connectedValue = textConnection.GetValue(); + if (connectedValue != null) + { + textKey = connectedValue.ToString() ?? ""; + } + } + + // If no direct text, check for inner Text nodes via Add connections + if (string.IsNullOrEmpty(textKey)) + { + var addConnection = Connections.FirstOrDefault(c => c.Type == NodeConnectionType.Add); + if (addConnection != null && addConnection.ConnectedTo.Any()) + { + var connectedNode = addConnection.ConnectedTo.First(); + if (connectedNode.Parent?.Name == "Text") + { + // Get the text key from the connected Text node + var textNodeConnection = connectedNode.Parent.Connections.FirstOrDefault(c => + string.Equals(c.Attribute, "tag", StringComparison.OrdinalIgnoreCase)); + + if (textNodeConnection?.OverrideValue != null) + { + textKey = textNodeConnection.OverrideValue.ToString() ?? ""; + } + } + } + } + + return textKey; + } + + protected override bool ShouldDrawConnection(EventEditorNodeConnection connection) + { + if (!EventEditorScreen.ConversationMode) { return base.ShouldDrawConnection(connection); } + + // In conversation mode, exclude Options and Text since we draw them manually + // Also exclude Add connections since we hide the child Text nodes and display their content inline + return connection.Type == NodeConnectionType.Activate || + connection.Type == NodeConnectionType.Next; + } + + protected override void DrawConnections(SpriteBatch spriteBatch) + { + if (!EventEditorScreen.ConversationMode) + { + base.DrawConnections(spriteBatch); + return; + } + + // In conversation mode, use the correct rectangle for connection positioning + Rectangle correctRect = GetDrawRectangle(); + int x = 0, y = 0; + foreach (EventEditorNodeConnection connection in Connections) + { + if (!ShouldDrawConnection(connection)) { continue; } + + switch (connection.Type.NodeSide) + { + case NodeConnectionType.Side.Left: + connection.Draw(spriteBatch, correctRect, y); + y++; + break; + case NodeConnectionType.Side.Right: + connection.Draw(spriteBatch, correctRect, x); + x++; + break; + } + } + } + } + + internal class EventConversationNode(Type type, string name) : EventTextDisplayNode(type, name) + { + protected override bool ShowOptions => true; + } + + internal class EventLogNode(Type type, string name) : EventTextDisplayNode(type, name) + { + protected override bool ShowOptions => false; + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EventEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EventEditorScreen.cs index ed6a1db23..9a1a37b2f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EventEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EventEditorScreen.cs @@ -18,6 +18,8 @@ namespace Barotrauma public override Camera Cam { get; } public static string? DrawnTooltip { get; set; } + + public static bool ConversationMode { get; set; } public static readonly List nodeList = new List(); @@ -37,6 +39,11 @@ namespace Barotrauma private LocationType? lastTestType; private GUITickBox? isTraitorEventBox; + private GUITickBox? conversationModeBox; + + private readonly LanguageIdentifier originalLanguage; + + private GUIDropDown? languageDropdown; private static int CreateID() { @@ -50,25 +57,99 @@ namespace Barotrauma { Cam = new Camera(); nodeList.Clear(); + + originalLanguage = GameSettings.CurrentConfig.Language; + CreateGUI(); } + public override void Select() + { + GUI.PreventPauseMenuToggle = false; + projectName = TextManager.Get("EventEditor.Unnamed").Value; + + UpdateLanguageDropdownSelection(); + + base.Select(); + } + + private void UpdateLanguageDropdownSelection() + { + if (languageDropdown == null) { return; } + + languageDropdown.SelectItem(GameSettings.CurrentConfig.Language); + } + + protected override void DeselectEditorSpecific() + { + // Restore the original language when leaving the editor + var config = GameSettings.CurrentConfig; + config.Language = originalLanguage; + GameSettings.SetCurrentConfig(config); + TextManager.LanguageChanged(); + } + + private static readonly HashSet hiddenNodesInConversationMode = new HashSet(); + + private static bool ShouldHideNodeInConversationMode(EditorNode node) + { + return hiddenNodesInConversationMode.Contains(node); + } + + private static void UpdateHiddenNodesInConversationMode() + { + hiddenNodesInConversationMode.Clear(); + + // Find all text display nodes (ConversationAction and EventLogAction) and mark their inner Text nodes (and descendants) as hidden + foreach (var textDisplayNode in nodeList.Where(IsEventTextDisplayNode)) + { + var addConnection = textDisplayNode.Connections.FirstOrDefault(c => c.Type == NodeConnectionType.Add); + if (addConnection != null && addConnection.ConnectedTo.Any()) + { + foreach (var connectedNode in addConnection.ConnectedTo) + { + if (connectedNode.Parent?.Name == "Text") + { + MarkNodeAndDescendantsAsHidden(connectedNode.Parent); + } + } + } + } + } + + private static bool IsEventTextDisplayNode(EditorNode node) => node is EventTextDisplayNode; + + private static void MarkNodeAndDescendantsAsHidden(EditorNode node) + { + hiddenNodesInConversationMode.Add(node); + + // Recursively mark all connected child nodes as hidden + foreach (var connection in node.Connections) + { + foreach (var connectedNode in connection.ConnectedTo) + { + if (connectedNode.Parent != null && !hiddenNodesInConversationMode.Contains(connectedNode.Parent)) + { + MarkNodeAndDescendantsAsHidden(connectedNode.Parent); + } + } + } + } + private void CreateGUI() { - GuiFrame = new GUIFrame(new RectTransform(new Vector2(0.2f, 0.4f), GUI.Canvas) { MinSize = new Point(300, 420) }); + GuiFrame = new GUIFrame(new RectTransform(new Vector2(0.2f, 0.5f), GUI.Canvas) { MinSize = new Point(300, 520) }); GUILayoutGroup layoutGroup = new GUILayoutGroup(RectTransform(0.9f, 0.9f, GuiFrame, Anchor.Center)) { Stretch = true, AbsoluteSpacing = GUI.IntScale(5) }; // === BUTTONS === // - GUILayoutGroup buttonLayout = new GUILayoutGroup(RectTransform(1.0f, 0.50f, layoutGroup)) { RelativeSpacing = 0.04f }; - GUIButton newProjectButton = new GUIButton(RectTransform(1.0f, 0.33f, buttonLayout), TextManager.Get("EventEditor.NewProject")); - GUIButton saveProjectButton = new GUIButton(RectTransform(1.0f, 0.33f, buttonLayout), TextManager.Get("EventEditor.SaveProject")); - GUIButton loadProjectButton = new GUIButton(RectTransform(1.0f, 0.33f, buttonLayout), TextManager.Get("EventEditor.LoadProject")); - GUIButton exportProjectButton = new GUIButton(RectTransform(1.0f, 0.33f, buttonLayout), TextManager.Get("EventEditor.Export")); - + GUILayoutGroup buttonLayout = new GUILayoutGroup(RectTransform(1.0f, 0.40f, layoutGroup)) { RelativeSpacing = 0.04f }; + GUIButton newProjectButton = new GUIButton(RectTransform(1.0f, 0.25f, buttonLayout), TextManager.Get("EventEditor.NewProject")); + GUIButton saveProjectButton = new GUIButton(RectTransform(1.0f, 0.25f, buttonLayout), TextManager.Get("EventEditor.SaveProject")); + GUIButton loadProjectButton = new GUIButton(RectTransform(1.0f, 0.25f, buttonLayout), TextManager.Get("EventEditor.LoadProject")); + GUIButton exportProjectButton = new GUIButton(RectTransform(1.0f, 0.25f, buttonLayout), TextManager.Get("EventEditor.Export")); // === LOAD PREFAB === // - - GUILayoutGroup loadEventLayout = new GUILayoutGroup(RectTransform(1.0f, 0.125f, layoutGroup)); + GUILayoutGroup loadEventLayout = new GUILayoutGroup(RectTransform(1.0f, 0.10f, layoutGroup)); new GUITextBlock(RectTransform(1.0f, 0.5f, loadEventLayout), TextManager.Get("EventEditor.LoadEvent"), font: GUIStyle.SubHeadingFont); GUILayoutGroup loadDropdownLayout = new GUILayoutGroup(RectTransform(1.0f, 0.5f, loadEventLayout), isHorizontal: true, childAnchor: Anchor.CenterLeft); @@ -76,8 +157,7 @@ namespace Barotrauma GUIButton loadButton = new GUIButton(RectTransform(0.2f, 1.0f, loadDropdownLayout), TextManager.Get("Load")); // === ADD ACTION === // - - GUILayoutGroup addActionLayout = new GUILayoutGroup(RectTransform(1.0f, 0.125f, layoutGroup)); + GUILayoutGroup addActionLayout = new GUILayoutGroup(RectTransform(1.0f, 0.10f, layoutGroup)); new GUITextBlock(RectTransform(1.0f, 0.5f, addActionLayout), TextManager.Get("EventEditor.AddAction"), font: GUIStyle.SubHeadingFont); GUILayoutGroup addActionDropdownLayout = new GUILayoutGroup(RectTransform(1.0f, 0.5f, addActionLayout), isHorizontal: true, childAnchor: Anchor.CenterLeft); @@ -85,7 +165,7 @@ namespace Barotrauma GUIButton addActionButton = new GUIButton(RectTransform(0.2f, 1.0f, addActionDropdownLayout), TextManager.Get("EventEditor.Add")); // === ADD VALUE === // - GUILayoutGroup addValueLayout = new GUILayoutGroup(RectTransform(1.0f, 0.125f, layoutGroup)); + GUILayoutGroup addValueLayout = new GUILayoutGroup(RectTransform(1.0f, 0.10f, layoutGroup)); new GUITextBlock(RectTransform(1.0f, 0.5f, addValueLayout), TextManager.Get("EventEditor.AddValue"), font: GUIStyle.SubHeadingFont); GUILayoutGroup addValueDropdownLayout = new GUILayoutGroup(RectTransform(1.0f, 0.5f, addValueLayout), isHorizontal: true, childAnchor: Anchor.CenterLeft); @@ -93,7 +173,7 @@ namespace Barotrauma GUIButton addValueButton = new GUIButton(RectTransform(0.2f, 1.0f, addValueDropdownLayout), TextManager.Get("EventEditor.Add")); // === ADD SPECIAL === // - GUILayoutGroup addSpecialLayout = new GUILayoutGroup(RectTransform(1.0f, 0.125f, layoutGroup)); + GUILayoutGroup addSpecialLayout = new GUILayoutGroup(RectTransform(1.0f, 0.10f, layoutGroup)); new GUITextBlock(RectTransform(1.0f, 0.5f, addSpecialLayout), TextManager.Get("EventEditor.AddSpecial"), font: GUIStyle.SubHeadingFont); GUILayoutGroup addSpecialDropdownLayout = new GUILayoutGroup(RectTransform(1.0f, 0.5f, addSpecialLayout), isHorizontal: true, childAnchor: Anchor.CenterLeft); GUIDropDown addSpecialDropdown = new GUIDropDown(RectTransform(0.8f, 1.0f, addSpecialDropdownLayout), elementCount: 1); @@ -156,7 +236,45 @@ namespace Barotrauma return true; }; - isTraitorEventBox = new GUITickBox(RectTransform(1.0f, 0.125f, layoutGroup), "Traitor event"); + isTraitorEventBox = new GUITickBox(RectTransform(1.0f, 0.10f, layoutGroup), "Traitor event"); + + // === CONVERSATION MODE CHECKBOX === // + conversationModeBox = new GUITickBox(RectTransform(1.0f, 0.10f, layoutGroup), "Conversation Mode"); + conversationModeBox.Selected = ConversationMode; + conversationModeBox.OnSelected = box => + { + ConversationMode = !ConversationMode; + UpdateHiddenNodesInConversationMode(); + return true; + }; + + // === LANGUAGE SELECTION === // + GUILayoutGroup languageLayout = new GUILayoutGroup(RectTransform(1.0f, 0.10f, layoutGroup)); + new GUITextBlock(RectTransform(1.0f, 0.5f, languageLayout), TextManager.Get("Language"), font: GUIStyle.SubHeadingFont); + + var languages = TextManager.AvailableLanguages + .OrderBy(l => TextManager.GetTranslatedLanguageName(l).ToIdentifier()); + + languageDropdown = new GUIDropDown(RectTransform(1.0f, 0.5f, languageLayout), elementCount: 10); + foreach (var language in languages) + { + languageDropdown.AddItem(TextManager.GetTranslatedLanguageName(language), language); + } + + // Select current language + languageDropdown.SelectItem(GameSettings.CurrentConfig.Language); + + languageDropdown.OnSelected = (component, userData) => + { + if (userData is LanguageIdentifier selectedLanguage) + { + var config = GameSettings.CurrentConfig; + config.Language = selectedLanguage; + GameSettings.SetCurrentConfig(config); + TextManager.LanguageChanged(); + } + return true; + }; screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); } @@ -323,6 +441,9 @@ namespace Barotrauma { GUI.NotifyPrompt(TextManager.Get("EventEditor.RandomGenerationHeader"), TextManager.Get("EventEditor.RandomGenerationBody")); } + + // Update hidden nodes after loading + UpdateHiddenNodesInConversationMode(); return true; }); return true; @@ -334,7 +455,22 @@ namespace Barotrauma Vector2 spawnPos = Cam.WorldViewCenter; spawnPos.Y = -spawnPos.Y; - EventNode newNode = new EventNode(type, type.Name) { ID = CreateID() }; + + // Create the appropriate node type based on the action type + EditorNode newNode; + if (EditorNode.IsInstanceOf(type, typeof(ConversationAction))) + { + newNode = new EventConversationNode(type, type.Name) { ID = CreateID() }; + } + else if (EditorNode.IsInstanceOf(type, typeof(EventLogAction))) + { + newNode = new EventLogNode(type, type.Name) { ID = CreateID() }; + } + else + { + newNode = new EventNode(type, type.Name) { ID = CreateID() }; + } + newNode.Position = spawnPos - newNode.Size / 2; nodeList.Add(newNode); return true; @@ -399,7 +535,19 @@ namespace Barotrauma Type? t = Type.GetType($"Barotrauma.{subElement.Name}"); if (t != null && EditorNode.IsInstanceOf(t, typeof(EventAction))) { - newNode = new EventNode(t, subElement.Name.ToString()) { Position = new Vector2(ident, 0), ID = CreateID() }; + // Create the appropriate node type based on the action type + if (EditorNode.IsInstanceOf(t, typeof(ConversationAction))) + { + newNode = new EventConversationNode(t, subElement.Name.ToString()) { Position = new Vector2(ident, 0), ID = CreateID() }; + } + else if (EditorNode.IsInstanceOf(t, typeof(EventLogAction))) + { + newNode = new EventLogNode(t, subElement.Name.ToString()) { Position = new Vector2(ident, 0), ID = CreateID() }; + } + else + { + newNode = new EventNode(t, subElement.Name.ToString()) { Position = new Vector2(ident, 0), ID = CreateID() }; + } } else { @@ -547,13 +695,6 @@ namespace Barotrauma return new RectTransform(new Vector2(x, y), parent.RectTransform, anchor); } - public override void Select() - { - GUI.PreventPauseMenuToggle = false; - projectName = TextManager.Get("EventEditor.Unnamed").Value; - base.Select(); - } - public override void AddToGUIUpdateList() { GuiFrame.AddToGUIUpdateList(); @@ -671,6 +812,7 @@ namespace Barotrauma private static void Load(XElement saveElement) { nodeList.Clear(); + hiddenNodesInConversationMode.Clear(); projectName = saveElement.GetAttributeString("name", TextManager.Get("EventEditor.Unnamed").Value); foreach (XElement element in saveElement.Elements()) { @@ -702,6 +844,9 @@ namespace Barotrauma } } } + + // Update hidden nodes after loading + UpdateHiddenNodesInConversationMode(); } private static void CreateContextMenu(EditorNode node, EventEditorNodeConnection? connection = null) @@ -971,21 +1116,25 @@ namespace Barotrauma foreach (EditorNode node in nodeList.Where(node => node is SpecialNode)) { + if (ConversationMode && ShouldHideNodeInConversationMode(node)) { continue; } node.Draw(spriteBatch); } // Render value nodes below event nodes foreach (EditorNode node in nodeList.Where(node => node is ValueNode)) { + if (ConversationMode && ShouldHideNodeInConversationMode(node)) { continue; } node.Draw(spriteBatch); } foreach (EditorNode node in nodeList.Where(node => node is EventNode)) { + if (ConversationMode && ShouldHideNodeInConversationMode(node)) { continue; } node.Draw(spriteBatch); } - + draggedNode?.Draw(spriteBatch); + foreach (var (node, _) in markedNodes) { node.Draw(spriteBatch); @@ -1013,6 +1162,11 @@ namespace Barotrauma CreateGUI(); } + if (PlayerInput.KeyHit(Keys.R) && PlayerInput.KeyDown(Keys.LeftShift)) + { + CreateGUI(); + } + Cam.MoveCamera((float) deltaTime, allowMove: true, allowZoom: GUI.MouseOn == null); Vector2 mousePos = Cam.ScreenToWorld(PlayerInput.MousePosition); mousePos.Y = -mousePos.Y; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs index 7eeb3526e..1e9dee31e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs @@ -319,14 +319,21 @@ namespace Barotrauma graphics.Clear(Color.Transparent); DamageEffect.CurrentTechnique = DamageEffect.Techniques["StencilShader"]; - DamageEffect.CurrentTechnique.Passes[0].Apply(); - spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, SamplerState.LinearWrap, effect: DamageEffect, transformMatrix: cam.Transform); + //reset so any parameters left over from previous usages of the shader don't persist + ResetDamageEffect(); + spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.LinearWrap, effect: DamageEffect, transformMatrix: cam.Transform); Submarine.DrawDamageable(spriteBatch, DamageEffect, false); - DamageEffect.Parameters["aCutoff"].SetValue(0.0f); - DamageEffect.Parameters["cCutoff"].SetValue(0.0f); - Submarine.DamageEffectCutoff = 0.0f; - DamageEffect.CurrentTechnique.Passes[0].Apply(); spriteBatch.End(); + //reset so parameters set in DrawDamageable don't persist + ResetDamageEffect(); + + void ResetDamageEffect() + { + DamageEffect.Parameters["aCutoff"].SetValue(0.0f); + DamageEffect.Parameters["cCutoff"].SetValue(0.0f); + Submarine.DamageEffectCutoff = 0.0f; + DamageEffect.CurrentTechnique.Passes[0].Apply(); + } sw.Stop(); GameMain.PerformanceCounter.AddElapsedTicks("Draw:Map:FrontDamageable", sw.ElapsedTicks); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs index a2fb9b852..629d8ea27 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs @@ -2090,7 +2090,10 @@ namespace Barotrauma }; serverLogReverseButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.05f), serverLogListboxLayout.RectTransform), style: "UIToggleButtonVertical"); - serverLogBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.95f), serverLogListboxLayout.RectTransform)); + serverLogBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.95f), serverLogListboxLayout.RectTransform)) + { + AutoHideScrollBar = false + }; //filter tickbox list ------------------------------------------------------------------ @@ -2198,7 +2201,7 @@ namespace Barotrauma OnClicked = (btn, obj) => { if (GameMain.Client == null) { return true; } - GUI.CreateVerificationPrompt(GameMain.GameSession.GameMode is CampaignMode ? "PauseMenuReturnToServerLobbyVerification" : "EndRoundSubNotAtLevelEnd", + GUI.CreateVerificationPrompt(GameMain.GameSession?.GameMode is CampaignMode ? "PauseMenuReturnToServerLobbyVerification" : "EndRoundSubNotAtLevelEnd", () => { GameMain.Client?.RequestEndRound(save: false); @@ -3304,11 +3307,17 @@ namespace Barotrauma VoteType voteType; if (component.Parent == GameMain.NetLobbyScreen.SubList.Content) { - if (SelectedMode == GameModePreset.PvP && MultiplayerPreferences.Instance.TeamPreference is not (CharacterTeamType.Team1 or CharacterTeamType.Team2)) + if (SelectedMode == GameModePreset.PvP && + MultiplayerPreferences.Instance.TeamPreference is not (CharacterTeamType.Team1 or CharacterTeamType.Team2)) { + if (TeamPreferenceListBox == null) + { + //refresh player frame to ensure we create the team preference list box + UpdatePlayerFrame(characterInfo: GameMain.Client?.CharacterInfo); + } + // we are in PvP but don't have a team selected, so we can't select a sub // and also highlight the team selection list - foreach (GUIComponent child in TeamPreferenceListBox.Content.Children) { if (child.UserData is CharacterTeamType.None) { continue; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SlideshowPlayer.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SlideshowPlayer.cs index 458614977..c73d5eeb0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SlideshowPlayer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SlideshowPlayer.cs @@ -1,4 +1,5 @@ -using Microsoft.Xna.Framework; +using Barotrauma.Extensions; +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using System.Linq; @@ -42,6 +43,8 @@ namespace Barotrauma protected override void Update(float deltaTime) { + if (slideshowPrefab.Slides.IsEmpty) { return; } + var slide = slideshowPrefab.Slides[Math.Min(state, slideshowPrefab.Slides.Length - 1)]; if (!Visible || (Finished && timer > slide.FadeOutDuration)) { return; } @@ -104,6 +107,7 @@ namespace Barotrauma private void RefreshText() { + if (slideshowPrefab.Slides.IsEmpty) { return; } var slide = slideshowPrefab.Slides[Math.Min(state, slideshowPrefab.Slides.Length - 1)]; currentText = slide.Text .Replace("[submarine]", Submarine.MainSub?.Info.Name ?? GameMain.GameSession?.SubmarineInfo?.Name ?? "Unknown") diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs index cd3420642..256c6bbbf 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs @@ -2088,7 +2088,7 @@ namespace Barotrauma if (packageToSaveTo != null) { var modProject = new ModProject(packageToSaveTo); - var fileListPath = packageToSaveTo.Path; + string fileListPath = packageToSaveTo.Path; if (packageToSaveTo == ContentPackageManager.VanillaCorePackage) { #if !DEBUG @@ -2104,10 +2104,12 @@ namespace Barotrauma SubmarineType.Wreck => "Content/Map/Wrecks/{0}", SubmarineType.BeaconStation => "Content/Map/BeaconStations/{0}", SubmarineType.EnemySubmarine => "Content/Map/EnemySubmarines/{0}", - SubmarineType.OutpostModule => MainSub.Info.FilePath.Contains("RuinModules") ? "Content/Map/RuinModules/{0}" : "Content/Map/Outposts/{0}", + SubmarineType.OutpostModule => MainSub.Info.FilePath != null && MainSub.Info.FilePath.Contains("RuinModules") ? "Content/Map/RuinModules/{0}" : "Content/Map/Outposts/{0}", _ => throw new InvalidOperationException() }, savePath); modProject.ModVersion = ""; + addSubAndSave(modProject, savePath, fileListPath); + return true; } else { @@ -2116,27 +2118,41 @@ namespace Barotrauma if (existingFilePath != null) { savePath = existingFilePath; + addSubAndSave(modProject, savePath, fileListPath); + return true; } //otherwise make sure we're not trying to overwrite another sub in the same package else { - savePath = Path.Combine(packageToSaveTo.Dir, savePath); - if (File.Exists(savePath)) + var existingSubInContentPackage = + SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Type == MainSub?.Info?.Type && packageToSaveTo.GetFiles().Any(f => f.Path == s.FilePath)); + if (existingSubInContentPackage != null) { - var verification = new GUIMessageBox(TextManager.Get("warning"), TextManager.Get("subeditor.duplicatesubinpackage"), - new LocalizedString[] { TextManager.Get("yes"), TextManager.Get("no") }); + string directoryName = Path.GetDirectoryName(existingSubInContentPackage.FilePath); + string directoryNameRelativeToPackage = Path.GetRelativePath(Path.GetDirectoryName(packageToSaveTo.Path), directoryName); + var verification = new GUIMessageBox(string.Empty, TextManager.GetWithVariable("subeditor.saveinexistingfolderprompt", "[folder]", directoryNameRelativeToPackage), + [TextManager.GetWithVariable("subeditor.saveinexistingfolderprompt.yes", "[folder]", directoryNameRelativeToPackage), TextManager.Get("subeditor.saveinexistingfolderprompt.no")]); verification.Buttons[0].OnClicked = (_, _) => { - addSubAndSave(modProject, savePath, fileListPath); + savePath = Path.Combine(directoryNameRelativeToPackage, savePath); + trySaveWithDuplicateCheck(modProject, fileListPath); + verification.Close(); + return true; + }; verification.Buttons[1].OnClicked = (_, _) => + { + trySaveWithDuplicateCheck(modProject, fileListPath); verification.Close(); return true; }; - verification.Buttons[1].OnClicked = verification.Close; - return false; + return true; + } + else + { + trySaveWithDuplicateCheck(modProject, fileListPath); + return true; } } } - addSubAndSave(modProject, savePath, fileListPath); } else { @@ -2150,9 +2166,28 @@ namespace Barotrauma { ModProject modProject = new ModProject { Name = name }; addSubAndSave(modProject, savePath, Path.Combine(Path.GetDirectoryName(savePath), ContentPackage.FileListFileName)); + return true; } } + void trySaveWithDuplicateCheck(ModProject modProject, string fileListPath) + { + savePath = Path.Combine(packageToSaveTo.Dir, savePath); + if (File.Exists(savePath)) + { + var verification = new GUIMessageBox(TextManager.Get("warning"), TextManager.Get("subeditor.duplicatesubinpackage"), + [TextManager.Get("yes"), TextManager.Get("no")]); + verification.Buttons[0].OnClicked = (_, _) => + { + addSubAndSave(modProject, savePath, fileListPath); + verification.Close(); + return true; + }; + verification.Buttons[1].OnClicked = verification.Close; + } + addSubAndSave(modProject, savePath, fileListPath); + } + void addSubAndSave(ModProject modProject, string filePath, string packagePath) { filePath = filePath.CleanUpPath(); @@ -2234,8 +2269,6 @@ namespace Barotrauma subNameLabel.Text = ToolBox.LimitString(MainSub.Info.Name, subNameLabel.Font, subNameLabel.Rect.Width); } } - - return false; } private void CreateSaveScreen(bool quickSave = false) @@ -2653,13 +2686,15 @@ namespace Barotrauma //--------------------------------------- - var extraSettingsContainer = new GUILayoutGroup(new RectTransform(new Vector2(1, 0.5f), subTypeDependentSettingFrame.RectTransform)) + var extraSettingsContainer = new GUILayoutGroup(new RectTransform(new Vector2(1, 0.75f), subTypeDependentSettingFrame.RectTransform)) { CanBeFocused = true, Visible = false, Stretch = true }; + var extraSubInfo = GetExtraSubmarineInfo(MainSub?.Info); + var minDifficultyGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), extraSettingsContainer.RectTransform), isHorizontal: true) { Stretch = true @@ -2668,12 +2703,12 @@ namespace Barotrauma TextManager.Get("minleveldifficulty"), textAlignment: Alignment.CenterLeft, wrap: true); var numInput = new GUINumberInput(new RectTransform(new Vector2(0.4f, 1.0f), minDifficultyGroup.RectTransform), NumberType.Int) { - IntValue = (int)(MainSub?.Info?.GetExtraSubmarineInfo?.MinLevelDifficulty ?? 0), + IntValue = (int)(extraSubInfo?.MinLevelDifficulty ?? 0), MinValueInt = 0, MaxValueInt = 100, OnValueChanged = (numberInput) => { - MainSub.Info.GetExtraSubmarineInfo.MinLevelDifficulty = numberInput.IntValue; + extraSubInfo.MinLevelDifficulty = numberInput.IntValue; } }; minDifficultyGroup.RectTransform.MaxSize = numInput.TextBox.RectTransform.MaxSize; @@ -2685,16 +2720,17 @@ namespace Barotrauma TextManager.Get("maxleveldifficulty"), textAlignment: Alignment.CenterLeft, wrap: true); numInput = new GUINumberInput(new RectTransform(new Vector2(0.4f, 1.0f), maxDifficultyGroup.RectTransform), NumberType.Int) { - IntValue = (int)(MainSub?.Info?.GetExtraSubmarineInfo?.MaxLevelDifficulty ?? 100), + IntValue = (int)(extraSubInfo?.MaxLevelDifficulty ?? 100), MinValueInt = 0, MaxValueInt = 100, OnValueChanged = (numberInput) => { - MainSub.Info.GetExtraSubmarineInfo.MaxLevelDifficulty = numberInput.IntValue; + extraSubInfo.MaxLevelDifficulty = numberInput.IntValue; } }; maxDifficultyGroup.RectTransform.MaxSize = numInput.TextBox.RectTransform.MaxSize; + GUITextBox missionTagsBox = CreateMissionTagsUI(extraSettingsContainer, extraSubInfo?.MissionTags ?? Enumerable.Empty(), ChangeMissionTags); //--------------------------------------- @@ -2759,15 +2795,13 @@ namespace Barotrauma triggerMissionTagsGroup.RectTransform.MaxSize = triggerMissionTagsBox.RectTransform.MaxSize; //--------------------------------------- - var enemySubmarineSettingsContainer = new GUILayoutGroup(new RectTransform(Vector2.One, subTypeDependentSettingFrame.RectTransform)) + var enemySubmarineSettingsContainer = new GUILayoutGroup(new RectTransform(Vector2.One, extraSettingsContainer.RectTransform)) { CanBeFocused = true, Visible = false, Stretch = true }; - // ------------------- - var enemySubmarineRewardGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), enemySubmarineSettingsContainer.RectTransform), isHorizontal: true) { Stretch = true @@ -2802,26 +2836,6 @@ namespace Barotrauma } }; enemySubmarineDifficultyGroup.RectTransform.MaxSize = numInput.TextBox.RectTransform.MaxSize; - var enemySubmarineTagsGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), enemySubmarineSettingsContainer.RectTransform), isHorizontal: true) - { - Stretch = true - }; - new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), enemySubmarineTagsGroup.RectTransform), - TextManager.Get("sp.item.tags.name"), textAlignment: Alignment.CenterLeft, wrap: true); - var tagsBox = new GUITextBox(new RectTransform(new Vector2(0.4f, 1.0f), enemySubmarineTagsGroup.RectTransform)) - { - OnEnterPressed = ChangeEnemySubTags, - OverflowClip = true, - Text = "default" - }; - tagsBox.OnDeselected += (textbox, _) => ChangeEnemySubTags(textbox, textbox.Text); - if (MainSub?.Info?.EnemySubmarineInfo?.MissionTags != null) - { - tagsBox.Text = string.Join(',', MainSub.Info.EnemySubmarineInfo.MissionTags); - } - - enemySubmarineTagsGroup.RectTransform.MaxSize = tagsBox.RectTransform.MaxSize; - enemySubmarineSettingsContainer.RectTransform.MinSize = new Point(0, enemySubmarineSettingsContainer.RectTransform.Children.Sum(c => c.Children.Any() ? c.Children.Max(c2 => c2.MinSize.Y) : 0)); //-------------------------------------------------------- @@ -2870,7 +2884,6 @@ namespace Barotrauma return true; } }; - beaconSettingsContainer.RectTransform.MinSize = new Point(0, beaconSettingsContainer.RectTransform.Children.Sum(c => c.Children.Any() ? c.Children.Max(c2 => c2.MinSize.Y) : 0)); //------------------------------------------------------------------ @@ -3110,13 +3123,26 @@ namespace Barotrauma { MainSub.Info.EnemySubmarineInfo ??= new EnemySubmarineInfo(MainSub.Info); } + + // Update mission tags UI when submarine type changes + var newExtraSubInfo = GetExtraSubmarineInfo(MainSub.Info); + if (newExtraSubInfo != null) + { + missionTagsBox.Text = string.Join(',', newExtraSubInfo.MissionTags); + } + previewImageButtonHolder.Children.ForEach(c => c.Enabled = MainSub.Info.AllowPreviewImage); outpostModuleSettingsContainer.Visible = type == SubmarineType.OutpostModule; - extraSettingsContainer.Visible = type == SubmarineType.BeaconStation || type == SubmarineType.Wreck; + extraSettingsContainer.Visible = newExtraSubInfo != null; beaconSettingsContainer.Visible = type == SubmarineType.BeaconStation; + beaconSettingsContainer.IgnoreLayoutGroups = !beaconSettingsContainer.Visible; enemySubmarineSettingsContainer.Visible = type == SubmarineType.EnemySubmarine; + enemySubmarineSettingsContainer.IgnoreLayoutGroups = !enemySubmarineSettingsContainer.Visible; subSettingsContainer.Visible = type == SubmarineType.Player; outpostSettingsContainer.Visible = type == SubmarineType.Outpost; + + extraSettingsContainer.Recalculate(); + return true; }; subSettingsContainer.RectTransform.MinSize = new Point(0, subSettingsContainer.RectTransform.Children.Sum(c => c.Children.Any() ? c.Children.Max(c2 => c2.MinSize.Y) : 0)); @@ -3412,6 +3438,18 @@ namespace Barotrauma if (quickSave) { SaveSub(packageToSaveInList.SelectedData as ContentPackage); } } + private static ExtraSubmarineInfo GetExtraSubmarineInfo(SubmarineInfo subInfo) + { + if (subInfo == null) { return null; } + return subInfo.Type switch + { + SubmarineType.BeaconStation => subInfo.BeaconStationInfo, + SubmarineType.Wreck => subInfo.WreckInfo, + SubmarineType.EnemySubmarine => subInfo.EnemySubmarineInfo, + _ => null, + }; + } + private void CreateSaveAssemblyScreen() { SetMode(Mode.Default); @@ -4948,29 +4986,46 @@ namespace Barotrauma return true; } - private bool ChangeEnemySubTags(GUITextBox textBox, string text) + private bool ChangeMissionTags(GUITextBox textBox, string text) { - if (string.IsNullOrWhiteSpace(text)) - { - textBox.Flash(GUIStyle.Red); - return false; - } + // Get the ExtraSubmarineInfo (all types inherit MissionTags from the parent class) + var extraSubInfo = GetExtraSubmarineInfo(MainSub?.Info); - if (MainSub.Info.EnemySubmarineInfo is { } enemySubInfo) + if (extraSubInfo?.MissionTags != null) { - enemySubInfo.MissionTags.Clear(); + extraSubInfo.MissionTags.Clear(); string[] tags = text.Split(','); foreach (string tag in tags) { - enemySubInfo.MissionTags.Add(tag.ToIdentifier()); + extraSubInfo.MissionTags.Add(tag.ToIdentifier()); } } + textBox.Text = text; textBox.Flash(GUIStyle.Green); - return true; } + private static GUITextBox CreateMissionTagsUI(GUIComponent parent, IEnumerable missionTags, GUITextBox.OnEnterHandler onEnterPressed) + { + var missionTagsGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), parent.RectTransform), isHorizontal: true) + { + Stretch = true + }; + new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), missionTagsGroup.RectTransform), + TextManager.Get("subeditor.missiontags"), textAlignment: Alignment.CenterLeft, wrap: true); + var tagsBox = new GUITextBox(new RectTransform(new Vector2(0.4f, 1.0f), missionTagsGroup.RectTransform)) + { + ToolTip = TextManager.Get("subeditor.missiontags.tooltip"), + OnEnterPressed = onEnterPressed, + OverflowClip = true, + Text = missionTags != null ? string.Join(',', missionTags) : "" + }; + tagsBox.OnDeselected += (textbox, _) => onEnterPressed(textbox, textbox.Text); + missionTagsGroup.RectTransform.MaxSize = tagsBox.RectTransform.MaxSize; + return tagsBox; + } + private void ChangeSubDescription(GUITextBox textBox, string text) { if (MainSub != null) @@ -5851,7 +5906,8 @@ namespace Barotrauma if (entity is Item item && item.Components.Any(ic => ic is not ConnectionPanel && ic is not Repairable && ic.GuiFrame != null)) { var container = item.GetComponents().ToList(); - if (!container.Any() || container.Any(ic => ic?.DrawInventory ?? false)) + if (container.None() || container.Any(ic => ic?.DrawInventory ?? false) || + item.GetComponent() != null) { OpenItem(item); break; diff --git a/Barotrauma/BarotraumaClient/ClientSource/SpamServerFilter.cs b/Barotrauma/BarotraumaClient/ClientSource/SpamServerFilter.cs index c2f07cf96..9a7fceda2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/SpamServerFilter.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/SpamServerFilter.cs @@ -36,11 +36,18 @@ namespace Barotrauma { public bool IsFiltered(ServerInfo info) { - if (!Filters.Any()) { return false; } + if (Filters.IsEmpty) { return false; } foreach (var (type, value) in Filters) { - if (!IsFiltered(info, type, value)) { return false; } + try + { + if (!IsFiltered(info, type, value)) { return false; } + } + catch (Exception e) + { + DebugConsole.ThrowError($"Failed to check filter type {type} on the server info {(info.ServerName ?? "null")}.", e); + } } return true; @@ -63,7 +70,9 @@ namespace Barotrauma SpamServerFilterType.MessageEquals => CompareEquals(desc, value), SpamServerFilterType.MessageContains => CompareContains(desc, value), - SpamServerFilterType.Endpoint => info.Endpoints.First().StringRepresentation.Equals(value, StringComparison.OrdinalIgnoreCase), + SpamServerFilterType.Endpoint => + info.Endpoints != null && + info.Endpoints.First().StringRepresentation.Equals(value, StringComparison.OrdinalIgnoreCase), SpamServerFilterType.PlayerCountLarger => info.PlayerCount > parsedInt, SpamServerFilterType.PlayerCountExact => info.PlayerCount == parsedInt, @@ -79,10 +88,23 @@ namespace Barotrauma }; static bool CompareEquals(string a, string b) - => a.Equals(b, StringComparison.OrdinalIgnoreCase) || Homoglyphs.Compare(a, b); + { + if (a == null || b == null) + { + return a == b; + } + return a.Equals(b, StringComparison.OrdinalIgnoreCase) || Homoglyphs.Compare(a, b); + + } static bool CompareContains(string a, string b) - => a.Contains(b, StringComparison.OrdinalIgnoreCase); + { + if (a == null || b == null) + { + return a == b; + } + return a.Contains(b, StringComparison.OrdinalIgnoreCase); + } } public XElement Serialize() diff --git a/Barotrauma/BarotraumaClient/ClientSource/Utils/P2POwnerDoSProtection.cs b/Barotrauma/BarotraumaClient/ClientSource/Utils/P2POwnerDoSProtection.cs new file mode 100644 index 000000000..6bb34f26e --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Utils/P2POwnerDoSProtection.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Barotrauma.Networking; + +namespace Barotrauma +{ + internal sealed class P2POwnerDoSProtection + { + /// + /// Delegate to be called when a client has sent too many packets in a short time. + /// + /// The endpoint of the client. + /// A suggestion to ban the client due to too many kicks. + public delegate void ExcessivePacketDelegate(P2PEndpoint endpoint, bool shouldBan); + + private readonly Dictionary packetCounts = new(); + private readonly Dictionary kicksByEndpoint = new(); + + private readonly ExcessivePacketDelegate onExcessivePackets; + private double nextCheckTime; + + // check every 10 seconds + private const int PacketCheckTimer = 10; + + public P2POwnerDoSProtection(ExcessivePacketDelegate onExcessivePackets) + { + this.onExcessivePackets = onExcessivePackets; + nextCheckTime = Timing.TotalTime + PacketCheckTimer; + } + + private static int MaxPacketCount + { + get + { + // Normally the packet limit is per second, but we want to check faster than that. + // multiply by 1.2 to allow for some leeway to allow the DoS protection deeper in the stack + // to handle this first. + const float limitMultiplier = (PacketCheckTimer / 60f) * 1.2f; + + if (GameMain.Client?.ServerSettings is not { } serverSettings) + { + // Shouldn't happen, but just in case. + return (int)MathF.Ceiling(ServerSettings.PacketLimitDefault * limitMultiplier); + } + + return (int)MathF.Ceiling(serverSettings.MaxPacketAmount * MathF.Max(serverSettings.TickRate / (float)ServerSettings.DefaultTickRate, 1f) * limitMultiplier); + } + } + + private static bool ShouldCheck() + { + if (GameMain.Client?.ServerSettings is { } serverSettings) + { + return serverSettings.EnableDoSProtection && serverSettings.MaxPacketAmount > ServerSettings.PacketLimitMin; + } + + return false; + } + + public void OnPacket(P2PEndpoint endpoint) + { + if (!ShouldCheck()) { return; } + + // count = default(int), if the endpoint is not in the dictionary + packetCounts.TryGetValue(endpoint, out int count); + packetCounts[endpoint] = ++count; + + if (Timing.TotalTime > nextCheckTime) + { + foreach (P2PEndpoint e in packetCounts.Keys.ToArray()) + { + CheckForExcessivePackets(e, count); + } + packetCounts.Clear(); + nextCheckTime = Timing.TotalTime + PacketCheckTimer; + } + } + + private void CheckForExcessivePackets(P2PEndpoint endpoint, int count) + { + if (count > MaxPacketCount) + { + kicksByEndpoint.TryGetValue(endpoint, out int kickCount); + kicksByEndpoint[endpoint] = ++kickCount; + + onExcessivePackets(endpoint, kickCount > 3); + + packetCounts.Remove(endpoint); + } + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index 14dd9e549..dfc77450d 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 1.9.8.0 + 1.10.5.0 Copyright © FakeFish 2018-2024 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index ebf575985..011b3992c 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 1.9.8.0 + 1.10.5.0 Copyright © FakeFish 2018-2024 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index 7787e4d7c..0ef2232a4 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 1.9.8.0 + 1.10.5.0 Copyright © FakeFish 2018-2024 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index 9d39aaae1..68d267482 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 1.9.8.0 + 1.10.5.0 Copyright © FakeFish 2018-2023 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index e36baf22a..fd8876d56 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 1.9.8.0 + 1.10.5.0 Copyright © FakeFish 2018-2023 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs index 13f248b8e..74b208a47 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs @@ -1850,6 +1850,10 @@ namespace Barotrauma HandleCommandForCrewOrSingleCharacter(args, ToggleGodMode, client); void ToggleGodMode(Character targetCharacter) { + if (args.Length > 1 && bool.TryParse(args[1], out bool removeafflictions)) + { + if (removeafflictions) { targetCharacter.CharacterHealth.RemoveAllAfflictions(); } + } targetCharacter.GodMode = godmodeStateOnFirstCharacter ?? !targetCharacter.GodMode; godmodeStateOnFirstCharacter = targetCharacter.GodMode; GameMain.NetworkMember.CreateEntityEvent(targetCharacter, new Character.CharacterStatusEventData()); diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs index 0d8d1793f..7adb32f32 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -1530,9 +1530,12 @@ namespace Barotrauma modeElement.Add(GameMain.GameSession?.EventManager.Save()); } - foreach (Identifier unlockedRecipe in GameMain.GameSession.UnlockedRecipes) + foreach ((CharacterTeamType team, Identifier unlockedRecipe) in GameMain.GameSession.UnlockedRecipes) { - modeElement.Add(new XElement("unlockedrecipe", new XAttribute("identifier", unlockedRecipe))); + modeElement.Add( + new XElement("unlockedrecipe", + new XAttribute("identifier", unlockedRecipe), + new XAttribute("team", team))); } CampaignMetadata?.Save(modeElement); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Pump.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Pump.cs index a026feea0..854291a36 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Pump.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Pump.cs @@ -7,7 +7,7 @@ namespace Barotrauma.Items.Components const float NetworkUpdateInterval = 5.0f; private float networkUpdateTimer; - partial void UpdateProjSpecific(float deltaTime) + partial void UpdateNetworking(float deltaTime) { networkUpdateTimer -= deltaTime; if (networkUpdateTimer <= 0.0f) @@ -51,6 +51,7 @@ namespace Barotrauma.Items.Components msg.WriteRangedInteger((int)(flowPercentage / 10.0f), -10, 10); msg.WriteBoolean(IsActive); msg.WriteBoolean(Hijacked); + msg.WriteBoolean(Disabled); if (TargetLevel != null) { msg.WriteBoolean(true); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/WifiComponent.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/WifiComponent.cs index 12105db73..9cba92029 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/WifiComponent.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/WifiComponent.cs @@ -4,6 +4,8 @@ namespace Barotrauma.Items.Components { partial class WifiComponent { + private readonly int[] networkReceivedChannelMemory = new int[ChannelMemorySize]; + public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null) { SharedEventWrite(msg); @@ -11,8 +13,21 @@ namespace Barotrauma.Items.Components public void ServerEventRead(IReadMessage msg, Client c) { - SharedEventRead(msg); - + int newChannel = msg.ReadRangedInteger(MinChannel, MaxChannel); + for (int i = 0; i < ChannelMemorySize; i++) + { + networkReceivedChannelMemory[i] = msg.ReadRangedInteger(MinChannel, MaxChannel); + } + + if (item.CanClientAccess(c)) + { + Channel = newChannel; + for (int i = 0; i < ChannelMemorySize; i++) + { + channelMemory[i] = networkReceivedChannelMemory[i]; + } + } + // Create an event to notify other clients about the changes item.CreateServerEvent(this); } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Inventory.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Inventory.cs index 6748365aa..3d85e08ad 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Inventory.cs @@ -59,7 +59,7 @@ namespace Barotrauma ServerLogRemovedItems(); #region local functions - bool IsInventoryAccessible() => sender.Character.CanAccessInventory(this, IsDragAndDropGiveAllowed ? CharacterInventory.AccessLevel.Allowed : CharacterInventory.AccessLevel.Limited); + bool IsInventoryAccessible() => sender.Character.CanAccessInventory(this, IsDragAndDropGiveAllowed ? CharacterInventory.AccessLevel.AllowFriendly : CharacterInventory.AccessLevel.AllowBotsAndPets); void CreateCorrectiveNetworkEvent() { @@ -177,7 +177,7 @@ namespace Barotrauma if (item.GetComponent() is not Pickable pickable || (pickable.IsAttached && !pickable.PickingDone) || item.AllowedSlots.None() || !item.IsInteractable(sender.Character)) { - DebugConsole.AddWarning($"Client {sender.Name} tried to pick up a non-pickable item \"{item}\" (parent inventory: {item.ParentInventory?.Owner.ToString() ?? "null"})", + DebugConsole.AddWarning($"Client {sender.Name} failed to put \"{item}\" in the inventory of {Owner} (parent inventory: {item.ParentInventory?.Owner.ToString() ?? "null"})", item.Prefab.ContentPackage); continue; } @@ -187,13 +187,20 @@ namespace Barotrauma var holdable = item.GetComponent(); if (holdable != null && !holdable.CanBeDeattached()) { continue; } + bool itemAccessDenied = !prevItems.Contains(item) && !itemAccessibility[item] && (sender.Character == null || item.PreviousParentInventory == null || !sender.Character.CanAccessInventory(item.PreviousParentInventory)); + //more restricted "adding" of handcuffs: we can't allow putting handcuffs on a player just because dragging and dropping is allowed + if (item.HasTag(Tags.HandLockerItem) && !itemAccessDenied) + { + itemAccessDenied = + !sender.Character.CanAccessInventory(this, CharacterInventory.AccessLevel.AllowBotsAndPets); + } if (itemAccessDenied) { #if DEBUG || UNSTABLE - DebugConsole.NewMessage($"Client {sender.Name} failed to pick up item \"{item}\" (parent inventory: {item.ParentInventory?.Owner.ToString() ?? "null"}). No access.", Color.Yellow); + DebugConsole.NewMessage($"Client {sender.Name} failed to put \"{item}\" in the inventory of {Owner} (parent inventory: {item.ParentInventory?.Owner.ToString() ?? "null"}). No access.", Color.Yellow); #endif if (item.body != null && !sender.PendingPositionUpdates.Contains(item)) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index b87e85a62..4ce36cbcf 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -3716,7 +3716,7 @@ namespace Barotrauma.Networking UpdateVoteStatus(); - SendChatMessage(peerDisconnectPacket.ChatMessage(client).Value, ChatMessageType.Server, changeType: peerDisconnectPacket.ConnectionChangeType); + SendChatMessage(peerDisconnectPacket.ChatMessage(client.Name).Value, ChatMessageType.Server, changeType: peerDisconnectPacket.ConnectionChangeType); UpdateCrewFrame(); @@ -4234,13 +4234,14 @@ namespace Barotrauma.Networking serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable); } - public void UnlockRecipe(Identifier identifier) + public void UnlockRecipe(CharacterTeamType team, Identifier identifier) { + IWriteMessage msg = new WriteOnlyMessage(); + msg.WriteByte((byte)ServerPacketHeader.UNLOCKRECIPE); + msg.WriteByte((byte)team); + msg.WriteIdentifier(identifier); foreach (var client in connectedClients) { - IWriteMessage msg = new WriteOnlyMessage(); - msg.WriteByte((byte)ServerPacketHeader.UNLOCKRECIPE); - msg.WriteIdentifier(identifier); serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable); } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/P2PServerPeer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/P2PServerPeer.cs index 3b317e7a7..c170aef88 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/P2PServerPeer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/P2PServerPeer.cs @@ -137,6 +137,10 @@ namespace Barotrauma.Networking { Disconnect(connectedClient.Connection, PeerDisconnectPacket.Banned(banReason)); } + else + { + SendDisconnectMessage(senderEndpoint, PeerDisconnectPacket.Banned(banReason)); + } } else if (packetHeader.IsDisconnectMessage()) { @@ -149,9 +153,10 @@ namespace Barotrauma.Networking Disconnect(connectedClient.Connection, PeerDisconnectPacket.WithReason(DisconnectReason.Disconnected)); } } - else if (packetHeader.IsHeartbeatMessage()) + else if (packetHeader.IsHeartbeatMessage() || packetHeader.IsDoSProtectionMessage()) { - //message exists solely as a heartbeat, ignore its contents + // ignore these messages, heartbeat messages just need to be acknowledged, + // and only the owner should be sending DoS protection messages return; } else if (packetHeader.IsConnectionInitializationStep()) @@ -206,6 +211,49 @@ namespace Barotrauma.Networking return; } + if (packetHeader.IsDoSProtectionMessage()) + { + var packet = INetSerializableStruct.Read(inc); + var disconnectPacket = INetSerializableStruct.Read(inc); + + if (packet.Endpoint.TryUnwrap(out var endpoint)) + { + PendingClient? pendingClient = pendingClients.Find(c => c.Connection.Endpoint == endpoint); + ClientConnectionData? connectedClientData = connectedClients.Find(c => c.Connection.Endpoint == endpoint); + string? clientName; + + if (pendingClient != null) + { + clientName = pendingClient.Name; + if (packet.ShouldBan) + { + BanPendingClient(pendingClient, disconnectPacket.AdditionalInformation, duration: null); + } + RemovePendingClient(pendingClient, disconnectPacket); + } + else if (connectedClientData != null) + { + clientName = connectedClientData.TryGetClientName(); + if (packet.ShouldBan) + { + connectedClientData.BanClient(serverSettings, disconnectPacket.AdditionalInformation, duration: null); + } + Disconnect(connectedClientData.Connection, disconnectPacket); + } + else + { + string errorMsg = $"Unable to remove client {endpoint} for triggering DoS protection, client not found in pending or connected clients"; + DebugConsole.ThrowError(errorMsg); + GameServer.Log(errorMsg, ServerLog.MessageType.Error); + return; + } + + GameServer.Log($"Client {clientName ?? endpoint.ToString()} {(packet.ShouldBan ? "banned" : "disconnected")} due to DoS protection (Sending too many packets).", ServerLog.MessageType.DoSProtection); + GameMain.Server?.SendChatMessage(disconnectPacket.ChatMessage(clientName).Value, ChatMessageType.Server, changeType: disconnectPacket.ConnectionChangeType); + } + return; + } + if (packetHeader.IsConnectionInitializationStep()) { if (OwnerConnection is null) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/ServerPeer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/ServerPeer.cs index e8a0e2e2e..a48385067 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/ServerPeer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/ServerPeer.cs @@ -13,7 +13,7 @@ namespace Barotrauma.Networking protected ServerPeer(Callbacks callbacks, ServerSettings serverSettings) : base(callbacks) { this.serverSettings = serverSettings; - this.connectedClients = new List(); + this.connectedClients = new List(); this.pendingClients = new List(); List contentPackageList = new List(); @@ -65,21 +65,57 @@ namespace Barotrauma.Networking } } - protected sealed class ConnectedClient + protected sealed class ClientConnectionData(TConnection connection) { - public readonly TConnection Connection; - public readonly MessageFragmenter Fragmenter; - public readonly MessageDefragmenter Defragmenter; + public readonly TConnection Connection = connection; + public readonly MessageFragmenter Fragmenter = new(); + public readonly MessageDefragmenter Defragmenter = new(); - public ConnectedClient(TConnection connection) + /// + /// Attempts to retrieve the name of the client associated with this connection + /// from a higher layer. + /// + /// Name of the client if found, null otherwise. + public string? TryGetClientName() { - Connection = connection; - Fragmenter = new(); - Defragmenter = new(); + if (GameMain.Server?.ConnectedClients is { } connClients) + { + foreach (Client? client in connClients) + { + if (client?.Connection is not { } clientConnection) { continue; } + + if (clientConnection.EndpointMatches(Connection.Endpoint) ) + { + return client.Name; + } + } + } + + return null; + } + + public void BanClient(ServerSettings settings, string banReason, TimeSpan? duration) + { + string clientName = TryGetClientName() ?? "Player"; + + Connection.AccountInfo.OtherMatchingIds.ForEach(BanAccountId); + + if (Connection.AccountInfo.AccountId.TryUnwrap(out var accountId)) + { + BanAccountId(accountId); + } + else + { + settings.BanList.BanPlayer(clientName, Connection.Endpoint, banReason, duration); + } + return; + + void BanAccountId(AccountId id) + => settings.BanList.BanPlayer(clientName, id, banReason, duration); } } - protected readonly List connectedClients; + protected readonly List connectedClients; protected readonly List pendingClients; protected readonly ServerSettings serverSettings; @@ -235,7 +271,7 @@ namespace Barotrauma.Networking if (pendingClient.InitializationStep == ConnectionInitialization.Success) { TConnection newConnection = pendingClient.Connection; - connectedClients.Add(new ConnectedClient(newConnection)); + connectedClients.Add(new ClientConnectionData(newConnection)); pendingClients.Remove(pendingClient); callbacks.OnInitializationComplete.Invoke(newConnection, pendingClient.Name); diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index 32e783019..808dc379b 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 1.9.8.0 + 1.10.5.0 Copyright © FakeFish 2018-2023 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]PipeTestSub/Dugong_PipeTest.sub b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]PipeTestSub/Dugong_PipeTest.sub new file mode 100644 index 0000000000000000000000000000000000000000..e0d5f17b77d05902411710aab64d6e5f392dc68b GIT binary patch literal 216810 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�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⋙&(D9{?rOXT9te*)x0(TUY(E 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 + + + \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]testGapSub/filelist.xml b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]testGapSub/filelist.xml new file mode 100644 index 000000000..bb59ee8d3 --- /dev/null +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]testGapSub/filelist.xml @@ -0,0 +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 new file mode 100644 index 0000000000000000000000000000000000000000..9f730ea4c709c8e9cb9dabb8e902213d7dc246c6 GIT binary patch literal 74550 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 literal 0 HcmV?d00001 diff --git a/Barotrauma/BarotraumaShared/ModLists/Release checklist mods.xml b/Barotrauma/BarotraumaShared/ModLists/Release checklist mods.xml index 8a2bef012..80da34a6a 100644 --- a/Barotrauma/BarotraumaShared/ModLists/Release checklist mods.xml +++ b/Barotrauma/BarotraumaShared/ModLists/Release checklist mods.xml @@ -6,7 +6,6 @@ - diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs index 31db3cded..1405cf7a1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs @@ -312,9 +312,10 @@ namespace Barotrauma if (!slots.HasFlag(characterInventory.SlotTypes[i])) { continue; } } targetSlot = i; - //slot free, continue var otherItem = targetInventory.GetItemAt(i); + //slot free, continue if (otherItem == null) { continue; } + if (!otherItem.IsInteractable(Character)) { return false; } //try to move the existing item to LimbSlot.Any and continue if successful if (otherItem.AllowedSlots.Contains(InvSlotType.Any) && targetInventory.TryPutItem(otherItem, Character, CharacterInventory.AnySlot)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs index 8f0293ba7..f7122bff5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs @@ -1092,6 +1092,8 @@ namespace Barotrauma } if (!isFleeing) { + CheckForDraggedCorpses(); + foreach (Character target in Character.CharacterList) { if (target.CurrentHull != hull) { continue; } @@ -1209,6 +1211,34 @@ namespace Barotrauma } } } + + private void CheckForDraggedCorpses() + { + if (Character.IsOnPlayerTeam) { return; } + if (Character.Submarine is not { Info.IsOutpost: true }) { return; } + + //find corpses in the same team + foreach (Character otherCharacter in Character.CharacterList) + { + if (otherCharacter.SelectedCharacter == null || + !otherCharacter.SelectedCharacter.IsDead || + otherCharacter.SelectedCharacter.TeamID != Character.TeamID || + otherCharacter.IsInstigator) + { + continue; + } + + if (!Character.CanSeeTarget(otherCharacter)) { continue; } + + // Player is dragging a corpse from our team + string dialogTag = Character.IsSecurity ? "dialogdraggingcorpsereactionsecurity" : "dialogdraggingcorpsereaction"; + Character.Speak(TextManager.Get(dialogTag).Value, messageType: null, + delay: Rand.Range(0.5f, 1.0f), identifier: "dialogdraggingcorpsereaction".ToIdentifier(), minDurationBetweenSimilar: 10.0f); + + AddCombatObjective(Character.IsSecurity ? AIObjectiveCombat.CombatMode.Arrest : AIObjectiveCombat.CombatMode.Retreat, otherCharacter); + break; // Only react to one at a time + } + } public override void OnHealed(Character healer, float healAmount) { @@ -1660,6 +1690,10 @@ namespace Barotrauma public AIObjective SetForcedOrder(Order order) { var objective = ObjectiveManager.CreateObjective(order); + if (order != null && !order.IsDismissal) + { + System.Diagnostics.Debug.Assert(objective != null); + } ObjectiveManager.SetForcedOrder(objective); return objective; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/NPCConversation.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/NPCConversation.cs index 46e1e6aff..d1ca09d38 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/NPCConversation.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/NPCConversation.cs @@ -71,7 +71,7 @@ namespace Barotrauma private static List GetCurrentFlags(Character speaker) { var currentFlags = new List(); - if (Submarine.MainSub != null && Submarine.MainSub.AtDamageDepth) { currentFlags.Add("SubmarineDeep".ToIdentifier()); } + if (Submarine.MainSub != null && Submarine.MainSub.AtCosmeticDamageDepth) { currentFlags.Add("SubmarineDeep".ToIdentifier()); } if (GameMain.GameSession != null && Level.Loaded != null) { @@ -84,7 +84,6 @@ namespace Barotrauma if (GameMain.GameSession.RoundDuration < 120.0f && speaker?.CurrentHull != null && GameMain.GameSession.Map?.CurrentLocation?.Reputation?.Value >= 0.0f && - (speaker.TeamID == CharacterTeamType.FriendlyNPC || speaker.TeamID == CharacterTeamType.None) && Character.CharacterList.Any(c => c.TeamID != speaker.TeamID && c.CurrentHull == speaker.CurrentHull)) { currentFlags.Add("EnterOutpost".ToIdentifier()); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItems.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItems.cs index a8eeb0daf..b5304e13c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItems.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItems.cs @@ -101,6 +101,7 @@ namespace Barotrauma public static bool IsValidTarget(Item item, Character character, bool checkInventory, bool allowUnloading = true, bool requireValidContainer = true, bool ignoreItemsMarkedForDeconstruction = true) { if (item == null) { return false; } + if (item.GetComponents().None(c => c is not Door && c.CanBePicked)) { return false; } if (item.DontCleanUp) { return false; } if (item.Illegitimate == character.IsOnPlayerTeam) { return false; } if (item.ParentInventory != null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs index 59d930560..092c07626 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs @@ -65,7 +65,7 @@ namespace Barotrauma private readonly AIObjectiveFindSafety findSafety; private readonly HashSet weapons = new HashSet(); - private readonly HashSet ignoredWeapons = new HashSet(); + private readonly HashSet ignoredWeapons = new HashSet(); private AIObjectiveContainItem seekAmmunitionObjective; private AIObjectiveGoTo retreatObjective; @@ -503,7 +503,8 @@ namespace Barotrauma HashSet allWeapons = FindWeaponsFromInventory(); while (allWeapons.Any()) { - Weapon = GetWeapon(allWeapons, out _weaponComponent); + Weapon = GetWeapon(allWeapons, out ItemComponent newWeaponComponent); + _weaponComponent = newWeaponComponent; if (Weapon == null) { // No weapons @@ -512,7 +513,7 @@ namespace Barotrauma if (!character.Inventory.Contains(Weapon) || WeaponComponent == null) { // Not in the inventory anymore or cannot find the weapon component - allWeapons.Remove(WeaponComponent); + allWeapons.RemoveWhere(weaponComponent => weaponComponent.Item == Weapon); Weapon = null; continue; } @@ -540,7 +541,7 @@ namespace Barotrauma else { // No ammo and should not try to seek ammo. - allWeapons.Remove(WeaponComponent); + allWeapons.RemoveWhere(weaponComponent => weaponComponent.Item == Weapon); Weapon = null; } } @@ -980,7 +981,6 @@ namespace Barotrauma weapons.Clear(); foreach (var item in character.Inventory.AllItems) { - if (ignoredWeapons.Contains(item)) { continue; } GetWeapons(item, weapons); if (item.OwnInventory != null) { @@ -990,11 +990,12 @@ namespace Barotrauma return weapons; } - private static void GetWeapons(Item item, ICollection weaponList) + private void GetWeapons(Item item, ICollection weaponList) { if (item == null) { return; } foreach (var component in item.Components) { + if (ignoredWeapons.Contains(component)) { continue; } if (component.CombatPriority > 0) { weaponList.Add(component); @@ -1332,19 +1333,21 @@ namespace Barotrauma { SteeringManager.Reset(); RemoveSubObjective(ref seekAmmunitionObjective); - ignoredWeapons.Add(Weapon); + ignoredWeapons.Add(WeaponComponent); Weapon = null; }); } - + ///

/// Reloads the ammunition found in the inventory. /// If seekAmmo is true, tries to get find the ammo elsewhere. /// + /// True if the weapon was reloaded successfully. private bool Reload(bool seekAmmo) { if (WeaponComponent == null) { return false; } if (Weapon.OwnInventory == null) { return true; } + if (!Weapon.IsInteractable(character)) { return false; } HumanAIController.UnequipEmptyItems(Weapon, allowDestroying: !character.IsOnPlayerTeam); ImmutableHashSet ammunitionIdentifiers = null; if (WeaponComponent.RequiredItems.ContainsKey(RelatedItem.RelationType.Contained)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs index 7cc00e50a..0dee7b8f0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs @@ -443,6 +443,10 @@ namespace Barotrauma newCurrentObjective.Abandoned += () => DismissSelf(order); CurrentOrders.Add(order.WithObjective(newCurrentObjective)); } + else if (!order.IsDismissal) + { + DebugConsole.ThrowError($"Failed to create an objective for the order: {order.Name}"); + } if (!HasOrders()) { // Recreate objectives, because some of them may be removed, if impossible to complete (e.g. due to path finding) @@ -458,6 +462,9 @@ namespace Barotrauma } } + /// + /// Creates an AI objective based on the order. Note that the method can return null in the case of e.g. Dismissal orders or orders that erroneously target something non-interactable. + /// public AIObjective CreateObjective(Order order, float priorityModifier = 1) { if (order == null || order.IsDismissal) { return null; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs index 95a6cab00..658c83afb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs @@ -341,6 +341,8 @@ namespace Barotrauma { if (component?.GetType() is Type componentType) { + // Items used via a controller (i.e. turrets) are not selectable but should still be valid targets. + if (!UseController && !component.CanBeSelected) { continue; } if (componentType == ItemComponentType) { return component; } if (CanTypeBeSubclass && componentType.IsSubclassOf(ItemComponentType)) { return component; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PetBehavior.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PetBehavior.cs index db7b774b7..69b92d180 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PetBehavior.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PetBehavior.cs @@ -5,6 +5,7 @@ using Microsoft.Xna.Framework; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Runtime.InteropServices; using System.Xml.Linq; using static Barotrauma.CharacterParams; @@ -434,6 +435,21 @@ namespace Barotrauma var petBehavior = (c.AIController as EnemyAIController)?.PetBehavior; if (petBehavior == null) { continue; } + //never save hostile pets or pets left outside + if (c.TeamID == CharacterTeamType.None || + c.TeamID == CharacterTeamType.Team2 || + c.Submarine == null) + { + continue; + } + + //pets must be in a player sub or owned by someone to be persistent + if (c.Submarine is not { Info.IsPlayer: true } && + petBehavior.Owner is not { IsOnPlayerTeam: true }) + { + continue; + } + XElement petElement = new XElement("pet", new XAttribute("speciesname", c.SpeciesName), new XAttribute("ownerhash", petBehavior.Owner?.Info?.GetIdentifier() ?? 0), diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AICharacter.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AICharacter.cs index ed01119ed..879c4197f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AICharacter.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AICharacter.cs @@ -42,7 +42,7 @@ namespace Barotrauma { enemyAi.PetBehavior?.Update(deltaTime); } - if (IsDead || Vitality <= 0.0f || Stun > 0.0f || IsIncapacitated) + if (IsDead || IsUnconscious || Stun > 0.0f || IsIncapacitated) { //don't enable simple physics on dead/incapacitated characters //the ragdoll controls the movement of incapacitated characters instead of the collider, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs index 4405a791e..6d49180c4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs @@ -8,8 +8,15 @@ using Barotrauma.Extensions; namespace Barotrauma { - abstract class AnimController : Ragdoll + abstract class AnimController : Ragdoll, ISerializableEntity { + /// + /// Most of the properties in this class are read-only, but can be useful for conditionals + /// + public Dictionary SerializableProperties { get; private set; } + + public string Name => nameof(AnimController); + public Vector2 RightHandIKPos { get; protected set; } public Vector2 LeftHandIKPos { get; protected set; } @@ -200,7 +207,10 @@ namespace Barotrauma public float WalkPos { get; protected set; } - public AnimController(Character character, string seed, RagdollParams ragdollParams = null) : base(character, seed, ragdollParams) { } + public AnimController(Character character, string seed, RagdollParams ragdollParams = null) : base(character, seed, ragdollParams) + { + SerializableProperties = SerializableProperty.GetProperties(this); + } public void UpdateAnimations(float deltaTime) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index d950dbf76..ca34f5bdb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -2411,7 +2411,9 @@ namespace Barotrauma if (Inventory != null) { - if (IsKeyHit(InputType.DropItem) && Screen.Selected is { IsEditor: false }) + //this doesn't need to be run by the server, clients sync the contents of their inventory with the server instead of the inputs used to manipulate the inventory +#if CLIENT + if (IsKeyHit(InputType.DropItem) && Screen.Selected is { IsEditor: false } && CharacterHUD.ShouldDrawInventory(this)) { foreach (Item item in HeldItems) { @@ -2429,6 +2431,7 @@ namespace Barotrauma break; } } +#endif bool CanUseItemsWhenSelected(Item item) => item == null || !item.Prefab.DisableItemUsageWhenSelected; if (CanUseItemsWhenSelected(SelectedItem) && CanUseItemsWhenSelected(SelectedSecondaryItem)) @@ -2632,6 +2635,7 @@ namespace Barotrauma public bool Unequip(Item item) { if (!HasEquippedItem(item)) { return false; } + if (!item.IsInteractable(this)) { return false; } if (!TryPutItemInAnySlot(item)) { if (!TryPutItemInBag(item)) @@ -2642,7 +2646,7 @@ namespace Barotrauma return true; } - public bool CanAccessInventory(Inventory inventory, CharacterInventory.AccessLevel accessLevel = CharacterInventory.AccessLevel.Limited) + public bool CanAccessInventory(Inventory inventory, CharacterInventory.AccessLevel accessLevel = CharacterInventory.AccessLevel.AllowBotsAndPets) { if (!CanInteract || inventory.Locked) { return false; } @@ -2686,7 +2690,7 @@ namespace Barotrauma /// /// Is the inventory accessible to the character? Doesn't check if the character can actually interact with it (distance checks etc). /// - public bool IsInventoryAccessibleTo(Character character, CharacterInventory.AccessLevel accessLevel = CharacterInventory.AccessLevel.Limited) + public bool IsInventoryAccessibleTo(Character character, CharacterInventory.AccessLevel accessLevel = CharacterInventory.AccessLevel.AllowBotsAndPets) { if (Removed || Inventory == null) { return false; } if (!Inventory.AccessibleWhenAlive && !IsDead) @@ -2701,9 +2705,9 @@ namespace Barotrauma if (IsKnockedDownOrRagdolled || LockHands) { return true; } return accessLevel switch { - CharacterInventory.AccessLevel.Restricted => false, - CharacterInventory.AccessLevel.Limited => (IsBot && IsOnSameTeam()) || IsFriendlyPet(), - CharacterInventory.AccessLevel.Allowed => IsOnSameTeam() || IsFriendlyPet(), + CharacterInventory.AccessLevel.OnlyIfIncapacitated => false, + CharacterInventory.AccessLevel.AllowBotsAndPets => (IsBot && IsOnSameTeam()) || IsFriendlyPet(), + CharacterInventory.AccessLevel.AllowFriendly => IsOnSameTeam() || IsFriendlyPet(), _ => throw new NotImplementedException() }; @@ -5714,7 +5718,7 @@ namespace Barotrauma public bool HasRecipeForItem(Identifier recipeIdentifier) { - if (GameMain.GameSession != null && GameMain.GameSession.UnlockedRecipes.Contains(recipeIdentifier)) { return true; } + if (GameMain.GameSession != null && GameMain.GameSession.HasUnlockedRecipe(this, recipeIdentifier)) { return true; } return characterTalents.Any(t => t.UnlockedRecipes.Contains(recipeIdentifier)); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs index af18586d3..7720554ab 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -135,6 +135,9 @@ namespace Barotrauma private Affliction stunAffliction; public Affliction BloodlossAffliction { get => bloodlossAffliction; } + /// + /// Is the character dead or below 0 vitality and not able to stay conscious? + /// public bool IsUnconscious { get { return Character.IsDead || (Vitality <= 0.0f && !Character.HasAbilityFlag(AbilityFlags.AlwaysStayConscious)); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/CharacterFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/CharacterFile.cs index 2222c41c4..e608d06ba 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/CharacterFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/CharacterFile.cs @@ -106,6 +106,8 @@ namespace Barotrauma void AddTexturePath(string path) { if (string.IsNullOrEmpty(path)) { return; } + //if the path contains a gender variable, we can't load it yet because we don't know which gender we need + if (path.Contains("[GENDER]")) { return; } texturePaths.Add(ContentPath.FromRaw(characterPrefab.ContentPackage, ragdollParams.Texture)); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs index 6d41c1c58..8dc9bb60d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs @@ -656,13 +656,17 @@ namespace Barotrauma NewMessage("***************", Color.Cyan); })); - commands.Add(new Command("godmode", "godmode [character name]: Toggle character godmode. Makes the targeted character invulnerable to damage. If the name parameter is omitted, the controlled character will receive godmode.", + commands.Add(new Command("godmode", "godmode [character name] [remove afflictions (true/false)]: Toggle character godmode. Makes the targeted character invulnerable to damage. If the name parameter is omitted, the controlled character will receive godmode.", (string[] args) => { bool? godmodeStateOnFirstCharacter = null; HandleCommandForCrewOrSingleCharacter(args, ToggleGodMode); void ToggleGodMode(Character targetCharacter) { + if (args.Length > 1 && bool.TryParse(args[1], out bool removeafflictions)) + { + if (removeafflictions) { targetCharacter.CharacterHealth.RemoveAllAfflictions(); } + } targetCharacter.GodMode = godmodeStateOnFirstCharacter ?? !targetCharacter.GodMode; godmodeStateOnFirstCharacter = targetCharacter.GodMode; NewMessage((targetCharacter.GodMode ? "Enabled godmode on " : "Disabled godmode on ") + targetCharacter.Name, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionStateAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionStateAction.cs index 48ef33028..a6dd612be 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionStateAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionStateAction.cs @@ -21,6 +21,9 @@ [Serialize(0, IsPropertySaveable.Yes, description: "The state to set the mission to, or how much to add to the state of the mission.")] public int State { get; set; } + [Serialize(false, IsPropertySaveable.Yes, description: "If set to true, the mission is forced to fail without a chance of retrying it.")] + public bool ForceFailure { get; set; } + private bool isFinished; public MissionStateAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) @@ -31,7 +34,7 @@ DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": MissionIdentifier has not been configured.", contentPackage: element.ContentPackage); } - if (Operation == OperationType.Add && State == 0) + if (Operation == OperationType.Add && State == 0 && !ForceFailure) { DebugConsole.AddWarning($"Potential error in event \"{parentEvent.Prefab.Identifier}\": {nameof(MissionStateAction)} is set to add 0 to the mission state, which will do nothing.", contentPackage: element.ContentPackage); @@ -54,6 +57,11 @@ foreach (Mission mission in GameMain.GameSession.Missions) { if (mission.Prefab.Identifier != MissionIdentifier) { continue; } + if (ForceFailure) + { + mission.ForceFailure = true; + } + switch (Operation) { case OperationType.Set: diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs index 4974504fc..10832764f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs @@ -410,16 +410,39 @@ namespace Barotrauma public static WayPoint GetSpawnPos(SpawnLocationType spawnLocation, SpawnType? spawnPointType, IEnumerable moduleFlags = null, IEnumerable spawnpointTags = null, bool asFarAsPossibleFromAirlock = false, bool requireTaggedSpawnPoint = false, bool allowInPlayerView = true) { bool requireHull = spawnLocation == SpawnLocationType.MainSub || spawnLocation == SpawnLocationType.Outpost; - List potentialSpawnPoints = WayPoint.WayPointList.FindAll(wp => IsValidSubmarineType(spawnLocation, wp.Submarine) && (wp.CurrentHull != null || !requireHull)); - potentialSpawnPoints = potentialSpawnPoints.FindAll(wp => wp.ConnectedDoor == null && wp.Ladders == null && wp.IsTraversable); + + IEnumerable potentialSpawnPoints = WayPoint.WayPointList.FindAll(wp => IsValidSubmarineType(spawnLocation, wp.Submarine) && (wp.CurrentHull != null || !requireHull)); + potentialSpawnPoints = potentialSpawnPoints.Where(wp => wp.ConnectedDoor == null && wp.Ladders == null && wp.IsTraversable); + + //find spawnpoints with the desired type, or any random spawnpoints if not specified + IEnumerable spawnPointsWithCorrectType; + if (spawnPointType.HasValue) + { + spawnPointsWithCorrectType = potentialSpawnPoints.Where(wp => + spawnPointType.Value.HasFlag(wp.SpawnType) && + //need to handle zero (SpawnType.Path) separately, because spawnPointType will always have the flag 0 + (wp.SpawnType != 0 || spawnPointType.Value == 0)); + } + else + { + spawnPointsWithCorrectType = potentialSpawnPoints.Where(wp => wp.SpawnType != SpawnType.Path); + } + if (spawnPointsWithCorrectType.Any()) + { + potentialSpawnPoints = spawnPointsWithCorrectType; + } + + //with correct module flags, if there are any if (moduleFlags != null && moduleFlags.Any()) { - var spawnPoints = potentialSpawnPoints.Where(wp => wp.CurrentHull is Hull h && h.OutpostModuleTags.Any(moduleFlags.Contains)); - if (spawnPoints.Any()) + var spawnPointsWithCorrectFlags = potentialSpawnPoints.Where(wp => wp.CurrentHull is Hull h && h.OutpostModuleTags.Any(moduleFlags.Contains)); + if (spawnPointsWithCorrectFlags.Any()) { - potentialSpawnPoints = spawnPoints.ToList(); + potentialSpawnPoints = spawnPointsWithCorrectFlags.ToList(); } } + + //with correct spawn point tags, if there are any if (spawnpointTags != null && spawnpointTags.Any()) { var spawnPointsWithTag = potentialSpawnPoints.Where(wp => spawnpointTags.Any(tag => wp.Tags.Contains(tag) && wp.ConnectedDoor == null && wp.IsTraversable)); @@ -461,16 +484,8 @@ namespace Barotrauma return null; } - IEnumerable validSpawnPoints; - if (spawnPointType.HasValue) - { - validSpawnPoints = potentialSpawnPoints.FindAll(wp => spawnPointType.Value.HasFlag(wp.SpawnType)); - } - else - { - validSpawnPoints = potentialSpawnPoints.FindAll(wp => wp.SpawnType != SpawnType.Path); - if (!validSpawnPoints.Any()) { validSpawnPoints = potentialSpawnPoints; } - } + //spawnpoints that match the desired criteria found, choose the best one next + IEnumerable validSpawnPoints = potentialSpawnPoints; //don't spawn in an airlock module if there are other options var airlockSpawnPoints = potentialSpawnPoints.Where(wp => wp.CurrentHull?.OutpostModuleTags.Contains("airlock".ToIdentifier()) ?? false); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/BeaconMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/BeaconMission.cs index b79d4e13f..f9b102a87 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/BeaconMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/BeaconMission.cs @@ -2,6 +2,7 @@ using Microsoft.Xna.Framework; using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Xml.Linq; @@ -23,10 +24,12 @@ namespace Barotrauma private bool swarmSpawned; private readonly List monsterSets = new List(); private readonly LocalizedString sonarLabel; + private readonly ImmutableArray beaconTags; public BeaconMission(MissionPrefab prefab, Location[] locations, Submarine sub) : base(prefab, locations, sub) { swarmSpawned = false; + beaconTags = prefab.ConfigElement.GetAttributeIdentifierArray("beacontags", []).ToImmutableArray(); foreach (var monsterElement in prefab.ConfigElement.GetChildElements("monster")) { @@ -185,6 +188,29 @@ namespace Barotrauma { levelData.HasBeaconStation = true; levelData.IsBeaconActive = false; + + if (beaconTags.Length > 0) + { + var selectedBeacon = GetRandomBeaconByTags(beaconTags, levelData); + if (selectedBeacon != null) + { + levelData.ForceBeaconStation = selectedBeacon; + } + else + { + DebugConsole.ThrowError($"Beacon mission \"{Prefab.Identifier}\" could not find a suitable beacon station with beacontags \"{string.Join(", ", beaconTags)}\" for level difficulty {levelData.Difficulty:F1}.", + contentPackage: Prefab.ContentPackage); + } + } + } + + private static SubmarineInfo GetRandomBeaconByTags(ImmutableArray tags, LevelData levelData) + { + return GetRandomSubmarineByTagsAndDifficulty( + tags, + levelData, + s => s.IsBeacon, + "beacon station"); } } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs index 757561538..e6629022b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs @@ -48,7 +48,7 @@ namespace Barotrauma protected static bool IsClient => GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient; - private readonly CheckDataAction completeCheckDataAction; + protected readonly CheckDataAction completeCheckDataAction; public readonly ImmutableArray Headers; public readonly ImmutableArray Messages; @@ -110,9 +110,11 @@ namespace Barotrauma public bool Failed { - get { return failed; } + get { return failed || ForceFailure; } } + public bool ForceFailure; + public virtual bool AllowRespawning { get { return true; } @@ -541,9 +543,10 @@ namespace Barotrauma { if (GameMain.NetworkMember is not { IsClient: true }) { - completed = + completed = + !ForceFailure && DetermineCompleted() && - (completeCheckDataAction == null ||completeCheckDataAction.GetSuccess()); + (completeCheckDataAction == null || completeCheckDataAction.GetSuccess()); } if (completed) { @@ -569,6 +572,10 @@ namespace Barotrauma TimesAttempted++; EndMissionSpecific(completed); + if (ForceFailure) + { + failed = true; + } } protected abstract bool DetermineCompleted(); @@ -829,6 +836,51 @@ namespace Barotrauma cargoSpawnPos.Position.X + Rand.Range(-20.0f, 20.0f, Rand.RandSync.ServerAndClient), cargoRoom.Rect.Y - cargoRoom.Rect.Height + itemPrefab.Size.Y / 2); } + + /// + /// Gets a random submarine by tags, filtered by difficulty. Used by missions that force specific submarines (wrecks, beacons, etc.) + /// + /// Mission tags to match against + /// Random seed for selection + /// Function to filter submarines by type (e.g., s => s.IsWreck) + /// Name of submarine type for error messages (e.g., "wreck", "beacon station") + /// Selected submarine, or null if none found + protected static SubmarineInfo GetRandomSubmarineByTagsAndDifficulty( + IEnumerable tags, + LevelData levelData, + Func submarineSelector, + string submarineTypeName) + { + var rand = new MTRandom(ToolBox.StringToInt(levelData.Seed)); + float levelDifficulty = levelData.Difficulty; + + var submarinesWithTags = SubmarineInfo.SavedSubmarines + .Where(submarineSelector) + .Where(s => + { + return s.GetExtraSubmarineInfo is { } extraInfo && (tags.None() || tags.Any(t => extraInfo.MissionTags.Contains(t))); + }) + .ToList(); + + var matchingSubmarines = submarinesWithTags + .Where(s => + { + return s.GetExtraSubmarineInfo is { } extraInfo && + levelDifficulty >= extraInfo.MinLevelDifficulty && + levelDifficulty <= extraInfo.MaxLevelDifficulty; + }) + .ToList(); + + if (matchingSubmarines.Count == 0) + { + if (submarinesWithTags.Count > 0) + { + DebugConsole.ThrowError($"Found {submarinesWithTags.Count} {submarineTypeName}(s) with matching tags \"{string.Join(", ", tags)}\", but none are suitable for level difficulty {levelDifficulty:F1}."); + } + return null; + } + return matchingSubmarines[rand.Next(matchingSubmarines.Count)]; + } } class AbilityMissionMoneyGainMultiplier : AbilityObject, IAbilityValue, IAbilityMission diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs index 7f2dbba0a..602e4dd0d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs @@ -4,6 +4,7 @@ using FarseerPhysics; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; namespace Barotrauma @@ -240,6 +241,8 @@ namespace Barotrauma /// private readonly float requiredDeliveryAmount; + private readonly ImmutableArray wreckTags; + private LocalizedString pickedUpMessage; /// @@ -311,12 +314,13 @@ namespace Barotrauma : base(prefab, locations, sub) { requiredDeliveryAmount = prefab.ConfigElement.GetAttributeFloat(nameof(requiredDeliveryAmount), 0.98f); - //LevelData may not be instantiated at this point, in that case use the name identifier of the location rng = new MTRandom(ToolBox.StringToInt( locations[0].LevelData?.Seed ?? locations[0].NameIdentifier.Value + locations[1].LevelData?.Seed ?? locations[1].NameIdentifier.Value)); + wreckTags = prefab.ConfigElement.GetAttributeIdentifierArray("wrecktags", []).ToImmutableArray(); + partiallyRetrievedMessage = GetMessage(nameof(partiallyRetrievedMessage)); allRetrievedMessage = GetMessage(nameof(allRetrievedMessage)); pickedUpMessage = GetMessage(nameof(pickedUpMessage)); @@ -756,5 +760,31 @@ namespace Barotrauma target.Reset(); } } + + public override void AdjustLevelData(LevelData levelData) + { + if (wreckTags.Length > 0) + { + var selectedWreck = GetRandomWreckByTags(wreckTags, levelData); + if (selectedWreck != null) + { + levelData.ForceWreck = selectedWreck; + } + else + { + DebugConsole.ThrowError($"Salvage mission \"{Prefab.Identifier}\" could not find a suitable wreck with wrecktags \"{string.Join(", ", wreckTags)}\" for level difficulty {levelData.Difficulty:F1}.", + contentPackage: Prefab.ContentPackage); + } + } + } + + private static SubmarineInfo GetRandomWreckByTags(ImmutableArray tags, LevelData levelData) + { + return GetRandomSubmarineByTagsAndDifficulty( + tags, + levelData, + s => s.IsWreck, + "wreck"); + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs index e4d8f4c58..7bbcf7418 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs @@ -304,12 +304,17 @@ namespace Barotrauma // Check all the prices before starting the transaction to make sure the modifiers stay the same for the whole transaction var buyValues = GetBuyValuesAtCurrentLocation(storeIdentifier, itemsToPurchase.Select(i => i.ItemPrefab)); var itemsInStoreCrate = GetBuyCrateItems(storeIdentifier, create: true); - foreach (PurchasedItem item in itemsToPurchase) + //handle checking which items can be purchased and deducting money first + foreach (PurchasedItem item in itemsToPurchase.ToList()) { if (item.Quantity <= 0) { continue; } // Exchange money int itemValue = item.Quantity * buyValues[item.ItemPrefab]; - if (!campaign.TryPurchase(client, itemValue)) { continue; } + if (!campaign.TryPurchase(client, itemValue)) + { + itemsToPurchase.Remove(item); + continue; + } // Add to the purchased items var purchasedItem = itemsPurchasedFromStore.Find(pi => pi.ItemPrefab == item.ItemPrefab && pi.DeliverImmediately == item.DeliverImmediately); @@ -329,6 +334,7 @@ namespace Barotrauma } store.Balance += itemValue; } + //actually spawn the items at this point if (GameMain.NetworkMember is not { IsClient: true }) { Character targetCharacter; diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs index a64925b36..a16e44f2d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs @@ -1724,7 +1724,10 @@ namespace Barotrauma GameMain.GameSession.EventManager.Load(subElement); break; case "unlockedrecipe": - GameMain.GameSession.UnlockRecipe(subElement.GetAttributeIdentifier("identifier", Identifier.Empty), showNotifications: false); + GameMain.GameSession.UnlockRecipe( + subElement.GetAttributeEnum("team", CharacterTeamType.Team1), + subElement.GetAttributeIdentifier("identifier", Identifier.Empty), + showNotifications: false); break; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs index 355687eb6..7608e395b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs @@ -170,8 +170,8 @@ namespace Barotrauma public Submarine? Submarine { get; set; } - private readonly HashSet unlockedRecipes = new HashSet(); - public IEnumerable UnlockedRecipes => unlockedRecipes; + private readonly HashSet<(CharacterTeamType team, Identifier identifier)> unlockedRecipes = new HashSet<(CharacterTeamType, Identifier)>(); + public IEnumerable<(CharacterTeamType, Identifier)> UnlockedRecipes => unlockedRecipes; public CampaignDataPath DataPath { get; set; } @@ -1499,25 +1499,32 @@ namespace Barotrauma #endif } - public void UnlockRecipe(Identifier identifier, bool showNotifications) + public void UnlockRecipe(CharacterTeamType team, Identifier identifier, bool showNotifications) { - if (unlockedRecipes.Add(identifier)) + if (unlockedRecipes.Add((team, identifier))) { #if CLIENT if (showNotifications) { foreach (var character in GetSessionCrewCharacters(CharacterType.Both)) { + if (character.TeamID != team) { continue; } LocalizedString recipeName = TextManager.Get($"entityname.{identifier}").Fallback(identifier.Value); character.AddMessage(TextManager.GetWithVariable("recipeunlockednotification", "[name]", recipeName).Value, GUIStyle.Yellow, playSound: true); } } #else - GameMain.Server.UnlockRecipe(identifier); + GameMain.Server.UnlockRecipe(team, identifier); #endif } } + public bool HasUnlockedRecipe(Character character, Identifier itemIdentifier) + { + if (character == null) { return false; } + return unlockedRecipes.Contains((character.TeamID, itemIdentifier)); + } + public static bool IsCompatibleWithEnabledContentPackages(IList contentPackageNames, out LocalizedString errorMsg) { errorMsg = ""; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs index 15eb5f958..291903c48 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs @@ -17,15 +17,21 @@ namespace Barotrauma { /// /// How much access other characters have to the inventory? - /// = Only accessible when character is knocked down or handcuffed. - /// = Can also access inventories of bots on the same team and friendly pets. - /// = Can also access other players in the same team (used for drag and drop give). /// public enum AccessLevel { - Restricted, - Limited, - Allowed + /// + /// Only accessible when character is knocked down or handcuffed. + /// + OnlyIfIncapacitated, + /// + /// Can also access inventories of bots on the same team and friendly pets. + /// + AllowBotsAndPets, + /// + /// Can also access other players in the same team (used for drag and drop give). + /// + AllowFriendly } private readonly Character character; @@ -342,8 +348,9 @@ namespace Barotrauma { foreach (Item existingItem in slots[slot].Items.ToList()) { + if (!existingItem.IsInteractable(character)) { continue; } existingItem.Drop(user); - if (existingItem.ParentInventory != null) { existingItem.ParentInventory.RemoveItem(existingItem); } + existingItem.ParentInventory?.RemoveItem(existingItem); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs index 6ca8e627f..851e0a8b1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs @@ -1,5 +1,6 @@ using Barotrauma.Networking; using Microsoft.Xna.Framework; +using System; using System.Linq; namespace Barotrauma.Items.Components @@ -197,7 +198,16 @@ namespace Barotrauma.Items.Components item.Drop(CurrentThrower, createNetworkEvent: GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer); item.WaterDragCoefficient = WaterDragCoefficient; - item.body.ApplyLinearImpulse(throwVector * ThrowForce * item.body.Mass * 3.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); + + float throwForce = ThrowForce; + //Reduce force when aiming down + float downwardsDotProduct = Vector2.Dot(-Vector2.UnitY, throwVector); //1 when pointing directly down, 0 when sideways, -1 when up + if (downwardsDotProduct > 0) + { + throwForce *= (1.0f - downwardsDotProduct * 0.7f); + } + + item.body.ApplyLinearImpulse(throwVector * throwForce * item.body.Mass * 3.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); //disable platform collisions until the item comes back to rest again item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs index 0f2ec8e49..8d7df0c21 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs @@ -483,6 +483,9 @@ namespace Barotrauma.Items.Components public virtual bool UpdateWhenInactive => false; + [Serialize(false, IsPropertySaveable.No, "If true, the component will retain its normal functionality when the item reaches 0 condition.")] + public bool UpdateWhenBroken { get; set; } + //called when isActive is true and condition > 0.0f public virtual void Update(float deltaTime, Camera cam) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs index ae47871e3..ae7016391 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs @@ -132,6 +132,12 @@ namespace Barotrauma.Items.Components set; } + [Serialize(true, IsPropertySaveable.No, description: "Should a button that allows sorting the items alphabetically be shown in the container's UI panel?")] + public bool ShowSortButton { get; set; } + + [Serialize(true, IsPropertySaveable.No, description: "Should a button that merges items into stacks be shown in the container's UI panel?")] + public bool ShowMergeButton { get; set; } + [Serialize(true, IsPropertySaveable.Yes, description: "When this item is equipped, and you 'quick use' (double click / equip button) another equippable item, should the game attempt to move that item inside this one?")] public bool QuickUseMovesItemsInside { get; set; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs index 5bd44334a..899258e4f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs @@ -406,6 +406,7 @@ namespace Barotrauma.Items.Components if (IsOutOfPower()) { return false; } + ApplyStatusEffects(ActionType.OnUse, 1.0f, activator); if (IsToggle && (activator == null || lastUsed < Timing.TotalTime - 0.1)) { if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) @@ -421,8 +422,7 @@ namespace Barotrauma.Items.Components item.SendSignal(new Signal(output, sender: user), "trigger_out"); } - lastUsed = Timing.TotalTime; - ApplyStatusEffects(ActionType.OnUse, 1.0f, activator); + lastUsed = Timing.TotalTime; return true; } @@ -541,6 +541,7 @@ namespace Barotrauma.Items.Components #if CLIENT PlaySound(ActionType.OnUse, picker); #endif + ApplyStatusEffects(ActionType.OnUse, 1f, picker); return true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/OxygenGenerator.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/OxygenGenerator.cs index 811deb841..a0f210be2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/OxygenGenerator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/OxygenGenerator.cs @@ -84,7 +84,10 @@ namespace Barotrauma.Items.Components CurrFlow = 0.0f; } - private void GetVents() + /// + /// Finds all the linked vents and calculates how much oxygen should be distributed to each of them based on the hull volumes. + /// + public void GetVents() { totalHullVolume = 0.0f; ventList ??= new List<(Vent vent, float hullVolume)>(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs index 6cf8f28a1..dab4a647c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs @@ -2,6 +2,7 @@ using Barotrauma.Networking; using Microsoft.Xna.Framework; using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -35,7 +36,7 @@ namespace Barotrauma.Items.Components { get { - if (item.ConditionPercentage > 10.0f || !IsActive) { return 0.0f; } + if (item.ConditionPercentage > 10.0f || !IsActive || Disabled) { return 0.0f; } return (1.0f - item.ConditionPercentage / 10.0f) * 100.0f; } } @@ -61,6 +62,23 @@ namespace Barotrauma.Items.Components set => maxFlow = value; } + private bool disabled; + [Serialize(false, IsPropertySaveable.Yes, description: "If true, the pump is unable to pump water.", alwaysUseInstanceValues: true)] + public bool Disabled + { + get => disabled; + set + { + if (disabled == value) { return; } + disabled = value; +#if SERVER + //send a network update soon + //don't force to 0 though so this doesn't lead to spam if the property is toggled rapidly + networkUpdateTimer = Math.Min(networkUpdateTimer, 0.5f); +#endif + } + } + [Editable, Serialize(true, IsPropertySaveable.Yes, alwaysUseInstanceValues: true)] public bool IsOn { @@ -68,15 +86,13 @@ namespace Barotrauma.Items.Components set { IsActive = value; } } + [Serialize(false, IsPropertySaveable.No)] + public bool CanCauseLethalPressure { get; set; } + private float currFlow; - public float CurrFlow - { - get - { - if (!IsActive) { return 0.0f; } - return Math.Abs(currFlow); - } - } + public float CurrFlow => IsActive ? Math.Abs(currFlow) : 0.0f; + + public bool IsHullFull => item.CurrentHull != null && item.CurrentHull.WaterVolume >= item.CurrentHull.Volume * Hull.MaxCompress; public override bool HasPower => IsActive && Voltage >= MinVoltage; public bool IsAutoControlled => pumpSpeedLockTimer > 0.0f || isActiveLockTimer > 0.0f; @@ -85,7 +101,7 @@ namespace Barotrauma.Items.Components public override bool UpdateWhenInactive => true; - public float CurrentStress => Math.Abs(flowPercentage / 100.0f); + public float CurrentStress => IsActive ? Math.Abs(flowPercentage / 100.0f) : 0.0f; public Pump(Item item, ContentXElement element) : base(item, element) @@ -95,48 +111,42 @@ namespace Barotrauma.Items.Components partial void InitProjSpecific(ContentXElement element); + private readonly List linkedHulls = []; + public override void Update(float deltaTime, Camera cam) { pumpSpeedLockTimer -= deltaTime; isActiveLockTimer -= deltaTime; - if (!IsActive) + currFlow = 0f; + + if (item.CurrentHull == null) { + if (TargetLevel != null) { FlowPercentage = 0f; } return; } - currFlow = 0.0f; - if (TargetLevel != null) { - float hullPercentage = 0.0f; - if (item.CurrentHull != null) + float hullWaterVolume = item.CurrentHull.WaterVolume; + float totalHullVolume = item.CurrentHull.Volume; + + linkedHulls.Clear(); + //hidden hulls still affect buoyancy, include them here + item.CurrentHull.GetLinkedHulls(linkedHulls, includeHiddenHulls: true); + foreach (var linkedHull in linkedHulls) { - float hullWaterVolume = item.CurrentHull.WaterVolume; - float totalHullVolume = item.CurrentHull.Volume; - foreach (var linked in item.CurrentHull.linkedTo) - { - if ((linked is Hull linkedHull)) - { - hullWaterVolume += linkedHull.WaterVolume; - totalHullVolume += linkedHull.Volume; - } - } - hullPercentage = hullWaterVolume / totalHullVolume * 100.0f; + hullWaterVolume += linkedHull.WaterVolume; + totalHullVolume += linkedHull.Volume; } + float hullPercentage = hullWaterVolume / totalHullVolume * 100.0f; FlowPercentage = ((float)TargetLevel - hullPercentage) * 10.0f; } - if (!HasPower) - { - return; - } + UpdateNetworking(deltaTime); - UpdateProjSpecific(deltaTime); - - ApplyStatusEffects(ActionType.OnActive, deltaTime); - - if (item.CurrentHull == null) { return; } + if (!IsActive || Disabled) { return; } + if (flowPercentage <= 0f && item.CurrentHull.WaterVolume <= 0f) { return; } float powerFactor = Math.Min(currPowerConsumption <= 0.0f || MinVoltage <= 0.0f ? 1.0f : Voltage, MaxOverVoltageFactor); @@ -150,8 +160,22 @@ namespace Barotrauma.Items.Components //less effective when in a bad condition currFlow *= MathHelper.Lerp(0.5f, 1.0f, item.Condition / item.MaxCondition); - item.CurrentHull.WaterVolume += currFlow * deltaTime * Timing.FixedUpdateRate; - if (item.CurrentHull.WaterVolume > item.CurrentHull.Volume) { item.CurrentHull.Pressure += 30.0f * deltaTime; } + if (MathUtils.NearlyEqual(currFlow, 0f, epsilon: 0.01f)) + { + currFlow = 0f; // Set to 0 for conditionals. + return; + } + + item.CurrentHull.WaterVolume += currFlow * deltaTime * Timing.FixedUpdateRate; + + if (flowPercentage > 0f && item.CurrentHull.WaterVolume > item.CurrentHull.Volume) + { + item.CurrentHull.Pressure += 30f * deltaTime; + if (CanCauseLethalPressure) { item.CurrentHull.LethalPressure += Hull.PressureBuildUpSpeed * deltaTime; } + } + + ApplyStatusEffects(ActionType.OnActive, deltaTime); + UpdateProjSpecific(deltaTime); } public void InfectBallast(Identifier identifier, bool allowMultiplePerShip = false) @@ -188,7 +212,7 @@ namespace Barotrauma.Items.Components public override float GetCurrentPowerConsumption(Connection connection = null) { //There shouldn't be other power connections to this - if (connection != this.powerIn || !IsActive) + if (connection != this.powerIn || !IsActive || Disabled) { return 0; } @@ -202,6 +226,8 @@ namespace Barotrauma.Items.Components partial void UpdateProjSpecific(float deltaTime); + partial void UpdateNetworking(float deltaTime); + public override void ReceiveSignal(Signal signal, Connection connection) { if (Hijacked) { return; } @@ -276,5 +302,11 @@ namespace Barotrauma.Items.Components } return true; } + + protected override void RemoveComponentSpecific() + { + base.RemoveComponentSpecific(); + linkedHulls.Clear(); + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerDistributor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerDistributor.cs index fd4340201..6086b0b24 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerDistributor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerDistributor.cs @@ -82,6 +82,11 @@ namespace Barotrauma.Items.Components #if CLIENT CreateGUI(); + if (Screen.Selected is not { IsEditor: true }) + { + //set text via the property to refresh the UI + Name = name; + } #endif } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/TriggerComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/TriggerComponent.cs index 28728b077..b2ff80ee6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/TriggerComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/TriggerComponent.cs @@ -13,10 +13,24 @@ namespace Barotrauma.Items.Components { partial class TriggerComponent : ItemComponent { - [Editable, Serialize(0.0f, IsPropertySaveable.Yes, description: "The maximum amount of force applied to the triggering entitites.", alwaysUseInstanceValues: true)] + [Editable, Serialize(0f, IsPropertySaveable.Yes, description: "The maximum amount of force applied to the triggering entitites.", alwaysUseInstanceValues: true)] public float Force { get; set; } + + [Editable, Serialize("0,0", IsPropertySaveable.Yes, description: "The maximum amount of directional force applied to the triggering entitites.", alwaysUseInstanceValues: true)] + public Vector2 DirectionalForce { get; set; } + + [Editable, Serialize(false, IsPropertySaveable.Yes, $"If true, {nameof(DirectionalForce)} is relative to the angle between the target and the item, Similar to {nameof(Force)}.\nIf false, it always pushes in the same direction, with respect to the item's rotation.", alwaysUseInstanceValues: true)] + public bool RelativeDirectionalForce { get; set; } + + [Editable, Serialize(true, IsPropertySaveable.Yes, "If false, no vertical force will be applied.", alwaysUseInstanceValues: true)] + public bool VerticalForce { get; set; } + + [Editable, Serialize(true, IsPropertySaveable.Yes, "If false, no horizontal force will be applied.", alwaysUseInstanceValues: true)] + public bool HorizontalForce { get; set; } + [Editable, Serialize(false, IsPropertySaveable.Yes, description: "Determines if the force gets higher the closer the triggerer is to the center of the trigger.", alwaysUseInstanceValues: true)] public bool DistanceBasedForce { get; set; } + [Editable, Serialize(false, IsPropertySaveable.Yes, description: "Determines if the force fluctuates over time or if it stays constant.", alwaysUseInstanceValues: true)] public bool ForceFluctuation { get; set; } @@ -141,12 +155,29 @@ namespace Barotrauma.Items.Components get => base.IsActive; set { + bool wasActive = base.IsActive; + base.IsActive = value; if (!IsActive) { TriggerActive = false; triggerers.Clear(); } + else if (!wasActive && PhysicsBody?.FarseerBody != null) + { + //when the trigger becomes active, we need to check which entities are inside it + ContactEdge ce = PhysicsBody.FarseerBody.ContactList; + while (ce != null && ce.Contact != null) + { + if (ce.Contact.Enabled) + { + var thisFixture = ce.Contact.FixtureA.Body == PhysicsBody.FarseerBody ? ce.Contact.FixtureA : ce.Contact.FixtureB; + var otherFixture = ce.Contact.FixtureA.Body == PhysicsBody.FarseerBody ? ce.Contact.FixtureB : ce.Contact.FixtureA; + OnCollision(thisFixture, otherFixture, ce.Contact); + } + ce = ce.Next; + } + } } } @@ -374,7 +405,9 @@ namespace Barotrauma.Items.Components float amount = MathUtils.InverseLerp(-1.0f, 1.0f, v); CurrentForceFluctuation = MathHelper.Lerp(1.0f - ForceFluctuationStrength, 1.0f, amount); ForceFluctuationTimer = 0.0f; - GameMain.NetworkMember?.CreateEntityEvent(this); +#if SERVER + item.CreateServerEvent(this); +#endif } } @@ -398,7 +431,7 @@ namespace Barotrauma.Items.Components } } - if (Math.Abs(Force) < 0.01f) + if (Force < 0.01f && DirectionalForce.LengthSquared() < 0.0001f) { // Just ignore very minimal forces continue; @@ -436,7 +469,25 @@ namespace Barotrauma.Items.Components if (diff.LengthSquared() < 0.0001f) { return; } float distanceFactor = DistanceBasedForce ? LevelTrigger.GetDistanceFactor(body, PhysicsBody, RadiusInDisplayUnits) : 1.0f; if (distanceFactor <= 0.0f) { return; } - Vector2 force = distanceFactor * (CurrentForceFluctuation * Force) * Vector2.Normalize(diff) * multiplier; + Vector2 radialForce = Force * Vector2.Normalize(diff); + Vector2 directionalForce; + if (RelativeDirectionalForce) + { + directionalForce = DirectionalForce * new Vector2(Math.Sign(diff.X), Math.Sign(diff.Y)); + } + else + { + Vector2 flippedForce = DirectionalForce; + if (item.FlippedX) { flippedForce.X = -flippedForce.X; } + if (item.FlippedY) { flippedForce.Y = -flippedForce.Y; } + directionalForce = MathUtils.RotatePoint(flippedForce, -item.RotationRad); + } + + Vector2 force = (radialForce + directionalForce) * CurrentForceFluctuation * distanceFactor * multiplier; + + if (!HorizontalForce) { force.Y = 0.0f; } + if (!VerticalForce) { force.Y = 0.0f; } + if (force.LengthSquared() < 0.01f) { return; } if (body.Mass < 1) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index 7fe35e286..945590ae3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -461,20 +461,15 @@ namespace Barotrauma } } - public float ImpactTolerance - { - get { return Prefab.ImpactTolerance; } - } - - public float InteractDistance - { - get { return Prefab.InteractDistance; } - } + public float ImpactTolerance => Prefab.ImpactTolerance; - public float InteractPriority - { - get { return Prefab.InteractPriority; } - } + public float ImpactDamage => Prefab.ImpactDamage; + public float ImpactDamageProbability => Prefab.ImpactDamageProbability; + + public float InteractDistance => Prefab.InteractDistance; + + public float InteractPriority => Prefab.InteractPriority; + public override Vector2 Position { @@ -1767,7 +1762,7 @@ namespace Barotrauma ic.Move(amount, ignoreContacts); } - if (body != null && (Submarine == null || !Submarine.Loading)) { FindHull(); } + if (body != null && (Submarine == null || !Submarine.Loading) || Screen.Selected is { IsEditor: true }) { FindHull(); } } public Rectangle TransformTrigger(Rectangle trigger, bool world = false) @@ -2387,7 +2382,7 @@ namespace Barotrauma { while (impactQueue.TryDequeue(out float impact)) { - HandleCollision(impact); + ReceiveImpact(impact); } } if (isDroppedStackOwner && body != null) @@ -2461,7 +2456,7 @@ namespace Barotrauma if (ic.IsActive || ic.UpdateWhenInactive) { - if (condition <= 0.0f) + if (!ic.UpdateWhenBroken && condition <= 0.0f) { ic.UpdateBroken(deltaTime, cam); } @@ -2713,25 +2708,35 @@ namespace Barotrauma return true; } - private void HandleCollision(float impact) + public void ReceiveImpact(float impactStrength, bool recursive = true) { - OnCollisionProjSpecific(impact); + OnCollisionProjSpecific(impactStrength); if (GameMain.NetworkMember is { IsClient: true }) { return; } - if (ImpactTolerance > 0.0f && Math.Abs(impact) > ImpactTolerance && hasStatusEffectsOfType[(int)ActionType.OnImpact]) + if (ImpactTolerance > 0.0f && Math.Abs(impactStrength) > ImpactTolerance && Rand.Range(0.0f, 1.0f) < ImpactDamageProbability) { - foreach (StatusEffect effect in statusEffectLists[ActionType.OnImpact]) + if (ImpactDamage != 0.0f) { - ApplyStatusEffect(effect, ActionType.OnImpact, deltaTime: 1.0f); + Condition -= impactStrength * ImpactDamage; } + + if (hasStatusEffectsOfType[(int)ActionType.OnImpact]) + { + foreach (StatusEffect effect in statusEffectLists[ActionType.OnImpact]) + { + ApplyStatusEffect(effect, ActionType.OnImpact, deltaTime: 1.0f); + } #if SERVER - GameMain.Server?.CreateEntityEvent(this, new ApplyStatusEffectEventData(ActionType.OnImpact)); + GameMain.Server?.CreateEntityEvent(this, new ApplyStatusEffectEventData(ActionType.OnImpact)); #endif + } } + if (!recursive) { return; } + foreach (Item contained in ContainedItems) { - if (contained.body != null) { contained.HandleCollision(impact); } + if (contained.body != null) { contained.ReceiveImpact(impactStrength, recursive: true); } } } @@ -3698,18 +3703,15 @@ namespace Barotrauma SerializableProperty property = extraData.SerializableProperty; ISerializableEntity entity = extraData.Entity; - msg.WriteVariableUInt32((uint)allProperties.Count); - if (property != null) { if (allProperties.Count > 1) { - int propertyIndex = allProperties.FindIndex(p => p.property == property && p.obj == entity); - if (propertyIndex < 0) + if (allProperties.None(p => p.property == property && p.obj == entity)) { throw new Exception($"Could not find the property \"{property.Name}\" in \"{entity.Name ?? "null"}\""); } - msg.WriteVariableUInt32((uint)propertyIndex); + msg.WriteIdentifier(property.Name.ToIdentifier()); } object value = property.GetValue(entity); @@ -3814,21 +3816,11 @@ namespace Barotrauma var allProperties = inGameEditableOnly ? GetInGameEditableProperties(ignoreConditions: true) : GetProperties(); if (allProperties.Count == 0) { return; } - int propertyCount = (int)msg.ReadVariableUInt32(); - if (propertyCount != allProperties.Count) + Identifier propertyIdentifier = msg.ReadIdentifier(); + int propertyIndex = allProperties.IndexOf(p => p.property.Name == propertyIdentifier); + if (propertyIndex < 0) { - throw new Exception($"Error in {nameof(ReadPropertyChange)}. The number of properties on the item \"{Prefab.Identifier}\" does not match between the server and the client. Server: {propertyCount}, client: {allProperties.Count}."); - } - - int propertyIndex = 0; - if (allProperties.Count > 1) - { - propertyIndex = (int)msg.ReadVariableUInt32(); - } - - if (propertyIndex >= allProperties.Count || propertyIndex < 0) - { - throw new Exception($"Error in {nameof(ReadPropertyChange)}. Property index out of bounds (item: {Prefab.Identifier}, index: {propertyIndex}, property count: {allProperties.Count}, in-game editable only: {inGameEditableOnly})"); + throw new Exception($"Error in {nameof(ReadPropertyChange)}. Could not find the property \"{propertyIdentifier}\" in item \"{Prefab.Identifier}\" (property count: {allProperties.Count}, in-game editable only: {inGameEditableOnly})"); } bool allowEditing = true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs index 3ae662440..c8357fc58 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs @@ -821,6 +821,15 @@ namespace Barotrauma set { impactTolerance = Math.Max(value, 0.0f); } } + [Serialize(0.0f, IsPropertySaveable.No, description: "The amount of damage the item takes from impacts. Acts as a multiplier on the strength of the impact. Note that ImpactTolerance must be set for impacts to register.")] + public float ImpactDamage { get; set; } + + [Serialize(1.0f, IsPropertySaveable.No, description: "Probability for impacts to register. Defaults to 1. Note that ImpactTolerance must also be set for impacts to register.")] + public float ImpactDamageProbability { get; set; } + + [Serialize(false, IsPropertySaveable.No, "If true, submarine impacts will trigger OnImpact effects. Only applies to items with a null or non-dynamic physics body - items with dynamic bodies always react to impacts.")] + public bool ReceiveSubmarineImpacts { get; set; } + [Serialize(0.0f, IsPropertySaveable.No)] public float OnDamagedThreshold { get; set; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs index e3250d7cf..2bb9cb587 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs @@ -102,12 +102,12 @@ namespace Barotrauma } /// - /// Index of the slot the target must be in when targeting a Contained item + /// Index of the slot the target must be in when targeting a Contained item or a character inventory. /// public int TargetSlot = -1; /// - /// The slot type the target must be in when targeting an item contained inside a character's inventory + /// The slot type the target must be in when targeting an item contained inside a character's inventory. /// public InvSlotType CharacterInventorySlotType; @@ -329,7 +329,6 @@ namespace Barotrauma IgnoreInEditor = element.GetAttributeBool("ignoreineditor", false); MatchOnEmpty = element.GetAttributeBool("matchonempty", false); TargetSlot = element.GetAttributeInt("targetslot", -1); - } public bool CheckRequirements(Character character, Item parentItem) @@ -344,22 +343,21 @@ namespace Barotrauma return CheckItem(parentItem.Container, this); case RelationType.Equipped: if (character == null) { return false; } - var heldItems = character.HeldItems; - if (RequireOrMatchOnEmpty && heldItems.None()) { return true; } - foreach (Item equippedItem in heldItems) + foreach (var item in character.Inventory.AllItemsMod) { - if (equippedItem == null) { continue; } - if (CheckItem(equippedItem, this)) + if (character.HasEquippedItem(item) && CheckItem(item, this)) { - if (RequireEmpty && equippedItem.Condition > 0) { return false; } + if (RequireEmpty && item.Condition > 0) { return false; } return true; } } - break; + //got this far -> no matching item was equipped + //return true if we require or want to match "empty" (no matching item), otherwise false + return RequireOrMatchOnEmpty; case RelationType.Picked: if (character == null) { return false; } if (character.Inventory == null) { return MatchOnEmpty || RequireEmpty; } - var allItems = character.Inventory.AllItems; + var allItems = TargetSlot == -1 ? character.Inventory.AllItems : character.Inventory.GetItemsAt(TargetSlot); if (RequireOrMatchOnEmpty && allItems.None()) { return true; } foreach (Item pickedItem in allItems) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs index 179355407..ea8a21405 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs @@ -396,7 +396,10 @@ namespace Barotrauma } } - if (MathUtils.NearlyEqual(force, 0.0f) && MathUtils.NearlyEqual(Attack.Stun, 0.0f) && Attack.Afflictions.None()) + if (Attack.Afflictions.None() && + MathUtils.NearlyEqual(force, 0.0f) && MathUtils.NearlyEqual(Attack.Stun, 0.0f) && + MathUtils.NearlyEqual(Attack.ItemDamage, 0.0f) && + MathUtils.NearlyEqual(Attack.StructureDamage, 0.0f)) { return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs index b589458a1..07e187866 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs @@ -370,18 +370,31 @@ namespace Barotrauma public override void Update(float deltaTime, Camera cam) { + Hull hull1 = linkedTo.Count < 1 ? null : linkedTo[0] as Hull; + Hull hull2 = linkedTo.Count < 2 ? null : (Hull)linkedTo[1]; + int updateInterval = 4; - float flowMagnitude = flowForce.LengthSquared(); - if (flowMagnitude < 1.0f) + //if one hull is at lethal pressure (connected to outside), and the other not yet, + //we need frequent updates to quickly move water into the other hull + if (hull1 != null && hull2 != null && + hull1.LethalPressure > 0.0f != hull2.LethalPressure > 0.0f) { - //very sparse updates if there's practically no water moving - updateInterval = 8; - } - else if (linkedTo.Count == 2 && flowMagnitude > 10.0f) - { - //frequent updates if water is moving between hulls updateInterval = 1; } + else + { + float flowMagnitude = flowForce.LengthSquared(); + if (flowMagnitude < 1.0f) + { + //very sparse updates if there's practically no water moving + updateInterval = 8; + } + else if (linkedTo.Count == 2 && flowMagnitude > 10.0f) + { + //frequent updates if water is moving between hulls + updateInterval = 1; + } + } updateCount++; if (updateCount < updateInterval) { return; } @@ -409,8 +422,6 @@ namespace Barotrauma return; } - Hull hull1 = (Hull)linkedTo[0]; - Hull hull2 = linkedTo.Count < 2 ? null : (Hull)linkedTo[1]; if (hull1 == hull2) { return; } UpdateOxygen(hull1, hull2, deltaTime); @@ -469,6 +480,8 @@ namespace Barotrauma higherSurface = Math.Max(hull1.Surface, hull2.Surface + subOffset.Y); float delta = 0.0f; + Hull flowSourceHull = null; + //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) { @@ -479,10 +492,9 @@ namespace Barotrauma { if (!(hull2.WaterVolume > 0.0f)) { return; } lowerSurface = hull1.Surface - hull1.WaveY[hull1.WaveY.Length - 1]; - //delta = Math.Min((room2.water.pressure - room1.water.pressure) * sizeModifier, Math.Min(room2.water.Volume, room2.Volume)); - //delta = Math.Min(delta, room1.Volume - room1.water.Volume + Water.MaxCompress); flowTargetHull = hull1; + flowSourceHull = hull2; //make sure not to move more than what the room contains delta = Math.Min(((hull2.Pressure + subOffset.Y) - hull1.Pressure) * 300.0f * sizeModifier * deltaTime, Math.Min(hull2.WaterVolume, hull2.Volume)); @@ -504,6 +516,7 @@ namespace Barotrauma lowerSurface = hull2.Surface - hull2.WaveY[hull2.WaveY.Length - 1]; flowTargetHull = hull2; + flowSourceHull = hull1; //make sure not to move more than what the room contains delta = Math.Min((hull1.Pressure - (hull2.Pressure + subOffset.Y)) * 300.0f * sizeModifier * deltaTime, Math.Min(hull1.WaterVolume, hull1.Volume)); @@ -547,7 +560,6 @@ namespace Barotrauma if (hull2.Pressure + subOffset.Y > hull1.Pressure && hull2.WaterVolume > 0.0f) { float delta = Math.Min(hull2.WaterVolume - hull2.Volume + (hull2.Volume * Hull.MaxCompress), deltaTime * 8000.0f * sizeModifier); - //make sure not to place more water to the target room than it can hold if (hull1.WaterVolume + delta > hull1.Volume * Hull.MaxCompress) { @@ -623,19 +635,30 @@ namespace Barotrauma } } - void UpdateRoomToOut(float deltaTime, Hull hull1) + /// + /// How much water can flow through the gap to the hull if the gap is connected outside. + /// + private float GetWaterFlowFromOutside(Hull hull, float deltaTime, bool ignoreCurrentWater = false) { //a variable affecting the water flow through the gap //the larger the gap is, the faster the water flows float sizeModifier = Size * open * open * (1.0f - overlappingGapFlowRateReduction); - float delta = 500.0f * sizeModifier * deltaTime; + if (!ignoreCurrentWater) + { + delta = Math.Min(delta, hull.Volume * Hull.MaxCompress - hull.WaterVolume); + } + return delta; + } + + void UpdateRoomToOut(float deltaTime, Hull hull1) + { + float delta = GetWaterFlowFromOutside(hull1, deltaTime); //make sure not to place more water to the target room than it can hold - delta = Math.Min(delta, hull1.Volume * Hull.MaxCompress - hull1.WaterVolume); hull1.WaterVolume += delta; - if (hull1.WaterVolume > hull1.Volume) { hull1.Pressure += 30.0f * deltaTime; } + if (hull1.WaterVolume > hull1.Volume) { hull1.Pressure += 100.0f * deltaTime; } flowTargetHull = hull1; @@ -698,6 +721,65 @@ namespace Barotrauma hull1.LethalPressure += ((Submarine != null && Submarine.AtDamageDepth) ? 100.0f : Hull.PressureBuildUpSpeed) * PressureDistributionSpeed * deltaTime; } } + + if (hull1.LethalPressure > 0) + { + SimulateWaterFlowFromOutsideToConnectedHulls(hull1, maxFlow: GetWaterFlowFromOutside(hull1, deltaTime, ignoreCurrentWater: true), deltaTime: deltaTime); + } + } + + private Hull GetOtherLinkedHull(Hull hull1) + { + if (linkedTo.Count != 2 || hull1 == null) { return null; } + return (linkedTo[0] == hull1 ? linkedTo[1] : linkedTo[0]) as Hull; + } + + private static readonly HashSet checkedHulls = new HashSet(); + + /// + /// Simulates water flow from the source to all the hulls it's connected to across the sub, as if the water was coming directly from outside. + /// Used to prevent gaps from slowing down flooding when hulls are directly connected outside and highly pressurized. + /// + void SimulateWaterFlowFromOutsideToConnectedHulls(Hull hull, float maxFlow, float deltaTime) + { + checkedHulls.Clear(); + checkedHulls.Add(hull); + foreach (var connectedGap in hull.ConnectedGaps) + { + if (connectedGap == this || !connectedGap.IsRoomToRoom || connectedGap.open <= 0.0f) { continue; } + var otherHull = connectedGap.GetOtherLinkedHull(hull); + if (otherHull == null) { continue; } + SimulateWaterFlowFromOutsideToConnectedHullsRecursive(otherHull, connectedGap, checkedHulls, hull, maxFlow, deltaTime); + } + } + + static void SimulateWaterFlowFromOutsideToConnectedHullsRecursive(Hull targetHull, Gap gap, HashSet checkedHulls, Hull originHull, float maxFlow, float deltaTime) + { + const float decay = 0.95f; + + maxFlow = Math.Min(maxFlow, gap.GetWaterFlowFromOutside(targetHull, deltaTime, ignoreCurrentWater: true)) * decay; + if (maxFlow <= 0.001f) { return; } + + checkedHulls.Add(targetHull); + + //don't multiply by deltatime here, we already did that in GetWaterFlowFromOutside + targetHull.WaterVolume += maxFlow; + //lerp lethal pressure up very fast + if (targetHull.WaterVolume > targetHull.Volume) + { + targetHull.LethalPressure = Math.Max(targetHull.LethalPressure, MathHelper.Lerp(targetHull.LethalPressure, originHull.LethalPressure, 0.1f)); + } + + //stop pushing water to the following hulls once we get to a hull that's not at high pressure yet + if (targetHull.LethalPressure <= 0 || targetHull.WaterVolume < targetHull.Volume) { return; } + + foreach (var connectedGap in targetHull.ConnectedGaps) + { + if (connectedGap == gap || !connectedGap.IsRoomToRoom || connectedGap.open <= 0.0f) { continue; } + var otherHull = connectedGap.GetOtherLinkedHull(targetHull); + if (otherHull == null || checkedHulls.Contains(otherHull)) { continue; } + SimulateWaterFlowFromOutsideToConnectedHullsRecursive(otherHull, connectedGap, checkedHulls, originHull, maxFlow, deltaTime); + } } public bool RefreshOutsideCollider() @@ -884,6 +966,8 @@ namespace Barotrauma base.Remove(); GapList.Remove(this); + checkedHulls.Clear(); + foreach (Hull hull in Hull.HullList) { hull.ConnectedGaps.Remove(this); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs index dce43c267..2a79f9482 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs @@ -785,7 +785,7 @@ namespace Barotrauma #region Shared network write private void SharedStatusWrite(IWriteMessage msg) { - msg.WriteRangedSingle(MathHelper.Clamp(waterVolume / Volume, 0.0f, 1.5f), 0.0f, 1.5f, 8); + msg.WriteSingle(waterVolume); System.Diagnostics.Debug.Assert(FireSources.Count <= MaxFireSources, $"Too many fire sources ({FireSources.Count}) in hull {ID} (max {MaxFireSources})."); msg.WriteRangedInteger(Math.Min(FireSources.Count, MaxFireSources), 0, MaxFireSources); @@ -833,7 +833,7 @@ namespace Barotrauma private void SharedStatusRead(IReadMessage msg, out float newWaterVolume, out NetworkFireSource[] newFireSources) { - newWaterVolume = msg.ReadRangedSingle(0.0f, 1.5f, 8) * Volume; + newWaterVolume = msg.ReadSingle(); int fireSourceCount = msg.ReadRangedInteger(0, MaxFireSources); newFireSources = new NetworkFireSource[fireSourceCount]; @@ -1269,6 +1269,23 @@ namespace Barotrauma return null; } + /// + /// Recursively find all the hulls linked to the specified hull. + /// + public void GetLinkedHulls(List linkedHulls, bool includeHiddenHulls = false) + { + foreach (var linkedEntity in linkedTo) + { + if (linkedEntity is Hull linkedHull) + { + if (linkedHulls.Contains(linkedHull)) { continue; } + if (!includeHiddenHulls && linkedHull.IsHidden) { continue; } + linkedHulls.Add(linkedHull); + linkedHull.GetLinkedHulls(linkedHulls, includeHiddenHulls); + } + } + } + public static void DetectItemVisibility(Character c=null) { if (c==null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs index 45f5c4244..c5f0d161f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs @@ -4291,6 +4291,11 @@ namespace Barotrauma { placeableWrecks.RemoveAt(i); } + // Exclude wrecks that have mission tags, those can't show up randomly + else if (wreckInfo.MissionTags.Count != 0) + { + placeableWrecks.RemoveAt(i); + } } if (placeableWrecks.None()) { @@ -4742,6 +4747,11 @@ namespace Barotrauma { beaconStationFiles.RemoveAt(i); } + // Exclude beacons that have mission tags, those can't show up randomly + else if (beaconInfo.MissionTags.Count != 0) + { + beaconStationFiles.RemoveAt(i); + } } } if (beaconStationFiles.None()) @@ -4926,17 +4936,17 @@ namespace Barotrauma { int corpseCount = Rand.Range(Loaded.GenerationParams.MinCorpseCount, Loaded.GenerationParams.MaxCorpseCount + 1); var allSpawnPoints = WayPoint.WayPointList.FindAll(wp => wp.Submarine == wreck && wp.CurrentHull != null); - var pathPoints = allSpawnPoints.FindAll(wp => wp.SpawnType == SpawnType.Path); + var humanSpawnPoints = allSpawnPoints.FindAll(wp => wp.SpawnType == SpawnType.Human); var corpsePoints = allSpawnPoints.FindAll(wp => wp.SpawnType == SpawnType.Corpse); - if (!corpsePoints.Any() && !pathPoints.Any()) { continue; } - pathPoints.Shuffle(Rand.RandSync.ServerAndClient); + if (corpsePoints.None() && humanSpawnPoints.None()) { continue; } + humanSpawnPoints.Shuffle(Rand.RandSync.ServerAndClient); // Sort by job so that we first spawn those with a predefined job (might have special id cards) corpsePoints = corpsePoints.OrderBy(p => p.AssignedJob == null).ThenBy(p => Rand.Value()).ToList(); var usedJobs = new HashSet(); int spawnCounter = 0; for (int j = 0; j < corpseCount; j++) { - WayPoint sp = corpsePoints.FirstOrDefault() ?? pathPoints.FirstOrDefault(); + WayPoint sp = corpsePoints.FirstOrDefault() ?? humanSpawnPoints.FirstOrDefault(); JobPrefab job = sp?.AssignedJob; CorpsePrefab selectedPrefab; if (job == null) @@ -4949,8 +4959,8 @@ namespace Barotrauma if (selectedPrefab == null) { corpsePoints.Remove(sp); - pathPoints.Remove(sp); - sp = corpsePoints.FirstOrDefault(sp => sp.AssignedJob == null) ?? pathPoints.FirstOrDefault(sp => sp.AssignedJob == null); + humanSpawnPoints.Remove(sp); + sp = corpsePoints.FirstOrDefault(sp => sp.AssignedJob == null) ?? humanSpawnPoints.FirstOrDefault(sp => sp.AssignedJob == null); // Deduce the job from the selected prefab selectedPrefab = GetCorpsePrefab(usedJobs); if (selectedPrefab != null) @@ -4972,7 +4982,7 @@ namespace Barotrauma { worldPos = sp.WorldPosition; corpsePoints.Remove(sp); - pathPoints.Remove(sp); + humanSpawnPoints.Remove(sp); } job ??= selectedPrefab.GetJobPrefab(predicate: p => !usedJobs.Contains(p)); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs index 95d602dce..c55e16485 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs @@ -473,7 +473,7 @@ namespace Barotrauma { get { - availableMissions.RemoveAll(m => m.Completed || (m.Failed && !m.Prefab.AllowRetry)); + availableMissions.RemoveAll(m => m.Completed || (m.Failed && !m.Prefab.AllowRetry) || m.ForceFailure); return availableMissions; } } @@ -1091,6 +1091,12 @@ namespace Barotrauma mission.TimesAttempted = loadedMission.TimesAttempted; availableMissions.Add(mission); if (loadedMission.SelectedMission) { selectedMissions.Add(mission); } + + var levelData = destination == this ? LevelData : Connections.FirstOrDefault(c => c.OtherLocation(this) == destination)?.LevelData; + if (levelData != null) + { + mission.AdjustLevelData(levelData); + } } loadedMissions = null; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationConnection.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationConnection.cs index 964aaceb3..6506f3d1d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationConnection.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationConnection.cs @@ -39,7 +39,7 @@ namespace Barotrauma { get { - availableMissions.RemoveAll(m => m.Completed || (m.Failed && !m.Prefab.AllowRetry)); + availableMissions.RemoveAll(m => m.Completed || (m.Failed && !m.Prefab.AllowRetry) || m.ForceFailure); return availableMissions; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs index dbfb5ece0..785f729c6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs @@ -235,15 +235,17 @@ namespace Barotrauma //backwards compatibility (or support for loading maps created with mods that modify the end biome setup): //if there's too few end locations, create more - int missingOutpostCount = endLocations.First().Biome.EndBiomeLocationCount - endLocations.Count; - Location firstEndLocation = EndLocations[0]; + Biome endBiome = firstEndLocation.Biome; + int missingOutpostCount = endBiome.EndBiomeLocationCount - endLocations.Count; + for (int i = 0; i < missingOutpostCount; i++) { Vector2 mapPos = new Vector2( MathHelper.Lerp(firstEndLocation.MapPosition.X, Width, MathHelper.Lerp(0.2f, 0.8f, i / (float)missingOutpostCount)), Height * MathHelper.Lerp(0.2f, 1.0f, (float)rand.NextDouble())); - var newEndLocation = new Location(mapPos, generationParams.DifficultyZones, firstEndLocation.Biome.Identifier, rand, forceLocationType: firstEndLocation.Type, existingLocations: Locations); + var newEndLocation = new Location(mapPos, generationParams.DifficultyZones, endBiome.Identifier, rand, forceLocationType: firstEndLocation.Type, existingLocations: Locations); + newEndLocation.Biome = endBiome; newEndLocation.LevelData = new LevelData(newEndLocation, this, difficulty: 100.0f); Locations.Add(newEndLocation); endLocations.Add(newEndLocation); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/ExtraSubmarineInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/ExtraSubmarineInfo.cs index 2f46623fe..533b283e0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/ExtraSubmarineInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/ExtraSubmarineInfo.cs @@ -11,6 +11,8 @@ namespace Barotrauma public Dictionary SerializableProperties { get; protected set; } + public HashSet MissionTags { get; } = []; + [Serialize(0.0f, IsPropertySaveable.Yes), Editable] public float MinLevelDifficulty { get; set; } @@ -21,6 +23,10 @@ namespace Barotrauma { Name = $"{nameof(ExtraSubmarineInfo)} ({submarineInfo.Name})"; SerializableProperties = SerializableProperty.DeserializeProperties(this, element); + foreach (var missionTag in element.GetAttributeIdentifierArray(nameof(MissionTags), [])) + { + MissionTags.Add(missionTag); + } } public ExtraSubmarineInfo(SubmarineInfo submarineInfo) @@ -41,11 +47,18 @@ namespace Barotrauma kvp.Value.TrySetValue(this, kvp.Value.GetValue(original)); } } + foreach (var missionTag in original.MissionTags) + { + MissionTags.Add(missionTag); + } } public virtual void Save(XElement element) { SerializableProperty.SerializeProperties(this, element); + // MissionTags is not automatically serialized because HashSet is not a supported type + // We need to manually serialize it as a comma-separated string + element.SetAttributeValue(nameof(MissionTags), string.Join(',', MissionTags)); } } @@ -135,18 +148,10 @@ namespace Barotrauma [Serialize(50.0f, IsPropertySaveable.Yes), Editable] public float PreferredDifficulty { get; set; } - private readonly HashSet missionTags = new HashSet(); - - public HashSet MissionTags => missionTags; - public EnemySubmarineInfo(SubmarineInfo submarineInfo, XElement element) : base(submarineInfo, element) { Name = $"{nameof(EnemySubmarineInfo)} ({submarineInfo.Name})"; SerializableProperties = SerializableProperty.DeserializeProperties(this, element); - foreach (var missionTag in element.GetAttributeIdentifierArray(nameof(MissionTags), Array.Empty())) - { - missionTags.Add(missionTag); - } } public EnemySubmarineInfo(SubmarineInfo submarineInfo) : base(submarineInfo) @@ -156,16 +161,8 @@ namespace Barotrauma public EnemySubmarineInfo(EnemySubmarineInfo original) : base(original) { - foreach (var missionTag in original.missionTags) - { - missionTags.Add(missionTag); - } } - public override void Save(XElement element) - { - base.Save(element); - element.Add(new XAttribute(nameof(MissionTags), string.Join(',', missionTags))); - } + } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs index dcb60b4b1..8b840c161 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs @@ -159,11 +159,11 @@ namespace Barotrauma if (GameMain.NetworkMember?.ServerSettings is { } serverSettings && serverSettings.SelectedOutpostName != "Random") { + var matchingOutpost = outpostInfos.FirstOrDefault(o => o.Name == serverSettings.SelectedOutpostName); //...but only if the outpost is suitable for the mission (or if the mission has no specific requirements for the outpost) - if (outpostInfosSuitableForMission.None() || - outpostInfosSuitableForMission.Any(outpostInfo => outpostInfo.OutpostTags.Contains(serverSettings.SelectedOutpostName))) + if (outpostInfosSuitableForMission.Contains(matchingOutpost) || + outpostInfosSuitableForMission.None()) { - var matchingOutpost = outpostInfos.FirstOrDefault(o => o.Name == serverSettings.SelectedOutpostName); if (matchingOutpost != null) { return matchingOutpost; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/PriceInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/PriceInfo.cs index ae4124505..529263889 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/PriceInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/PriceInfo.cs @@ -37,14 +37,14 @@ namespace Barotrauma public bool RequiresUnlock { get; } /// - /// Used when neither or are defined. + /// Default minimum amount when no MinAvailableAmount is defined. /// - private const int DefaultAmount = 5; + private const int DefaultMinAmount = 1; /// - /// How much more the maximum stock is relative to the minimum stock if not defined. Stores will gradually stock up towards the maximum. + /// Default maximum amount when no MaxAvailableAmount is defined. /// - private const float DefaultMaxAvailabilityRelativeToMin = 1.2f; + private const int DefaultMaxAmount = 5; /// /// If set, the item is only available in outposts with this faction. @@ -66,11 +66,12 @@ namespace Barotrauma public PriceInfo(XElement element) { Price = element.GetAttributeInt("buyprice", 0); - MinLevelDifficulty = element.GetAttributeInt("minleveldifficulty", 0); + MinLevelDifficulty = GetMinLevelDifficulty(element, 0); BuyingPriceMultiplier = element.GetAttributeFloat("buyingpricemultiplier", 1f); CanBeBought = true; - MinAvailableAmount = Math.Min(GetMinAmount(element, defaultValue: DefaultAmount), CargoManager.MaxQuantity); - MaxAvailableAmount = MathHelper.Clamp(GetMaxAmount(element, defaultValue: (int)(MinAvailableAmount * DefaultMaxAvailabilityRelativeToMin)), MinAvailableAmount, CargoManager.MaxQuantity); + MinAvailableAmount = Math.Min(GetMinAmount(element, defaultValue: DefaultMinAmount), CargoManager.MaxQuantity); + int maxAmount = GetMaxAmount(element, defaultValue: DefaultMaxAmount); + MaxAvailableAmount = MathHelper.Clamp(maxAmount, MinAvailableAmount, CargoManager.MaxQuantity); RequiresUnlock = element.GetAttributeBool("requiresunlock", false); RequiredFaction = element.GetAttributeIdentifier(nameof(RequiredFaction), Identifier.Empty); System.Diagnostics.Debug.Assert(MaxAvailableAmount >= MinAvailableAmount); @@ -112,27 +113,27 @@ namespace Barotrauma var priceInfos = new List(); defaultPrice = null; int basePrice = element.GetAttributeInt("baseprice", 0); - int minAmount = GetMinAmount(element, defaultValue: DefaultAmount); - int maxAmount = GetMaxAmount(element, defaultValue: (int)(DefaultAmount * DefaultMaxAvailabilityRelativeToMin)); - int minLevelDifficulty = element.GetAttributeInt("minleveldifficulty", 0); + int minAmount = GetMinAmount(element, defaultValue: DefaultMinAmount); + int maxAmount = GetMaxAmount(element, defaultValue: DefaultMaxAmount); + int minLevelDifficulty = GetMinLevelDifficulty(element, 0); bool canBeSpecial = element.GetAttributeBool("canbespecial", true); float buyingPriceMultiplier = element.GetAttributeFloat("buyingpricemultiplier", 1f); bool displayNonEmpty = element.GetAttributeBool("displaynonempty", false); - bool soldByDefault = element.GetAttributeBool("sold", element.GetAttributeBool("soldbydefault", true)); + bool soldByDefault = GetSold(element, element.GetAttributeBool("soldbydefault", true)); bool requiresUnlock = element.GetAttributeBool("requiresunlock", false); Identifier requiredFactionByDefault = element.GetAttributeIdentifier(nameof(RequiredFaction), Identifier.Empty); foreach (XElement childElement in element.GetChildElements("price")) { float priceMultiplier = childElement.GetAttributeFloat("multiplier", 1.0f); - bool sold = childElement.GetAttributeBool("sold", soldByDefault); - int storeMinLevelDifficulty = childElement.GetAttributeInt("minleveldifficulty", minLevelDifficulty); + bool sold = GetSold(childElement, soldByDefault); + int storeMinLevelDifficulty = GetMinLevelDifficulty(childElement, minLevelDifficulty); float storeBuyingMultiplier = childElement.GetAttributeFloat("buyingpricemultiplier", buyingPriceMultiplier); string backwardsCompatibleIdentifier = childElement.GetAttributeString("locationtype", ""); if (!string.IsNullOrEmpty(backwardsCompatibleIdentifier)) { backwardsCompatibleIdentifier = $"merchant{backwardsCompatibleIdentifier}"; } - string storeIdentifier = childElement.GetAttributeString("storeidentifier", backwardsCompatibleIdentifier); + string storeIdentifier = GetStoreIdentifier(childElement, backwardsCompatibleIdentifier); // TODO: Add some error messages if we have defined the min or max amount while the item is not sold var priceInfo = new PriceInfo(price: (int)(priceMultiplier * basePrice), canBeBought: sold, @@ -167,12 +168,40 @@ namespace Barotrauma return priceInfos; } - private static int GetMinAmount(XElement element, int defaultValue) => element != null ? - element.GetAttributeInt("minamount", element.GetAttributeInt("minavailable", defaultValue)) : - defaultValue; + private static int GetMinAmount(XElement element, int defaultValue) => + element?.GetAttributeInt("minamount", element.GetAttributeInt("minavailable", defaultValue)) ?? defaultValue; - private static int GetMaxAmount(XElement element, int defaultValue) => element != null ? - element.GetAttributeInt("maxamount", element.GetAttributeInt("maxavailable", defaultValue)) : - defaultValue; + private static int GetMaxAmount(XElement element, int defaultValue) => + element?.GetAttributeInt("maxamount", element.GetAttributeInt("maxavailable", defaultValue)) ?? defaultValue; + + public static bool HasMinAmountDefined(XElement element) => element != null && + (element.GetAttribute("minamount") != null || element.GetAttribute("minavailable") != null); + + public static bool HasMaxAmountDefined(XElement element) => element != null && + (element.GetAttribute("maxamount") != null || element.GetAttribute("maxavailable") != null); + + public static bool HasSoldDefined(XElement element) => element != null && + element.GetAttribute("sold") != null; + + public static string GetMinAmountString(XElement element) + { + if (element == null) { return null; } + return element.GetAttributeString("minamount", null) ?? element.GetAttributeString("minavailable", null); + } + + public static string GetMaxAmountString(XElement element) + { + if (element == null) { return null; } + return element.GetAttributeString("maxamount", null) ?? element.GetAttributeString("maxavailable", null); + } + + public static bool GetSold(XElement element, bool defaultValue = true) => + element?.GetAttributeBool("sold", defaultValue) ?? defaultValue; + + public static int GetMinLevelDifficulty(XElement element, int defaultValue = 0) => + element?.GetAttributeInt("minleveldifficulty", defaultValue) ?? defaultValue; + + public static string GetStoreIdentifier(XElement element, string defaultValue = "unknown") => + element?.GetAttributeString("storeidentifier", defaultValue) ?? defaultValue; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs index e3816f58c..45df0b97f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs @@ -343,6 +343,9 @@ namespace Barotrauma } + /// + /// Is the sub at the depth where it starts to take damage to appear due to the pressure? + /// public bool AtDamageDepth { get @@ -352,6 +355,18 @@ namespace Barotrauma } } + /// + /// Is the sub at the depth where cosmetic effects (e.g. camera shake) start to appear due to the pressure? + /// + public bool AtCosmeticDamageDepth + { + get + { + if (Level.Loaded == null || subBody == null) { return false; } + return RealWorldDepth > Level.Loaded.RealWorldCrushDepth + SubmarineBody.CosmeticDamageEffectThreshold && RealWorldDepth > RealWorldCrushDepth + SubmarineBody.CosmeticDamageEffectThreshold; + } + } + public bool IsRespawnShuttle => GameMain.NetworkMember?.RespawnManager is { } respawnManager && respawnManager.RespawnShuttles.Contains(this); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs index e1be7173a..28b8af8c5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs @@ -579,13 +579,16 @@ namespace Barotrauma Body.SetTransform(ConvertUnits.ToSimUnits(position), 0.0f); } + /// + /// Camera shake and sounds start playing 500 meters before crush depth + /// + public const float CosmeticDamageEffectThreshold = -500.0f; + private void UpdateDepthDamage(float deltaTime) { if (GameMain.GameSession?.GameMode is TestGameMode) { return; } if (Level.Loaded == null) { return; } - //camera shake and sounds start playing 500 meters before crush depth - const float CosmeticEffectThreshold = -500.0f; //breaches won't get any more severe 500 meters below crush depth const float MaxEffectThreshold = 500.0f; const float MinWallDamageProbability = 0.1f; @@ -598,7 +601,7 @@ namespace Barotrauma //(gives you a bit of time to react and return if you start the round in a level that's too deep) const float MinRoundDuration = 60.0f; - if (Submarine.RealWorldDepth < Level.Loaded.RealWorldCrushDepth + CosmeticEffectThreshold || Submarine.RealWorldDepth < Submarine.RealWorldCrushDepth + CosmeticEffectThreshold) + if (!Submarine.AtCosmeticDamageDepth) { return; } @@ -606,9 +609,9 @@ namespace Barotrauma damageSoundTimer -= deltaTime; if (damageSoundTimer <= 0.0f) { - const float PressureSoundRange = -CosmeticEffectThreshold; + const float PressureSoundRange = -CosmeticDamageEffectThreshold; //Ratio between 0 (where the 'approaching crush depth' indication starts) and 1 (at crush depth or past it) - float closenessToCrushDepthRatio = Math.Clamp((Submarine.RealWorldDepth - (Submarine.RealWorldCrushDepth + CosmeticEffectThreshold)) / PressureSoundRange, 0f, 1f); + float closenessToCrushDepthRatio = Math.Clamp((Submarine.RealWorldDepth - (Submarine.RealWorldCrushDepth + CosmeticDamageEffectThreshold)) / PressureSoundRange, 0f, 1f); #if CLIENT SoundPlayer.PlayDamageSound("pressure", MathHelper.Lerp(0f, 100f, closenessToCrushDepthRatio), submarine.WorldPosition + Rand.Vector(Rand.Range(0.0f, Math.Min(submarine.Borders.Width, submarine.Borders.Height))), 20000.0f, gain: 1f + closenessToCrushDepthRatio * 2); #endif @@ -1066,8 +1069,15 @@ namespace Barotrauma foreach (Item item in Item.ItemList) { - if (item.Submarine != submarine || item.CurrentHull == null || item.body == null || !item.body.Enabled) { continue; } - if (item.body.Mass > impulseMagnitude) { continue; } + if (item.Submarine != submarine) { continue; } + + if (item.body is not { BodyType: BodyType.Dynamic }) + { + if (!item.Prefab.ReceiveSubmarineImpacts) { continue; } + item.ReceiveImpact(impact, recursive: false); + } + + if (!item.body.Enabled || item.CurrentHull == null || item.body.Mass > impulseMagnitude) { continue; } item.body.ApplyLinearImpulse(impulse, 10.0f); item.PositionUpdateInterval = 0.0f; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/INetSerializableStruct.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/INetSerializableStruct.cs index 9d2fbc195..69d121f72 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/INetSerializableStruct.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/INetSerializableStruct.cs @@ -820,16 +820,22 @@ namespace Barotrauma { int prevPos = inc.BitPosition; + const int MaxBytesToLog = 500; + StringBuilder hexData = new(); inc.BitPosition = 0; - while (inc.BitPosition < inc.LengthBits) + while (inc.BitPosition < inc.LengthBits && + inc.BytePosition < MaxBytesToLog) { byte b = inc.ReadByte(); hexData.Append($"{b:X2} "); } // trim the last space if there is one if (hexData.Length > 0) { hexData.Length--; } - + if (inc.BytePosition >= MaxBytesToLog) + { + hexData.Append($" (data truncated, {inc.LengthBytes} bytes in the full message)"); + } inc.BitPosition = prevPos; //only log the error once per sender, so this can't be abused by spamming the server with malformed data to fill up the console with errors diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/NetworkExtensions.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/NetworkExtensions.cs index da74d80f2..4475b9b89 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/NetworkExtensions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/NetworkExtensions.cs @@ -19,7 +19,7 @@ namespace Barotrauma.Networking return msg; } #endif - public static void WriteNetSerializableStruct(this IWriteMessage msg, INetSerializableStruct serializableStruct) + public static void WriteNetSerializableStruct(this IWriteMessage msg, T serializableStruct) where T : INetSerializableStruct { serializableStruct.Write(msg); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/NetworkPeerStructs.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/NetworkPeerStructs.cs index 99cbc056e..b484d0f78 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/NetworkPeerStructs.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/NetworkPeerStructs.cs @@ -94,6 +94,12 @@ namespace Barotrauma.Networking public Option RetriesLeft; } + [NetworkSerialize] + internal readonly record struct DoSProtectionPacket(string EndpointStr, bool ShouldBan) : INetSerializableStruct + { + public Option Endpoint => P2PEndpoint.Parse(EndpointStr); + } + [NetworkSerialize] internal readonly struct PeerDisconnectPacket : INetSerializableStruct { @@ -109,19 +115,24 @@ namespace Barotrauma.Networking AdditionalInformation = additionalInformation; } - public LocalizedString ChatMessage(Client c) + public LocalizedString ChatMessage(string? name) { + if (string.IsNullOrEmpty(name)) + { + name = TextManager.Get("ServerMessage.UnknownClient").Value; + } + LocalizedString message = DisconnectReason switch { DisconnectReason.Disconnected => TextManager.GetWithVariable("ServerMessage.ClientLeftServer", - "[client]", c.Name), - DisconnectReason.Banned => TextManager.GetWithVariable("servermessage.bannedfromserver", "[client]", c.Name), - DisconnectReason.Kicked => TextManager.GetWithVariable("servermessage.kickedfromserver", "[client]", c.Name), + "[client]", name), + DisconnectReason.Banned => TextManager.GetWithVariable("servermessage.bannedfromserver", "[client]", name), + DisconnectReason.Kicked => TextManager.GetWithVariable("servermessage.kickedfromserver", "[client]", name), _ => TextManager.GetWithVariables("ChatMsg.DisconnectedWithReason", - ("[client]", c.Name), + ("[client]", name), ("[reason]", TextManager.Get($"ChatMsg.DisconnectReason.{DisconnectReason}"))) }; - if (!string.IsNullOrEmpty(AdditionalInformation) && + if (!string.IsNullOrEmpty(AdditionalInformation) && DisconnectReason is DisconnectReason.Banned or DisconnectReason.Kicked) { message += " "+ TextManager.Get("banreason") + " " + TextManager.GetServerMessage(AdditionalInformation); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerLog.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerLog.cs index b84244ee1..3f0795cc8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerLog.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerLog.cs @@ -89,8 +89,6 @@ namespace Barotrauma.Networking private readonly Queue lines; private readonly Queue unsavedLines; - private readonly bool[] msgTypeHidden = new bool[Enum.GetValues(typeof(MessageType)).Length]; - public int LinesPerFile { get { return linesPerFile; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Prefabs/IImplementsVariants.cs b/Barotrauma/BarotraumaShared/SharedSource/Prefabs/IImplementsVariants.cs index 1c70a649d..33b259cc8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Prefabs/IImplementsVariants.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Prefabs/IImplementsVariants.cs @@ -79,14 +79,13 @@ namespace Barotrauma bool cleared = false; foreach (var subElement in element.Elements()) { - if (elementNamesToRemove.Contains(subElement.NameAsIdentifier())) - { - if (!elementsToRemove.Contains(subElement)) { elementsToRemove.Add(subElement); } - matchingElementFound = true; - continue; - } if (replacementSubElement.Name.ToString().Equals("clearall", StringComparison.OrdinalIgnoreCase)) { + if (elementNamesToRemove.Contains(subElement.NameAsIdentifier())) + { + if (!elementsToRemove.Contains(subElement)) { elementsToRemove.Add(subElement); } + matchingElementFound = true; + } continue; } else if (replacementSubElement.Name.ToString().Equals("clear", StringComparison.OrdinalIgnoreCase)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty/SerializableProperty.cs b/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty/SerializableProperty.cs index ab31f4582..440d9bdf7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty/SerializableProperty.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty/SerializableProperty.cs @@ -876,7 +876,18 @@ namespace Barotrauma Dictionary dictionary = new Dictionary(); foreach (var property in properties) { - var serializableProperty = new SerializableProperty(property); + //if the getter is private, we must get it from the declaring type to access it and check if it exists + SerializableProperty serializableProperty = null; + try + { + serializableProperty = new SerializableProperty(property); + } + catch (AmbiguousMatchException) + { + //can happen e.g. with AnimController.CurrentGroundedParams, which is of an abstract type - + //let's just ignore these types of properties (you can't really do anything with SerializableProperties that are reference types anyway) + continue; + } dictionary.Add(serializableProperty.Name.ToIdentifier(), serializableProperty); } @@ -1066,6 +1077,15 @@ namespace Barotrauma } } } + else if (attributeName == "unlockrecipe" || attributeName == "unlockrecipes") + { + var recipes = subElement.GetAttributeIdentifierImmutableHashSet("unlockrecipes", + def: subElement.GetAttributeIdentifierImmutableHashSet("unlockrecipe", ImmutableHashSet.Empty)); + foreach (var recipe in recipes) + { + GameMain.GameSession?.UnlockRecipe(CharacterTeamType.Team1, recipe, showNotifications: false); + } + } if (entity.SerializableProperties.TryGetValue(attributeName, out SerializableProperty property)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs index 1f5e2d51b..eb44f9d6b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs @@ -696,7 +696,8 @@ namespace Barotrauma root.Add(CampaignSettings.CurrentSettings.Save()); #endif - configDoc.SaveSafe(PlayerConfigPath); + //allow retrying a few times because the file may be in use if the player is running multiple instances of the game on the same machine + configDoc.SaveSafe(PlayerConfigPath, maxRetries: 4); System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings { diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs index ba05591dd..87d64919a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs @@ -425,6 +425,11 @@ namespace Barotrauma { return PropertyMatchesRequirement(targetChar, characterProperty); } + else if (targetChar?.AnimController?.SerializableProperties is { } animControllerProperties + && animControllerProperties.TryGetValue(AttributeName, out var animControllerProperty)) + { + return PropertyMatchesRequirement(targetChar.AnimController, animControllerProperty); + } return ComparisonOperatorIsNotEquals; case ConditionType.SkillRequirement: if (targetChar != null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index 6777e0a04..3ca3b308d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -420,7 +420,7 @@ namespace Barotrauma [Serialize(1, IsPropertySaveable.No, description: "How many characters to spawn.")] public int Count { get; private set; } - [Serialize(false, IsPropertySaveable.No, description: + [Serialize(false, IsPropertySaveable.No, description: "Should the buffs of the character executing the effect be transferred to the spawned character?"+ " Useful for effects that \"transform\" a character to something else by deleting the character and spawning a new one on its place.")] public bool TransferBuffs { get; private set; } @@ -445,11 +445,11 @@ namespace Barotrauma [Serialize("", IsPropertySaveable.No, description: "An affliction to apply on the spawned character.")] public Identifier AfflictionOnSpawn { get; private set; } - [Serialize(1, IsPropertySaveable.No, description: + [Serialize(1, IsPropertySaveable.No, description: $"The strength of the affliction applied on the spawned character. Only relevant if {nameof(AfflictionOnSpawn)} is defined.")] public int AfflictionStrength { get; private set; } - [Serialize(false, IsPropertySaveable.No, description: + [Serialize(false, IsPropertySaveable.No, description: "Should the player controlling the character that executes the effect gain control of the spawned character?" + " Useful for effects that \"transform\" a character to something else by deleting the character and spawning a new one on its place.")] public bool TransferControl { get; private set; } @@ -470,7 +470,7 @@ namespace Barotrauma [Serialize(false, IsPropertySaveable.No)] public bool InheritEventTags { get; private set; } - + [Serialize(false, IsPropertySaveable.No, description: "Should the character team be inherited from the entity that owns the status effect?")] public bool InheritTeam { get; private set; } @@ -722,9 +722,9 @@ namespace Barotrauma private readonly List<(Identifier eventIdentifier, Identifier tag)> eventTargetTags; /// - /// Can be used to make the effect unlock a fabrication recipe globally for the entire crew. + /// Can be used to make the effect unlock a fabrication recipe (or multiple recipes separated by a comma) globally for the entire crew. /// - public readonly Identifier UnlockRecipe; + public readonly ImmutableHashSet UnlockRecipes; private Character user; @@ -740,6 +740,8 @@ namespace Barotrauma /// public readonly float SeverLimbsProbability; + private readonly Vector2 randomCondition; + public PhysicsBody sourceBody; /// @@ -802,11 +804,11 @@ namespace Barotrauma private readonly List talentTriggers; private readonly List giveExperiences; private readonly List giveSkills; - + private HashSet<(Character targetCharacter, AnimLoadInfo anim)> failedAnimations; public readonly record struct AnimLoadInfo(AnimationType Type, Either File, float Priority, ImmutableArray ExpectedSpeciesNames); private readonly List animationsToTrigger; - + /// /// How long the effect runs (in seconds). Note that if is true, /// there can be multiple instances of the effect running at a time. @@ -903,9 +905,10 @@ namespace Barotrauma } SeverLimbsProbability = MathHelper.Clamp(element.GetAttributeFloat(0.0f, "severlimbs", "severlimbsprobability"), 0.0f, 1.0f); + randomCondition = element.GetAttributeVector2("randomcondition", Vector2.Zero); - string[] targetTypesStr = - element.GetAttributeStringArray("target", null) ?? + string[] targetTypesStr = + element.GetAttributeStringArray("target", null) ?? element.GetAttributeStringArray("targettype", Array.Empty()); foreach (string s in targetTypesStr) { @@ -939,7 +942,10 @@ namespace Barotrauma playSoundOnRequiredItemFailure = element.GetAttributeBool("playsoundonrequireditemfailure", false); #endif - UnlockRecipe = element.GetAttributeIdentifier(nameof(UnlockRecipe), Identifier.Empty); + UnlockRecipes = + element.GetAttributeIdentifierImmutableHashSet(nameof(UnlockRecipes), + //backwards compatibility + def: element.GetAttributeIdentifierImmutableHashSet("UnlockRecipe", ImmutableHashSet.Empty)); List propertyAttributes = new List(); propertyConditionals = new List(); @@ -1014,7 +1020,7 @@ namespace Barotrauma DebugConsole.AddWarning( $"StatusEffect tags defined using the attribute 'tags' in StatusEffect ({parentDebugName}). "+ "Please use the attribute 'statuseffecttags' or 'settags' instead to make it more explicit whether the 'tags' attribute means the status effect's tags, or tags the effect is supposed to set. " + - "The game now assumes it means the status effect's tags.", + "The game now assumes it means the status effect's tags.", contentPackage: element.ContentPackage); #endif } @@ -1038,7 +1044,7 @@ namespace Barotrauma //if the status effect has a duration, assume tags mean this status effect's tags and leave item tags untouched. propertyAttributes.RemoveAll(a => a.Name.ToString().Equals("tags", StringComparison.OrdinalIgnoreCase)); } - + List<(Identifier propertyName, object value)> propertyEffects = new List<(Identifier propertyName, object value)>(); foreach (XAttribute attribute in propertyAttributes) { @@ -1074,7 +1080,7 @@ namespace Barotrauma dropItem = true; break; case "removecharacter": - removeCharacter = true; + removeCharacter = true; containerForItemsOnCharacterRemoval = subElement.GetAttributeIdentifier("moveitemstocontainer", Identifier.Empty); break; case "breaklimb": @@ -1132,7 +1138,7 @@ namespace Barotrauma continue; } } - + Affliction afflictionInstance = afflictionPrefab.Instantiate(subElement.GetAttributeFloat(1.0f, "amount", nameof(afflictionInstance.Strength))); // Deserializing the object normally might cause some unexpected side effects. At least it clamps the strength of the affliction, which we don't want here. // Could probably be solved by using the NonClampedStrength or by bypassing the clamping, but ran out of time and played it safe here. @@ -1166,10 +1172,10 @@ namespace Barotrauma break; case "spawnitem": var newSpawnItem = new ItemSpawnInfo(subElement, parentDebugName); - if (newSpawnItem.ItemPrefab != null) + if (newSpawnItem.ItemPrefab != null) { spawnItems ??= new List(); - spawnItems.Add(newSpawnItem); + spawnItems.Add(newSpawnItem); } break; case "triggerevent": @@ -1247,7 +1253,7 @@ namespace Barotrauma Identifier[] expectedSpeciesNames = subElement.GetAttributeIdentifierArray("expectedspecies", Array.Empty()); animationsToTrigger ??= new List(); animationsToTrigger.Add(new AnimLoadInfo(animType, file, priority, expectedSpeciesNames.ToImmutableArray())); - + break; case "forcesay": forceSayIdentifier = subElement.GetAttributeIdentifier("message", Identifier.Empty); @@ -1294,7 +1300,7 @@ namespace Barotrauma return conditionValue < 0.0f || (setValue && conditionValue <= 0.0f); } } - return false; + return randomCondition.X < 0f || randomCondition.Y < 0f; } public bool IncreasesItemCondition() @@ -1306,7 +1312,7 @@ namespace Barotrauma return conditionValue > 0.0f || (setValue && conditionValue > 0.0f); } } - return false; + return randomCondition.X > 0f || randomCondition.Y > 0f; } private bool ChangesItemCondition(Identifier propertyName, object value, out float conditionValue) @@ -1383,7 +1389,7 @@ namespace Barotrauma if (HasTargetType(TargetType.NearbyItems)) { //optimization for powered components that can be easily fetched from Powered.PoweredList - if (TargetIdentifiers != null && + if (TargetIdentifiers != null && TargetIdentifiers.Count == 1 && (TargetIdentifiers.Contains("powered") || TargetIdentifiers.Contains("junctionbox") || TargetIdentifiers.Contains("relaycomponent"))) { @@ -1491,8 +1497,8 @@ namespace Barotrauma { owner = ownerItem.ParentInventory?.Owner; } - if (owner is Item container) - { + if (owner is Item container) + { if (pc.Type == PropertyConditional.ConditionType.HasTag) { //if we're checking for tags, just check the Item object, not the ItemComponents @@ -1500,8 +1506,8 @@ namespace Barotrauma } else { - if (shouldShortCircuit(AnyTargetMatches(container.AllPropertyObjects, pc.TargetItemComponent, pc), out valueToReturn)) { return valueToReturn; } - } + if (shouldShortCircuit(AnyTargetMatches(container.AllPropertyObjects, pc.TargetItemComponent, pc), out valueToReturn)) { return valueToReturn; } + } } if (owner is Character character && shouldShortCircuit(pc.Matches(character), out valueToReturn)) { return valueToReturn; } } @@ -1673,7 +1679,7 @@ namespace Barotrauma PlaySound(entity, GetHull(entity), GetPosition(entity, targets, worldPosition)); } #endif - return; + return; } if (Duration > 0.0f && !Stackable) @@ -1743,7 +1749,7 @@ namespace Barotrauma } } } - + } position += Offset; position += Rand.Vector(Rand.Range(0.0f, RandomOffset)); @@ -1784,7 +1790,7 @@ namespace Barotrauma } for (int i = 0; i < targets.Count; i++) { - if (targets[i] is not Item item) { continue; } + if (targets[i] is not Item item) { continue; } for (int j = 0; j < useItemCount; j++) { if (item.Removed) { continue; } @@ -1807,8 +1813,8 @@ namespace Barotrauma { for (int i = 0; i < targets.Count; i++) { - if (targets[i] is Item item) - { + if (targets[i] is Item item) + { foreach (var itemContainer in item.GetComponents()) { foreach (var containedItem in itemContainer.Inventory.AllItemsMod) @@ -1885,6 +1891,11 @@ namespace Barotrauma if (target is Entity targetEntity) { if (targetEntity.Removed) { continue; } + if (targetEntity is Item targetItem && randomCondition != Vector2.Zero) + { + float newCondition = Rand.Range(randomCondition.X, randomCondition.Y); + targetItem.Condition = GetModifiedValue(targetItem.Condition, newCondition, deltaTime); + } } else if (target is Limb limb) { @@ -1893,10 +1904,7 @@ namespace Barotrauma } foreach (var (propertyName, value) in PropertyEffects) { - if (!target.SerializableProperties.TryGetValue(propertyName, out SerializableProperty property)) - { - continue; - } + if (!target.SerializableProperties.TryGetValue(propertyName, out SerializableProperty property)) { continue; } ApplyToProperty(target, property, value, deltaTime); } } @@ -2034,10 +2042,10 @@ namespace Barotrauma } } } - + TryTriggerAnimation(target, entity); - if (!forceSayIdentifier.IsEmpty) + if (!forceSayIdentifier.IsEmpty) { LocalizedString messageToSay = TextManager.Get(forceSayIdentifier).Fallback(forceSayIdentifier.Value); @@ -2121,7 +2129,7 @@ namespace Barotrauma foreach (GiveTalentInfo giveTalentInfo in giveTalentInfos) { if (giveTalentInfo.GiveRandom) - { + { // for the sake of technical simplicity, for now do not allow talents to be given if the character could unlock them in their talent tree as well IEnumerable viableTalents = giveTalentInfo.TalentIdentifiers.Where(id => !targetCharacter.Info.UnlockedTalents.Contains(id) && !characterTalentTree.AllTalentIdentifiers.Contains(id)); if (viableTalents.None()) { continue; } @@ -2142,8 +2150,8 @@ namespace Barotrauma { foreach ((Identifier eventId, Identifier tag) in eventTargetTags) { - if (GameMain.GameSession.EventManager.ActiveEvents.FirstOrDefault(e => e.Prefab.Identifier == eventId) is ScriptedEvent ev) - { + if (GameMain.GameSession.EventManager.ActiveEvents.FirstOrDefault(e => e.Prefab.Identifier == eventId) is ScriptedEvent ev) + { targets.Where(t => t is Entity).ForEach(t => ev.AddTarget(tag, (Entity)t)); } } @@ -2157,9 +2165,13 @@ namespace Barotrauma fire.Size = new Vector2(FireSize, fire.Size.Y); } - if (isNotClient && !UnlockRecipe.IsEmpty && GameMain.GameSession is { } gameSession) + if (isNotClient && UnlockRecipes.Any() && GameMain.GameSession is { } gameSession) { - gameSession.UnlockRecipe(UnlockRecipe, showNotifications: true); + foreach (var unlockRecipe in UnlockRecipes) + { + Character targetCharacter = user ?? targets.Select(GetCharacterFromTarget).NotNull().FirstOrDefault(); + gameSession.UnlockRecipe(targetCharacter?.TeamID ?? CharacterTeamType.Team1, unlockRecipe, showNotifications: true); + } } if (isNotClient && triggeredEvents != null && GameMain.GameSession?.EventManager is { } eventManager) @@ -2168,7 +2180,7 @@ namespace Barotrauma { Event ev = eventPrefab.CreateInstance(eventManager.RandomSeed); if (ev == null) { continue; } - eventManager.QueuedEvents.Enqueue(ev); + eventManager.QueuedEvents.Enqueue(ev); if (ev is ScriptedEvent scriptedEvent) { if (!triggeredEventTargetTag.IsEmpty) @@ -2198,7 +2210,7 @@ namespace Barotrauma foreach (CharacterSpawnInfo characterSpawnInfo in spawnCharacters) { var characters = new List(); - + CharacterTeamType? inheritedTeam = null; if (characterSpawnInfo.InheritTeam) { @@ -2211,16 +2223,16 @@ namespace Barotrauma _ => null // Default to Team1, when we can't deduce the team (for example when spawning outside the sub AND character inventory). } ?? (isPvP ? CharacterTeamType.None : CharacterTeamType.Team1); - + CharacterTeamType? GetTeamFromSubmarine(MapEntity e) { if (e.Submarine == null) { return null; } // Don't allow team FriendlyNPC in outposts, because if you buy a spawner item (such as husk container) from the store and choose to get it immediately, it will be spawned in the outpost. - return !isPvP && e.Submarine.Info.IsOutpost && e.Submarine.TeamID == CharacterTeamType.FriendlyNPC ? + return !isPvP && e.Submarine.Info.IsOutpost && e.Submarine.TeamID == CharacterTeamType.FriendlyNPC ? CharacterTeamType.Team1 : e.Submarine.TeamID; } } - + for (int i = 0; i < characterSpawnInfo.Count; i++) { Entity.Spawner.AddCharacterToSpawnQueue(characterSpawnInfo.SpeciesName, position + Rand.Vector(characterSpawnInfo.Spread, Rand.RandSync.Unsynced) + characterSpawnInfo.Offset, @@ -2311,11 +2323,11 @@ namespace Barotrauma Character.Controlled = newCharacter; } #elif SERVER - foreach (Client c in GameMain.Server.ConnectedClients) - { - if (c.Character != target) { continue; } - GameMain.Server.SetClientCharacter(c, newCharacter); - } + foreach (Client c in GameMain.Server.ConnectedClients) + { + if (c.Character != target) { continue; } + GameMain.Server.SetClientCharacter(c, newCharacter); + } #endif } if (characterSpawnInfo.RemovePreviousCharacter) { Entity.Spawner?.AddEntityToRemoveQueue(character); } @@ -2477,7 +2489,7 @@ namespace Barotrauma position = entity.WorldPosition; if (entity is Item it) { - sourceBody ??= + sourceBody ??= (entity as Item)?.body ?? (entity as Character)?.AnimController.Collider; } @@ -2532,7 +2544,7 @@ namespace Barotrauma else if (parentItem != null) { rotation = PhysicsBody.TransformRotation( - -parentItem.RotationRad + chosenItemSpawnInfo.RotationRad, + -parentItem.RotationRad + chosenItemSpawnInfo.RotationRad, dir: parentItem.FlippedX ? -1.0f : 1.0f); } break; @@ -2758,29 +2770,17 @@ namespace Barotrauma private void ApplyToProperty(ISerializableEntity target, SerializableProperty property, object value, float deltaTime) { - if (disableDeltaTime || setValue) { deltaTime = 1.0f; } - if (value is int || value is float) + if (value is int or float) { - float propertyValueF = property.GetFloatValue(target); + float newValue = GetModifiedValue(property.GetFloatValue(target), Convert.ToSingle(value), deltaTime); if (property.PropertyType == typeof(float)) { - float floatValue = value is float single ? single : (int)value; - floatValue *= deltaTime; - if (!setValue) - { - floatValue += propertyValueF; - } - property.TrySetValue(target, floatValue); + property.TrySetValue(target, newValue); return; } else if (property.PropertyType == typeof(int)) { - int intValue = (int)(value is float single ? single * deltaTime : (int)value * deltaTime); - if (!setValue) - { - intValue += (int)propertyValueF; - } - property.TrySetValue(target, intValue); + property.TrySetValue(target, (int)newValue); return; } } @@ -2792,6 +2792,13 @@ namespace Barotrauma property.TrySetValue(target, value); } + private float GetModifiedValue(float currValue, float newValue, float deltaTime) + { + if (setValue) { return newValue; } + if (disableDeltaTime) { deltaTime = 1f; } + return currValue + newValue * deltaTime; + } + public static void UpdateAll(float deltaTime) { UpdateAllProjSpecific(deltaTime); @@ -2848,7 +2855,7 @@ namespace Barotrauma element.Parent.RegisterTreatmentResults(element.Parent.user, element.Entity as Item, limb, affliction, result); } } - + foreach ((Identifier affliction, float amount) in element.Parent.ReduceAffliction) { Limb targetLimb = null; @@ -2894,10 +2901,10 @@ namespace Barotrauma element.Parent.TryTriggerAnimation(target, element.Entity); } - element.Parent.ApplyProjSpecific(deltaTime, - element.Entity, - element.Targets, - element.Parent.GetHull(element.Entity), + element.Parent.ApplyProjSpecific(deltaTime, + element.Entity, + element.Targets, + element.Parent.GetHull(element.Entity), element.Parent.GetPosition(element.Entity, element.Targets), playSound: element.Timer >= element.Duration); @@ -2970,7 +2977,7 @@ namespace Barotrauma if (limb == null) { return; } foreach (Affliction limbAffliction in limb.character.CharacterHealth.GetAllAfflictions()) { - if (result.Afflictions != null && + if (result.Afflictions != null && /* "affliction" is the affliction directly defined in the status effect (e.g. "5 internal damage (per second / per frame / however the effect is defined to run)"), * "result" is how much we actually applied of that affliction right now (taking into account the elapsed time, resistances and such) */ result.Afflictions.FirstOrDefault(a => a.Prefab == limbAffliction.Prefab) is Affliction resultAffliction && diff --git a/Barotrauma/BarotraumaShared/SharedSource/Text/RichString.cs b/Barotrauma/BarotraumaShared/SharedSource/Text/RichString.cs index 7e8300c5c..285d1a30c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Text/RichString.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Text/RichString.cs @@ -1,7 +1,6 @@ #nullable enable using System; using System.Collections.Immutable; -using System.Diagnostics; namespace Barotrauma { @@ -30,6 +29,8 @@ namespace Barotrauma public LocalizedString NestedStr { get; private set; } public readonly LocalizedString SanitizedString; + public bool Loaded => loaded; + #if CLIENT private readonly GUIFont? font; private readonly GUIComponentStyle? componentStyle; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs index 1f22f9682..e3acf1d32 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; +using System.Threading; + #if CLIENT using Barotrauma.Networking; using Barotrauma.Steam; @@ -99,7 +101,8 @@ namespace Barotrauma.IO this System.Xml.Linq.XDocument doc, string path, System.Xml.Linq.SaveOptions saveOptions = System.Xml.Linq.SaveOptions.None, - bool throwExceptions = false) + bool throwExceptions = false, + int maxRetries = 0) { if (!Validation.CanWrite(path, false)) { @@ -114,7 +117,21 @@ namespace Barotrauma.IO } return; } - doc.Save(path, saveOptions); + + for (int i = 0; i <= maxRetries; i++) + { + try + { + doc.Save(path, saveOptions); + break; + } + catch (IOException e) + { + if (i >= maxRetries) { throw; } + DebugConsole.NewMessage("Failed save XML document {" + e.Message + "}, retrying in 250 ms..."); + Thread.Sleep(250); + } + } } public static void SaveSafe(this System.Xml.Linq.XElement element, string path, bool throwExceptions = false) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs index 66151feec..67d2740dd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs @@ -116,10 +116,23 @@ namespace Barotrauma #if SERVER get { return Path.Combine(GetSaveFolder(SaveType.Singleplayer), "temp_server"); } #else - get { return Path.Combine(GetSaveFolder(SaveType.Singleplayer), "temp"); } + get + + { + string tempFolder = Path.Combine(GetSaveFolder(SaveType.Singleplayer), "temp"); +#if DEBUG + if (GameClient.MultiClientTestMode && GameMain.Client != null) + { + //append the name of the client to the download folder to avoid multiple clients + //from trying to download a file into the same path at the same time + tempFolder += "_" + GameMain.Client.Name; + } +#endif + return tempFolder; + } #endif } - + public static void EnsureSaveFolderExists() { try diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index f6878cdb4..de28279f8 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,69 @@ +------------------------------------------------------------------------------------------------------------------------------------------------- +v1.10.5.0 +------------------------------------------------------------------------------------------------------------------------------------------------- + +Balance: +- Railgun rebalance: The railgun has been deemed on the weaker side by the community. It has been given a satisfaction pass, where now the high-power shell behaves more as expected, capable of penetrating multiple limbs or submarine walls, dealing more damage. +- Broad-spectrum Antibiotics now affect all limbs with infected wound with one application, but cure less infection per limb. Base prices for Plastiseal, Antibiotic Glue and Broad-Spectrum Antibiotics have been adjusted. +- Antibiotic Glue is now less effective on healing burns, bleeding and infections and inflicts organ damage even with successful application (and even more with failure). +- Added a pass to make grenades more easy to aim (and less likely to always hit the wall in the back due to excessive sliding/rolling) + - Reduced and equalized throwforce of most throwables (grenades, explosives) to 3.5 (some were 3.5 already, some 4.0). + - Add angular dampening on grenades to reduce excessive sliding/rolling. + - Frag grenade now uses a rectangular body instead of round/capsule, to avoid rolling too far (more in line with other grenades). + - Reduced throw force when aiming throwables downwards. +- Lower the rate at which skyholder artifacts and portable pumps drain water (both were way too effective for managing leaks). +- Made all variants of fractal guardians vulnerable to EMPs. +- Security NPCs on submarine encounters are now better armed, with weapons fitting the faction. + +Blueprints: +- Added new "blueprint" type of items, which unlock crafting recipes. +- Three new alien material blueprints: physicorium, dementonite (gravity) and incendium, which unlock relevant (ammo) recipes. +- Alien blueprints need to be researched at a Research station. +- Merchants of all factions now sell item blueprints fitting to the faction's identity, locked behind reputation. + +Miscellaneous changes: +- Made vent sounds a bit more quiet. +- Made crate shelf and makeshift shelf container UIs vertical to match the look of the sprites. +- Made some items be attachable only to the floor. + - Planters cannot overlap, and attach only to the floor, rather than attaching to the wall. + - Makeshift shelves are attached to the floor. +- Outpost NPCs react to players dragging other outpost NPC's corpses. Normal NPCs flee, security arrests you. + +Fixes: +- Fixed gaps significantly slowing down the flow of water, especially when water is flowing up through the gaps. +- Fixed PvP outpost selection not working reliably (when you selected some specific outpost, it was possible for some other outpost to get selected regardless). +- Fixed bot AI not running if a bot below 0 vitality is forced to stay unconscious with e.g. adrenaline or talents. +- Fixed incorrect offset on the light that renders on a boarding pod that's in a loader. +- Fixed defense bot only escaping if it attemps to fire it's weapon and fails due to being out of ammo, but not when it doesn't have any ammo available. +- Fixed outpost NPCs sometimes spawning on non-human spawnpoints, e.g. the monster spawnpoints in research modules. +- Fixed NPC conversations about the submarine being very deep starting to appear too late (at the point when the sub is already at crush depth, instead of the point where camera shake and audio effects start appearing). +- Fixed yet another issue with modded hairs: hairs got misaligned in the bottom-right character portrait if they were set to inherit the origin of the head sprite, and the head sprite's origin was not at the center. +- Fixed all pets in the level getting added to the player crew at the end of a round, e.g. even hostile pets or pets inside beacon stations. +- Fixed id cards only being sold in normal outposts and cities (not mining, research or military outposts). +- Fixed loading screen tips often cycling too fast to read during the initial loading screen. +- Fixed "Find Jacov Subra" mission completing if you enter the outpost he's hiding in, even if you don't find him. +- Fixed vents' oxygen output warning being incorrectly shown when vents have been moved between hulls in the sub editor. +- The "drop item" hotkey is disabled when the inventory is not visible (e.g. when operating a turret). +- Fixed inability to issue orders that don't target any character in particular (e.g. ignore or deconstruct orders) when no-one can hear the order. +- Server log fixes: + - Fixed new lines added to the log ignoring the filtering. + - Fixed log jumping up when new lines are added while you've scrolled up to read older messages. + +Modding: +- When saving a submarine or an outpost in the editor, the game suggests saving it in a subfolder that contains subs of the same type instead of always saving it in the root folder of the mod. +- Fixed "Equipped" item requirement only checking held items, not worn items as the documentation says. +- Option to define tags for wrecks and beacon stations in the sub editor, and to make a wreck or beacon mission choose a random wreck or beacon station with the specified tags using the attributes "BeaconTags" and "WreckTags". +- Fixed new elements defined in an item variant failing to be added to the variant if there's ClearAll elements present. +- Fixed submarine upgrade UI displaying an incorrect icon on items that don't have an inventory icon but use the item's normal sprite instead. +- The "sort stacks" and "merge stacks" buttons can be hidden from containers using the attributes "ShowSortButton" and "ShowMergeButton". +- Changed how store item availabilities work by default: if no min/max amount is defined, there can be 0-5 items available instead of there always being 5. +- Some new decorative outpost props: empty versions of the existing storage shelves which contained some decorative items. +- Fixed game freezing if a bot uses a weapon that has both a RangedWeapon and a RepairTool component. +- AnimController properties can now be accessed using conditionals. +- Fixed bots being able to unequip and reload non-interactable items if they have those in their inventory (e.g. if some custom event forces them to spawn with non-interactable items). +- Fixed corpses spawning in wrecks that have neither human or corpse spawnpoints (making it impossible to create wrecks with no corpses). +- Fixed unnecessary console errors about failing to load a texture when trying to preload some monster/enemy with [GENDER] tags in the filename. + ------------------------------------------------------------------------------------------------------------------------------------------------- v1.9.8.0 ------------------------------------------------------------------------------------------------------------------------------------------------- @@ -76,6 +142,16 @@ Fixes: - Fixed inability to focus on unconscious characters by clicking on the crew list. - Fixed parts of the Jove sculptures found in ruins being misaligned in mirrored levels (= when travelling through the level backwards). - Fixed the Multi-tool not functioning as a screwdriver for event checks. + +Changes and additions: +- Upgraded to .NET 8. This should not cause any noticeable changes, aside from perhaps very minor performance improvements. Allows code mods to use C# 12 features. +- Added option to filter by name and to change the sorting of the save files listed in the "load game" menu. +- Made circuit boxes' external connections (the once that hook up to other items) use the same labels that have been set inside the circuit box. +- Made the freecam console command a toggle: entering it again gives you back control of the character. +- Added "loslightingfreecam" console command (convenient for testing, executes those 3 commands). + +Fixes: +- Fixed bots being reluctant to go into rooms with low oxygen, even if they're trying to get diving gear from that room. - Fixed "fake fires" you see when under psychosis sometimes turning into real fires in single player. - Fixed PvP mode weapon crates showing up in the sub editor. - Fixed undoing the removal of an item from a container sometimes causing a crash in the sub editor. diff --git a/Libraries/BarotraumaLibs/BarotraumaCore/Networking/Primitives/NetworkEnums.cs b/Libraries/BarotraumaLibs/BarotraumaCore/Networking/Primitives/NetworkEnums.cs index b7c9866e0..5e7c5bd04 100644 --- a/Libraries/BarotraumaLibs/BarotraumaCore/Networking/Primitives/NetworkEnums.cs +++ b/Libraries/BarotraumaLibs/BarotraumaCore/Networking/Primitives/NetworkEnums.cs @@ -32,7 +32,8 @@ namespace Barotrauma.Networking IsDisconnectMessage = 0x4, IsServerMessage = 0x8, IsHeartbeatMessage = 0x10, - IsDataFragment = 0x20 + IsDataFragment = 0x20, + IsDoSProtectionMessage = 0x40, } public static class NetworkEnumExtensions @@ -51,9 +52,12 @@ namespace Barotrauma.Networking public static bool IsHeartbeatMessage(this PacketHeader h) => h.HasFlag(PacketHeader.IsHeartbeatMessage); - + public static bool IsDataFragment(this PacketHeader h) => h.HasFlag(PacketHeader.IsDataFragment); + + public static bool IsDoSProtectionMessage(this PacketHeader h) + => h.HasFlag(PacketHeader.IsDoSProtectionMessage); } public static class NetworkMagicStrings diff --git a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatch.cs b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatch.cs index 2d08ff6ec..b5775bbc6 100644 --- a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatch.cs +++ b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatch.cs @@ -91,7 +91,7 @@ namespace Microsoft.Xna.Framework.Graphics Effect _spriteEffect; readonly EffectParameter _matrixTransform; - readonly EffectPass _spritePass; + EffectPass _spritePass; Matrix? _matrix; private Viewport _lastViewport; @@ -238,6 +238,7 @@ namespace Microsoft.Xna.Framework.Graphics else _matrixTransform.SetValue(_projection); + _spritePass = _spriteEffect.CurrentTechnique.Passes[0]; _spritePass.Apply(); } From bad999d5fc31c781a08516a4f525d71183477ff5 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 17 Sep 2025 13:45:40 +0300 Subject: [PATCH 4/4] Update bug-reports.yml --- .github/DISCUSSION_TEMPLATE/bug-reports.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/DISCUSSION_TEMPLATE/bug-reports.yml b/.github/DISCUSSION_TEMPLATE/bug-reports.yml index ef6294c28..e9c69188b 100644 --- a/.github/DISCUSSION_TEMPLATE/bug-reports.yml +++ b/.github/DISCUSSION_TEMPLATE/bug-reports.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.9.8.0 (Summer Update Hotfix 1) - - v1.10.2.0 (unstable) + - v1.10.5.0 (Autumn Update 2025) - Other validations: required: true