Skip to content

Commit cfab935

Browse files
committed
Merge branch 'master' of https://github.com/Regalis11/Barotrauma into develop
2 parents b8fd898 + bad999d commit cfab935

File tree

121 files changed

+2579
-632
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

121 files changed

+2579
-632
lines changed

.github/DISCUSSION_TEMPLATE/bug-reports.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ body:
7373
label: Version
7474
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.
7575
options:
76-
- v1.9.8.0 (Summer Update Hotfix 1)
76+
- v1.10.5.0 (Autumn Update 2025)
7777
- Other
7878
validations:
7979
required: true

Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,12 @@ public void DrawIcon(SpriteBatch spriteBatch, Vector2 screenPos, Vector2 targetA
419419
float scale = Math.Min(targetAreaSize.X / headSprite.size.X, targetAreaSize.Y / headSprite.size.Y);
420420
headSprite.SourceRect = new Rectangle(CalculateOffset(headSprite, Head.SheetIndex.ToPoint()), headSprite.SourceRect.Size);
421421
SetHeadEffect(spriteBatch);
422-
headSprite.Draw(spriteBatch, screenPos, scale: scale, color: Head.SkinColor, spriteEffect: spriteEffects);
422+
Vector2 origin = headSprite.Origin;
423+
if (flip)
424+
{
425+
origin.X = headSprite.size.X - origin.X;
426+
}
427+
headSprite.Draw(spriteBatch, screenPos, origin: origin, scale: scale, color: Head.SkinColor, spriteEffect: spriteEffects);
423428
if (AttachmentSprites != null)
424429
{
425430
float depthStep = 0.000001f;
@@ -467,6 +472,14 @@ private void DrawAttachmentSprite(SpriteBatch spriteBatch, WearableSprite attach
467472
{
468473
origin = head.Origin;
469474
attachment.Sprite.Origin = origin;
475+
if (spriteEffects.HasFlag(SpriteEffects.FlipHorizontally))
476+
{
477+
origin.X = head.size.X - origin.X;
478+
}
479+
if (spriteEffects.HasFlag(SpriteEffects.FlipVertically))
480+
{
481+
origin.Y = head.size.Y - origin.Y;
482+
}
470483
}
471484
else
472485
{
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
using Microsoft.Xna.Framework;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Xml.Linq;
6+
7+
namespace Barotrauma
8+
{
9+
internal static partial class DebugConsole
10+
{
11+
private static void InitShowSoldItems()
12+
{
13+
commands.Add(new Command("showsolditems",
14+
"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)]: " +
15+
"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.",
16+
(string[] args) =>
17+
{
18+
string filter = args.Length > 0 ? args[0].ToLowerInvariant() : null;
19+
bool includeStores = args.Length <= 1 || !args[1].Equals("false", StringComparison.InvariantCultureIgnoreCase);
20+
bool soldOnly = args.Length <= 2 || !args[2].Equals("false", StringComparison.InvariantCultureIgnoreCase);
21+
int limit = 50;
22+
if (args.Length > 3 && int.TryParse(args[3], out int parsedLimit))
23+
{
24+
limit = Math.Max(1, parsedLimit);
25+
}
26+
bool hideStoreOverrides = args.Length > 4 && args[4].Equals("true", StringComparison.InvariantCultureIgnoreCase);
27+
28+
var itemsWithPrice = ItemPrefab.Prefabs
29+
.Where(item => item.ConfigElement.Element.Element("Price") != null);
30+
31+
// apply filtering
32+
var matchingItems = itemsWithPrice
33+
.OrderBy(i => i.Name.Value)
34+
.Where(item => MatchesFilter(item, filter, includeStores, soldOnly))
35+
.ToList();
36+
37+
// output results
38+
NewMessage("=== Shop Item Availability ===", Color.Cyan);
39+
NewMessage($"Filter: {filter ?? "all"}, IncludeStores: {includeStores}, SoldOnly: {soldOnly}, Limit: {limit}, HideStoreOverrides: {hideStoreOverrides}", Color.Yellow);
40+
NewMessage($"Items: {matchingItems.Count} matching out of {itemsWithPrice.Count()} being sold (showing first {Math.Min(limit, matchingItems.Count)})", Color.LightGreen);
41+
NewMessage("", Color.White);
42+
43+
foreach (var item in matchingItems.Take(limit))
44+
{
45+
PrintItemInfo(item, hideStoreOverrides);
46+
}
47+
},
48+
getValidArgs: () =>
49+
[
50+
["all", "no-defined", "only-min", "only-max", "name:*"], // filter
51+
["true", "false"], // includeStores
52+
["true", "false"], // soldOnly
53+
["10", "25", "50", "100"], // limit suggestions
54+
["false", "true"] // hidestoreoverrides
55+
]));
56+
}
57+
58+
private static bool MatchesFilter(ItemPrefab item, string filter, bool includeStores, bool soldOnly)
59+
{
60+
var priceElement = item.ConfigElement.Element.Element("Price");
61+
if (priceElement == null) { return false; } // No price = not sold = don't include
62+
if (!includeStores) { return MatchesPriceElement(priceElement, item, filter, soldOnly); }
63+
64+
// Check if Base element matches first...
65+
if (MatchesPriceElement(priceElement, item, filter, soldOnly)) { return true; }
66+
67+
// ...then check store-specific price element matches
68+
foreach (var storeElement in priceElement.Elements().Where(e => e.Name == "Price"))
69+
{
70+
if (MatchesPriceElement(storeElement, item, filter, soldOnly)) { return true; }
71+
}
72+
73+
return false;
74+
}
75+
76+
private static bool MatchesPriceElement(XElement priceEl, ItemPrefab itemPrefab, string filter, bool soldOnly)
77+
{
78+
bool isSold = PriceInfo.GetSold(priceEl, true);
79+
if (soldOnly && !isSold) { return false; }
80+
if (filter == null) { return true; }
81+
82+
// Handle name pattern matching
83+
if (filter.StartsWith("name:"))
84+
{
85+
string pattern = filter[5..];
86+
string name = itemPrefab.Name.Value.ToLowerInvariant();
87+
string identifier = itemPrefab.Identifier.Value.ToLowerInvariant();
88+
89+
// If pattern contains '*', treat as wildcard (convert to regex)
90+
if (pattern.Contains('*'))
91+
{
92+
// Escape regex special chars except *
93+
string regexPattern = System.Text.RegularExpressions.Regex.Escape(pattern).Replace("\\*", ".*");
94+
return System.Text.RegularExpressions.Regex.IsMatch(name, $"^{regexPattern}$")
95+
|| System.Text.RegularExpressions.Regex.IsMatch(identifier, $"^{regexPattern}$");
96+
}
97+
else
98+
{
99+
// No wildcards: match exactly
100+
return name == pattern || identifier == pattern;
101+
}
102+
}
103+
104+
bool hasMin = PriceInfo.HasMinAmountDefined(priceEl);
105+
bool hasMax = PriceInfo.HasMaxAmountDefined(priceEl);
106+
107+
// Apply the filter logic
108+
return filter switch
109+
{
110+
"no-defined" => !hasMin && !hasMax, // Neither min nor max defined
111+
"only-min" => hasMin && !hasMax, // Only min defined
112+
"only-max" => !hasMin && hasMax, // Only max defined
113+
_ => true // No filter or show all
114+
};
115+
}
116+
117+
private static void PrintItemInfo(ItemPrefab item, bool hideStoreOverrides = false)
118+
{
119+
var priceElement = item.ConfigElement.Element.Element("Price");
120+
if (priceElement == null) { return; }
121+
122+
bool hasMinDefined = PriceInfo.HasMinAmountDefined(priceElement);
123+
bool hasMaxDefined = PriceInfo.HasMaxAmountDefined(priceElement);
124+
125+
string minRaw = PriceInfo.GetMinAmountString(priceElement);
126+
string maxRaw = PriceInfo.GetMaxAmountString(priceElement);
127+
int minLevelDifficulty = PriceInfo.GetMinLevelDifficulty(priceElement, 0);
128+
129+
// Get the resolved values (what PriceInfo would actually use)
130+
var priceInfo = new PriceInfo(priceElement);
131+
int resolvedMin = priceInfo.MinAvailableAmount;
132+
int resolvedMax = priceInfo.MaxAvailableAmount;
133+
134+
string minStatus = hasMinDefined ? $"XML:{minRaw}" : "DEFAULT:1";
135+
string maxStatus = hasMaxDefined ? $"XML:{maxRaw}" : "DEFAULT:5";
136+
137+
string minLevelInfo = minLevelDifficulty > 0 ? $" | MinLvl: {minLevelDifficulty}" : "";
138+
NewMessage($"{item.Name} ({item.Identifier}) | Min: {minStatus}{resolvedMin} | Max: {maxStatus}{resolvedMax}{minLevelInfo}", Color.White);
139+
140+
if (hideStoreOverrides) { return; }
141+
142+
var storeOverrides = priceElement.Elements().Where(e => e.Name == "Price")
143+
.Select(p => {
144+
string storeId = PriceInfo.GetStoreIdentifier(p, "unknown");
145+
string storeMin = PriceInfo.GetMinAmountString(p);
146+
string storeMax = PriceInfo.GetMaxAmountString(p);
147+
bool? storeSold = PriceInfo.HasSoldDefined(p) ? PriceInfo.GetSold(p, true) : null;
148+
149+
// Check if this store overrides anything
150+
if (storeMin != null || storeMax != null || storeSold != null)
151+
{
152+
var parts = new List<string>();
153+
if (storeMin != null || storeMax != null)
154+
{
155+
parts.Add($"min:{storeMin ?? "base"}, max:{storeMax ?? "base"}");
156+
}
157+
if (storeSold != null)
158+
{
159+
parts.Add($"sold:{storeSold.Value.ToString().ToLowerInvariant()}");
160+
}
161+
return $"{storeId}({string.Join(", ", parts)})";
162+
}
163+
return null;
164+
})
165+
.Where(s => s != null)
166+
.ToList();
167+
168+
if (storeOverrides.Count != 0)
169+
{
170+
NewMessage($" Store overrides: {string.Join(", ", storeOverrides)}", Color.Gray);
171+
}
172+
}
173+
}
174+
}

Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,8 @@ private static void AssignRelayToServer(string names, bool relay)
399399

400400
private static void InitProjectSpecific()
401401
{
402+
InitShowSoldItems();
403+
402404
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 =>
403405
{
404406
if (!EosInterface.Core.IsInitialized)
@@ -3476,6 +3478,13 @@ void getTextsFromElement(XElement element, List<string> list, string parentName)
34763478
}
34773479
}
34783480
}));
3481+
3482+
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) =>
3483+
{
3484+
GameClient.MultiClientTestMode = !GameClient.MultiClientTestMode;
3485+
NewMessage($"{(GameClient.MultiClientTestMode ? "Enabled" : "Disabled")} MultiClientTestMode on the client.");
3486+
}));
3487+
AssignRelayToServer("multiclienttestmode", false);
34793488
#endif
34803489

34813490
commands.Add(new Command("reloadcorepackage", "", (string[] args) =>
@@ -4257,6 +4266,8 @@ void getTextsFromElement(XElement element, List<string> list, string parentName)
42574266
}));
42584267
}
42594268

4269+
4270+
42604271
private static void ReloadWearables(Character character, int variant = 0)
42614272
{
42624273
foreach (var limb in character.AnimController.Limbs)
@@ -4493,7 +4504,9 @@ public static void StartLocalMPSession(int numClients = 2)
44934504
#endif
44944505
System.Threading.Thread.Sleep(1000);
44954506
}
4496-
4507+
#if DEBUG
4508+
GameClient.MultiClientTestMode = true;
4509+
#endif
44974510
GameMain.Client = new GameClient("client1",
44984511
new LidgrenEndpoint(System.Net.IPAddress.Loopback, NetConfig.DefaultPort), "localhost", Option<int>.None());
44994512

@@ -4505,9 +4518,9 @@ public static void StartLocalMPSession(int numClients = 2)
45054518
{
45064519
System.Threading.Thread.Sleep(1000);
45074520
#if WINDOWS
4508-
Process.Start("Barotrauma.exe", arguments: "-connect server localhost -username client" + i);
4521+
Process.Start("Barotrauma.exe", arguments: "-connect server localhost -username client" + i + " -multiclienttestmode");
45094522
#else
4510-
Process.Start("./Barotrauma", arguments: "-connect server localhost -username client" + i);
4523+
Process.Start("./Barotrauma", arguments: "-connect server localhost -username client" + i + " -multiclienttestmode");
45114524
#endif
45124525
}
45134526
}

Barotrauma/BarotraumaClient/ClientSource/Events/Missions/GoToMission.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
{
33
partial class GoToMission : Mission
44
{
5-
public override bool DisplayAsCompleted => State >= Prefab.MaxProgressState;
5+
public override bool DisplayAsCompleted =>
6+
State >= Prefab.MaxProgressState &&
7+
//if there's some additional check for completion, don't display as completed until we've checked it and set the mission as completed
8+
(Completed || completeCheckDataAction == null);
69
public override bool DisplayAsFailed => false;
710
}
811
}

Barotrauma/BarotraumaClient/ClientSource/Events/Missions/SalvageMission.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ public override void ClientRead(IReadMessage msg)
139139
}
140140
}
141141

142-
public override IEnumerable<Entity> HudIconTargets => targets.Where(static t => !t.Retrieved && t.Item?.GetRootInventoryOwner() is not Character { IsLocalPlayer: true }).Select(static t => t.Item);
142+
public override IEnumerable<Entity> HudIconTargets => targets
143+
.Where(static t => t.Item != null && !t.Retrieved && t.Item?.GetRootInventoryOwner() is not Character { IsLocalPlayer: true })
144+
.Select(static t => t.Item);
143145
}
144146
}

Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System;
77
using System.Collections.Concurrent;
88
using System.Collections.Generic;
9+
using System.Collections.Immutable;
910
using System.Linq;
1011

1112
namespace Barotrauma
@@ -50,9 +51,15 @@ public bool PlayingSplashScreen
5051
}
5152

5253
private RichString selectedTip;
54+
55+
private string selectedTipString;
56+
private ImmutableArray<RichTextData>? selectedTipRichTextData;
57+
5358
private void SetSelectedTip(LocalizedString tip)
5459
{
5560
selectedTip = RichString.Rich(tip);
61+
selectedTipString = string.Empty;
62+
selectedTipRichTextData = null;
5663
}
5764

5865
public float LoadState;
@@ -165,21 +172,28 @@ public void Draw(SpriteBatch spriteBatch, GraphicsDevice graphics, float deltaTi
165172
textPos.Y += GUIStyle.LargeFont.MeasureString(loadText.ToUpper()).Y * 1.2f;
166173
}
167174

168-
if (GUIStyle.Font.HasValue && selectedTip != null)
175+
if (GUIStyle.Font.HasValue && selectedTip != null && !selectedTip.SanitizedValue.IsNullOrEmpty())
169176
{
170-
string wrappedTip = ToolBox.WrapText(selectedTip.SanitizedValue, GameMain.GraphicsWidth * 0.3f, GUIStyle.Font.Value);
177+
//store the string value of the LocalizedString to prevent the text from changing if/when new text packs are loaded during the loading screen
178+
if (selectedTipString.IsNullOrEmpty())
179+
{
180+
selectedTipString = selectedTip.SanitizedValue;
181+
selectedTipRichTextData = selectedTip.RichTextData;
182+
}
183+
184+
string wrappedTip = ToolBox.WrapText(selectedTipString, GameMain.GraphicsWidth * 0.3f, GUIStyle.Font.Value);
171185
string[] lines = wrappedTip.Split('\n');
172-
float lineHeight = GUIStyle.Font.MeasureString(selectedTip).Y;
186+
float lineHeight = GUIStyle.Font.MeasureString(selectedTipString).Y;
173187

174-
if (selectedTip.RichTextData != null)
188+
if (selectedTipRichTextData != null)
175189
{
176190
int rtdOffset = 0;
177191
for (int i = 0; i < lines.Length; i++)
178192
{
179193
GUIStyle.Font.DrawStringWithColors(spriteBatch, lines[i],
180194
new Vector2(textPos.X, (int)(textPos.Y + i * lineHeight)),
181195
Color.White,
182-
0f, Vector2.Zero, 1f, SpriteEffects.None, 0f, selectedTip.RichTextData.Value, rtdOffset);
196+
0f, Vector2.Zero, 1f, SpriteEffects.None, 0f, selectedTipRichTextData.Value, rtdOffset);
183197
rtdOffset += lines[i].Length;
184198
}
185199
}

0 commit comments

Comments
 (0)